[java-idp-plugin-vci] 01/01: Reviewing issuer-metadata flow, intercept, counter, profile id constant, headers, tests

Codeberg noreply at shibboleth.net
Fri Nov 6 11:41:50 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch dev/REVISSFLOW
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/20e631f3dbf9a7417ed928f7e8aa6c595f36bf2d

commit 20e631f3dbf9a7417ed928f7e8aa6c595f36bf2d
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Fri Nov 6 13:41:24 2026 +0200

    Reviewing issuer-metadata flow, intercept, counter, profile id constant, headers, tests
---
 README.md                                          |  2 +
 ...efaultOpenIDVCIIssuerMetadataConfiguration.java |  5 ++
 .../FormOutboundIssuerMetadataResponseMessage.java | 52 ++++++++++++--
 .../vci/issuer-metadata/issuer-metadata-beans.xml  | 20 ++----
 .../vci/issuer-metadata/issuer-metadata-flow.xml   |  6 +-
 .../plugin/openidvci/conf/openid-vci.properties    |  2 +
 ...ltOpenIDVCIIssuerMetadataConfigurationTest.java | 37 ++++++++++
 ...mOutboundIssuerMetadataResponseMessageTest.java | 83 +++++++++++++++++++++-
 8 files changed, 185 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index 5000174..979ed4a 100644
--- a/README.md
+++ b/README.md
@@ -4682,6 +4682,7 @@ Endpoint `/idp/profile/openid/vci/issuer-metadata`, profile ID `http://geant.org
 | Name | Type | Default | Description |
 |---|---|---|---|
 | `issuerMetadataSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.issuerMetadata.SigningConfiguration** | Signing of the metadata. Published unsigned until **openidvci.issuerMetadata.SigningCredentials** exists. Signed metadata is served for `Accept: application/jwt`. |
+| `inboundInterceptorFlows` | List<String> | | Interceptor flows run before the metadata is formed. |
 | `outboundInterceptorFlows` | List<String> | | Interceptor flows run before the response is sent, also for an error response. |
 
 **OAuth.JWTVCIssuer** (`/idp/profile/oauth/jwt-vc-issuer`) and **OAuth.AuthorizationServer**
@@ -4976,6 +4977,7 @@ The file comes with the plugin and is loaded automatically.
 | `openidvci.issuerMetadata.resolver.values` | Bean ID | **openidvci.issuerMetadata.DefaultDynamicValueResolvers** | Members resolved outside the file. |
 | `openidvci.issuerMetadata.minRefreshDelay`, `…maxRefreshDelay` | Duration | `PT5M`, `PT4H` | Bounds of re-reading the file. |
 | `openidvci.issuerMetadata.signedLifetime` | Duration | | `exp` of signed metadata. Unset, no `exp`. |
+| `openidvci.issuerMetadata.cacheControl` | String | `max-age=3600` | Cache-Control of the response. Keep `max-age` shorter than `signedLifetime`. |
 | `openidvci.jwtVcIssuer.template` | File pathname | `%{idp.home}/static/jwt-vc-issuer.json` | JWT VC issuer metadata template. |
 | `openidvci.jwtVcIssuer.templated` | Boolean | `true` | Whether the file is Velocity. |
 | `openidvci.jwtVcIssuer.resolver` | Bean ID | **openidvci.DefaultJWTVCIssuerMetadataResolver** | Resolver of the document. |
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfiguration.java
index 3034ffe..7bb33f4 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfiguration.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfiguration.java
@@ -50,6 +50,11 @@ public class DefaultOpenIDVCIIssuerMetadataConfiguration extends AbstractInterce
     @NotEmpty
     public static final String PROFILE_ID = "http://geant.org/ns/profiles/openid/vci/issuer-metadata";
 
+    /** Credential Issuer metadata profile counter name. */
+    @Nonnull
+    @NotEmpty
+    public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.openid.vci.issuer-metadata";
+
     /** Signing of the published metadata. */
     @Nonnull
     private Function<ProfileRequestContext, SignatureSigningConfiguration>
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
index 3ef7a23..ec6b380 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
@@ -37,19 +37,24 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.net.HttpHeaders;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.security.jose.SignatureException;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.ResolverException;
 
 /**
@@ -95,12 +100,28 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
     @NonnullBeforeExec
     private CredentialIssuerMetadata metadata;
 
+    /** Value of the Cache-Control header. */
+    @Nonnull
+    @NotEmpty
+    private String cacheControl;
+
     /** Constructor. */
     public FormOutboundIssuerMetadataResponseMessage() {
         final Function<ProfileRequestContext, SecurityParametersContext> splcs = new ChildContextLookup<>(
                 SecurityParametersContext.class).compose(new OutboundMessageContextLookup());
         assert splcs != null;
         securityParametersLookupStrategy = splcs;
+        cacheControl = "max-age=3600";
+    }
+
+    /**
+     * Set the value of the Cache-Control header.
+     *
+     * @param value header value
+     */
+    public void setCacheControl(@Nonnull @NotEmpty final String value) {
+        checkSetterPreconditions();
+        cacheControl = Constraint.isNotNull(StringSupport.trimOrNull(value), "Cache-Control cannot be null or empty");
     }
 
     /**
@@ -178,24 +199,33 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
+        final HttpServletResponse response = getHttpServletResponse();
+        if (response == null) {
+            log.error("{} Could not fetch HttpServletResponse", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return;
+        }
+
         if (prefersSignedMetadata()) {
             final SignatureSigningParameters parameters = resolveSigningParameters(profileRequestContext);
             if (parameters != null) {
-                publishSigned(profileRequestContext, parameters);
+                publishSigned(profileRequestContext, parameters, response);
                 return;
             }
             log.debug("{} Signed metadata was asked for but no signing credential is configured, "
                     + "publishing the document unsigned", getLogPrefix());
         }
 
+        final CredentialIssuerMetadataSuccessResponse unsigned;
         try {
-            profileRequestContext.ensureOutboundMessageContext()
-                    .setMessage(new CredentialIssuerMetadataSuccessResponse(metadata.getMembers()));
+            unsigned = new CredentialIssuerMetadataSuccessResponse(metadata.getMembers());
         } catch (final JsonProcessingException e) {
             log.error("{} Could not serialize Credential Issuer metadata", getLogPrefix(), e);
             ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
             return;
         }
+        setHeaders(response);
+        profileRequestContext.ensureOutboundMessageContext().setMessage(unsigned);
 
         log.debug("{} Publishing Credential Issuer metadata of {}, members {}", getLogPrefix(),
                 metadata.getCredentialIssuer(), metadata.getMembers().keySet());
@@ -206,9 +236,10 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
      *
      * @param profileRequestContext current profile request context
      * @param parameters            parameters to sign with
+     * @param response              response to set the headers of
      */
     private void publishSigned(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final SignatureSigningParameters parameters) {
+            @Nonnull final SignatureSigningParameters parameters, @Nonnull final HttpServletResponse response) {
 
         final Instant now = Instant.now();
         final JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder();
@@ -230,6 +261,7 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
             return;
         }
 
+        setHeaders(response);
         profileRequestContext.ensureOutboundMessageContext()
                 .setMessage(new SignedCredentialIssuerMetadataSuccessResponse(document));
 
@@ -237,6 +269,18 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
                 metadata.getCredentialIssuer(), metadata.getMembers().keySet());
     }
 
+    /**
+     * Set the headers of a published document.
+     *
+     * @param response response to set the headers of
+     */
+    private void setHeaders(@Nonnull final HttpServletResponse response) {
+        response.setHeader(HttpHeaders.CACHE_CONTROL, cacheControl);
+        response.setHeader(HttpHeaders.X_CONTENT_TYPE_OPTIONS, FormOutboundMetadataDocumentResponseMessage.NOSNIFF);
+        response.setHeader(HttpHeaders.CONTENT_SECURITY_POLICY,
+                FormOutboundMetadataDocumentResponseMessage.CONTENT_SECURITY_POLICY);
+    }
+
     /**
      * Get whether the request names signed metadata and not the unsigned document.
      *
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
index bc3d407..f911156 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
@@ -12,9 +12,11 @@
        default-init-method="initialize" default-destroy-method="destroy">
 
   <bean id="openidvci.profileId" class="java.lang.String"
-        c:_0="http://geant.org/ns/profiles/openid/vci/issuer-metadata"/>
+        c:_0="#{T(org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultOpenIDVCIIssuerMetadataConfiguration).PROFILE_ID}"/>
   <bean id="openidvci.loggingId" class="java.lang.String"
         c:_0="%{openidvci.logging.issuer-metadata:OpenID.VCI.IssuerMetadata}"/>
+  <util:constant id="shibboleth.metrics.ProfileCounter"
+        static-field="org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultOpenIDVCIIssuerMetadataConfiguration.PROFILE_COUNTER"/>
 
   <bean id="InitializeUnverifiedRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext" scope="prototype" />
@@ -27,21 +29,17 @@
       <bean lazy-init="true"
             class="org.geant.shibboleth.plugin.openidvci.profile.config.navigate.IssuerMetadataSignatureSigningConfigurationLookupFunction" />
     </property>
-    <!-- Metadata is signed with the issuer's own key, there is no client to have one. -->
     <property name="signatureSigningParametersResolver">
       <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
     </property>
-    <property name="securityParametersContextLookupStrategy">
-      <bean parent="shibboleth.Functions.Compose"
-            c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-            c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-    </property>
   </bean>
 
   <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundIssuerMetadataResponseMessage"
         scope="prototype"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
+        p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
+        p:cacheControl="%{openidvci.issuerMetadata.cacheControl:max-age=3600}"
         p:metadataResolver-ref="#{'%{openidvci.issuerMetadata.resolver:openidvci.DefaultCredentialIssuerMetadataResolver}'.trim()}"
         p:signedMetadataLifetime="#{'%{openidvci.issuerMetadata.signedLifetime:}'.trim().isEmpty() ? null : T(java.time.Duration).parse('%{openidvci.issuerMetadata.signedLifetime:}'.trim())}" />
 
@@ -51,14 +49,6 @@
     <property name="eventContextLookupStrategy">
       <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
     </property>
-    <property name="mappedErrors">
-      <map value-type="com.nimbusds.oauth2.sdk.ErrorObject">
-        <entry key="#{T(org.opensaml.profile.action.EventIds).IO_ERROR}"
-               value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).SERVER_ERROR}" />
-        <entry key="#{T(org.opensaml.profile.action.EventIds).INVALID_SEC_CFG}"
-               value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).SERVER_ERROR}" />
-      </map>
-    </property>
   </bean>
 
 </beans>
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-flow.xml
index 3840e91..8ec6ff0 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-flow.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-flow.xml
@@ -5,6 +5,9 @@
       parent="openid/vci/abstract-api">
 
   <action-state id="InitializeMandatoryContexts">
+    <on-entry>
+      <set name="flowScope.transitionAfterInboundIntercept" value="'BuildResponseMessage'"/>
+    </on-entry>
     <evaluate expression="InitializeProfileRequestContext" />
     <evaluate expression="PopulateMetricContext" />
     <evaluate expression="FlowStartPopulateAuditContext" />
@@ -13,9 +16,10 @@
     <evaluate expression="SelectRelyingPartyConfiguration" />
     <evaluate expression="SelectProfileConfiguration" />
     <evaluate expression="PopulateIssuerMetadataSignatureSigningParameters" />
+    <evaluate expression="PopulateInboundInterceptContext" />
     <evaluate expression="'proceed'" />
 
-    <transition on="proceed" to="BuildResponseMessage" />
+    <transition on="proceed" to="CheckInboundInterceptContext" />
   </action-state>
 
   <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml" />
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
index dd03190..3c109e6 100644
--- a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
@@ -53,6 +53,8 @@ openidvci.issuer =
 #openidvci.issuerMetadata.maxRefreshDelay = PT4H
 # Lifetime written as 'exp' of signed metadata. Leave unset to publish it without one.
 #openidvci.issuerMetadata.signedLifetime = PT4H
+# Cache-Control of the response, unsigned and signed. Keep max-age shorter than signedLifetime.
+#openidvci.issuerMetadata.cacheControl = max-age=3600
 
 # Signing key of issued Verifiable Credentials. Used only if you enable the credential of the
 # same name in conf/openid-vci-credentials.xml. Until that, Credentials are signed with the
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfigurationTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfigurationTest.java
new file mode 100644
index 0000000..8ec39a7
--- /dev/null
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCIIssuerMetadataConfigurationTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.shibboleth.plugin.openidvci.profile.config.impl;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/** Tests for {@link DefaultOpenIDVCIIssuerMetadataConfiguration}. */
+public class DefaultOpenIDVCIIssuerMetadataConfigurationTest {
+
+    @Test
+    public void testId() {
+        Assert.assertEquals(new DefaultOpenIDVCIIssuerMetadataConfiguration().getId(),
+                DefaultOpenIDVCIIssuerMetadataConfiguration.PROFILE_ID);
+    }
+
+    @Test
+    public void testCounter() {
+        Assert.assertEquals(DefaultOpenIDVCIIssuerMetadataConfiguration.PROFILE_COUNTER,
+                "net.shibboleth.idp.profiles.openid.vci.issuer-metadata");
+    }
+
+}
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
index 25eab5d..f5cacb1 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
@@ -49,6 +49,8 @@ import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
 
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
@@ -56,9 +58,11 @@ import net.shibboleth.oidc.jwa.support.SignatureConstants;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 import net.shibboleth.shared.primitive.NonnullSupplier;
 import net.shibboleth.shared.resolver.ResolverException;
 import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.webflow.execution.RequestContext;
 
 /**
@@ -72,6 +76,8 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
 
     private MockHttpServletRequest httpRequest;
 
+    private MockHttpServletResponse httpResponse;
+
     private ECKey signingKey;
 
     @BeforeMethod
@@ -80,6 +86,7 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
         profileRequestCtx = new WebflowRequestContextProfileRequestContextLookup().apply(requestCtx);
         profileRequestCtx.setOutboundMessageContext(new MessageContext());
         httpRequest = new MockHttpServletRequest();
+        httpResponse = new MockHttpServletResponse();
         signingKey = new ECKeyGenerator(Curve.P_256).keyID("metadata-signing").generate();
     }
 
@@ -100,6 +107,14 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
 
     private FormOutboundIssuerMetadataResponseMessage actionOf(final CredentialIssuerMetadata metadata,
             final Duration lifetime) throws Exception {
+        final FormOutboundIssuerMetadataResponseMessage action = uninitialized(metadata, true);
+        action.setSignedMetadataLifetime(lifetime);
+        action.initialize();
+        return action;
+    }
+
+    private FormOutboundIssuerMetadataResponseMessage uninitialized(final CredentialIssuerMetadata metadata,
+            final boolean withResponse) {
         final FormOutboundIssuerMetadataResponseMessage action = new FormOutboundIssuerMetadataResponseMessage();
         action.setMetadataResolver(new StubResolver(metadata, false));
         action.setHttpServletRequestSupplier(new NonnullSupplier<>() {
@@ -107,11 +122,24 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
                 return httpRequest;
             }
         });
-        action.setSignedMetadataLifetime(lifetime);
-        action.initialize();
+        if (withResponse) {
+            action.setHttpServletResponseSupplier(new NonnullSupplier<>() {
+                public HttpServletResponse get() {
+                    return httpResponse;
+                }
+            });
+        }
         return action;
     }
 
+    private void assertDocumentHeaders(final String cacheControl) {
+        Assert.assertEquals(httpResponse.getHeaders("Cache-Control"), List.of(cacheControl));
+        Assert.assertEquals(httpResponse.getHeaders("X-Content-Type-Options"),
+                List.of(FormOutboundMetadataDocumentResponseMessage.NOSNIFF));
+        Assert.assertEquals(httpResponse.getHeaders("Content-Security-Policy"),
+                List.of(FormOutboundMetadataDocumentResponseMessage.CONTENT_SECURITY_POLICY));
+    }
+
     private void configureSigning(final String algorithm, final PrivateKey key) throws Exception {
         final BasicJWKCredential credential = new BasicJWKCredential();
         credential.setPublicKey(signingKey.toPublicKey());
@@ -323,6 +351,57 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
 
         ActionTestingSupport.assertEvent(actionOf(CredentialIssuerMetadata.parse(members())).execute(requestCtx),
                 EventIds.INVALID_SEC_CFG);
+        Assert.assertNull(httpResponse.getHeader("Cache-Control"));
+    }
+
+    @Test
+    public void testUnsignedHeaders() throws Exception {
+        ActionTestingSupport.assertProceedEvent(
+                actionOf(CredentialIssuerMetadata.parse(members())).execute(requestCtx));
+
+        assertDocumentHeaders("max-age=3600");
+    }
+
+    @Test
+    public void testSignedHeaders() throws Exception {
+        httpRequest.addHeader("Accept", "application/jwt");
+        configureSigning(SignatureConstants.ALGO_ID_SIGNATURE_ES_256, signingKey.toPrivateKey());
+
+        ActionTestingSupport.assertProceedEvent(
+                actionOf(CredentialIssuerMetadata.parse(members())).execute(requestCtx));
+
+        assertDocumentHeaders("max-age=3600");
+    }
+
+    @Test
+    public void testCacheControlReplacesPreviousValue() throws Exception {
+        httpResponse.addHeader("Cache-Control", "no-store");
+        final FormOutboundIssuerMetadataResponseMessage action =
+                uninitialized(CredentialIssuerMetadata.parse(members()), true);
+        action.setCacheControl(" max-age=60 ");
+        action.initialize();
+
+        ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+
+        assertDocumentHeaders("max-age=60");
+    }
+
+    @Test
+    public void testEmptyCacheControl() throws Exception {
+        final FormOutboundIssuerMetadataResponseMessage action =
+                uninitialized(CredentialIssuerMetadata.parse(members()), true);
+
+        Assert.assertThrows(ConstraintViolationException.class, () -> action.setCacheControl(" "));
+    }
+
+    @Test
+    public void testNoHttpServletResponse() throws Exception {
+        final FormOutboundIssuerMetadataResponseMessage action =
+                uninitialized(CredentialIssuerMetadata.parse(members()), false);
+        action.initialize();
+
+        ActionTestingSupport.assertEvent(action.execute(requestCtx), IdPEventIds.INVALID_PROFILE_CONFIG);
+        Assert.assertNull(profileRequestCtx.ensureOutboundMessageContext().getMessage());
     }
 
     @Test

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list