[java-idp-oidc] branch main updated: JOIDC-99 - Support OAuth 2.0 Authorization Server Issuer Identification as per RFC9207

Henri Mikkonen henri.mikkonen at iki.fi
Fri May 20 13:28:01 UTC 2022


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=ab3f4406c6e474fe63b285ec2804fedc5ec8c1e4

The following commit(s) were added to refs/heads/main by this push:
     new ab3f4406 JOIDC-99 - Support OAuth 2.0 Authorization Server Issuer Identification as per RFC9207
ab3f4406 is described below

commit ab3f4406c6e474fe63b285ec2804fedc5ec8c1e4
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 20 16:26:26 2022 +0300

    JOIDC-99 - Support OAuth 2.0 Authorization Server Issuer Identification as per RFC9207
    
    https://shibboleth.atlassian.net/browse/JOIDC-99
    
    Wired the new profile configuration property into use when building success or
    error authentication response.
---
 .../BuildAuthenticationErrorResponseFromEvent.java |  81 ++++++++++-
 .../FormOutboundAuthenticationResponseMessage.java |  74 +++++++++-
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 150 ++++++++++++++++++++-
 .../src/test/resources/conf/relying-party.xml      |   7 +
 4 files changed, 307 insertions(+), 5 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/BuildAuthenticationErrorResponseFromEvent.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/BuildAuthenticationErrorResponseFromEvent.java
index 14ead389..240956d6 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/BuildAuthenticationErrorResponseFromEvent.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/BuildAuthenticationErrorResponseFromEvent.java
@@ -18,18 +18,33 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
 import java.net.URI;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.EventContext;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.nimbusds.oauth2.sdk.ErrorObject;
 import com.nimbusds.oauth2.sdk.ResponseMode;
 import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseModeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseTypeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestStateLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ValidatedRedirectURILookupFunction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.oidc.profile.config.logic.IncludeIssuerInAuthenticationResponsePredicate;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
  * This action reads an event from the configured {@link EventContext} lookup strategy, constructs an OIDC
@@ -38,6 +53,70 @@ import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ValidatedRedir
 public class BuildAuthenticationErrorResponseFromEvent
         extends AbstractBuildErrorResponseFromEvent<AuthenticationErrorResponse> {
 
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(BuildAuthenticationErrorResponseFromEvent.class);
+
+    /** Strategy used to obtain the response issuer value. */
+    @Nonnull private Function<ProfileRequestContext, String> issuerLookupStrategy;
+
+    /** Predicate to signal whether or not to include iss-parameter to the response. */
+    @Nonnull private Predicate<ProfileRequestContext> includeIssuerInResponsePredicate;
+
+    /** Issuer value to included in the response message, if configured to be included. */
+    private Issuer issuer;
+
+    /**
+     * Constructor.
+     */
+    public BuildAuthenticationErrorResponseFromEvent() {
+        issuerLookupStrategy = new ResponderIdLookupFunction();
+        includeIssuerInResponsePredicate = new IncludeIssuerInAuthenticationResponsePredicate();
+    }
+
+    /**
+     * Set the strategy used to locate the issuer value to use.
+     * 
+     * @param strategy what to set
+     */
+    public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the predicate to signal whether or not to include iss-parameter to the response.
+     *
+     * @param predicate what to set
+     */
+    public void setIncludeIssuerInResponsePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        includeIssuerInResponsePredicate = Constraint.isNotNull(predicate,
+                "Include issuer in response predicate cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        if (includeIssuerInResponsePredicate.test(profileRequestContext)) {
+            final String issValue = issuerLookupStrategy.apply(profileRequestContext);
+            if (StringSupport.trimOrNull(issValue) == null) {
+                log.error("{} Could not resolve value for issuer even though it's required", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+                return false;
+            }
+            issuer = new Issuer(issValue);
+        } else {
+            issuer = null;
+        }
+        return true;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected AuthenticationErrorResponse buildErrorResponse(final ErrorObject error,
@@ -58,7 +137,7 @@ public class BuildAuthenticationErrorResponseFromEvent
             responseMode = defaultResponseMode;
         }
         return new AuthenticationErrorResponse(redirectURI, error,
-                new DefaultRequestStateLookupFunction().apply(profileRequestContext), responseMode);
+                new DefaultRequestStateLookupFunction().apply(profileRequestContext), issuer, responseMode);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/FormOutboundAuthenticationResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/FormOutboundAuthenticationResponseMessage.java
index 28029451..cd425f9a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/FormOutboundAuthenticationResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/FormOutboundAuthenticationResponseMessage.java
@@ -17,6 +17,9 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
+import java.util.function.Function;
+import java.util.function.Predicate;
+
 import javax.annotation.Nonnull;
 
 import org.opensaml.profile.action.ActionSupport;
@@ -24,11 +27,19 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseModeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestStateLookupFunction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.oidc.profile.config.logic.IncludeIssuerInAuthenticationResponsePredicate;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
  * Action that forms outbound message based on request and response context. Formed message is set to
@@ -39,6 +50,67 @@ public class FormOutboundAuthenticationResponseMessage extends AbstractOIDCAuthe
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(FormOutboundAuthenticationResponseMessage.class);
 
+    /** Strategy used to obtain the response issuer value. */
+    @Nonnull private Function<ProfileRequestContext, String> issuerLookupStrategy;
+
+    /** Predicate to signal whether or not to include iss-parameter to the response. */
+    @Nonnull private Predicate<ProfileRequestContext> includeIssuerInResponsePredicate;
+
+    /** Issuer value to included in the response message, if configured to be included. */
+    private Issuer issuer;
+
+    /**
+     * Constructor.
+     */
+    public FormOutboundAuthenticationResponseMessage() {
+        issuerLookupStrategy = new ResponderIdLookupFunction();
+        includeIssuerInResponsePredicate = new IncludeIssuerInAuthenticationResponsePredicate();
+    }
+
+    /**
+     * Set the strategy used to locate the issuer value to use.
+     * 
+     * @param strategy what to set
+     */
+    public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the predicate to signal whether or not to include iss-parameter to the response.
+     *
+     * @param predicate what to set
+     */
+    public void setIncludeIssuerInResponsePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        includeIssuerInResponsePredicate = Constraint.isNotNull(predicate,
+                "Include issuer in response predicate cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        if (includeIssuerInResponsePredicate.test(profileRequestContext)) {
+            final String issValue = issuerLookupStrategy.apply(profileRequestContext);
+            if (StringSupport.trimOrNull(issValue) == null) {
+                log.error("{} Could not resolve value for issuer even though it's required", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+                return false;
+            }
+            issuer = new Issuer(issValue);
+        } else {
+            issuer = null;
+        }
+        return true;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -51,7 +123,7 @@ public class FormOutboundAuthenticationResponseMessage extends AbstractOIDCAuthe
         final AuthenticationResponse resp = new AuthenticationSuccessResponse(getOidcResponseContext().getRedirectURI(),
                 getOidcResponseContext().getAuthorizationCode(), getOidcResponseContext().getProcessedToken(),
                 getOidcResponseContext().getAccessToken(),
-                new DefaultRequestStateLookupFunction().apply(profileRequestContext), null,
+                new DefaultRequestStateLookupFunction().apply(profileRequestContext), null, issuer,
                 new DefaultRequestResponseModeLookupFunction().apply(profileRequestContext));
         profileRequestContext.getOutboundMessageContext().setMessage(resp);
     }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 13fb2f9d..89abe9c7 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -39,7 +39,10 @@ import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ErrorResponse;
+import com.nimbusds.oauth2.sdk.Response;
 import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
 import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
@@ -62,6 +65,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     String issuer = "https://op.example.org";
     String redirectUri = "https://example.org/cb";
     String clientId = "mockClientId";
+    String clientIdIssInResponse = "mockClientIdIssInResponse";
     String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
     Scope scope = Scope.parse("openid profile email");
     
@@ -94,6 +98,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -112,8 +117,29 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
-    
+
+    @Test
+    public void testWithAuthorizationCodeFlowIssInResponse() throws IOException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdIssInResponse&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri);
+        storeMetadata(storageService, clientIdIssInResponse, clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(successResponse.getIssuer());
+        Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
+    }
+
     @Test
     public void testWithAuthorizationCodeFlowNoOpenid() throws IOException, SessionException {
         request.setMethod("GET");
@@ -156,6 +182,27 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
+    }
+
+    @Test
+    public void testWithImplicitFlowIssInResponse() throws IOException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdIssInResponse&response_type=id_token&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&nonce=idhas3h23hi13h1o2i32");
+        storeMetadata(storageService, clientIdIssInResponse, clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(successResponse.getIssuer());
+        Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
     }
 
     @Test
@@ -174,6 +221,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -197,6 +245,32 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
                 AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
         Assert.assertEquals(token.getAudience(), Collections.singletonList(issuer));
         Assert.assertNull(token.getClaimsSet().getStringClaim("eduPersonScopedAffiliation"));
+        Assert.assertNull(successResponse.getIssuer());
+    }
+
+    @Test
+    public void testWithImplicitTokenFlowIssInResponse() throws IOException, SessionException, DataSealerException, ParseException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdIssInResponse&response_type=id_token+token&scope=openid%20profile"
+                + "&redirect_uri="+ redirectUri + "&nonce=idhas3h23hi13h1o2i32");
+        storeMetadata(storageService, clientIdIssInResponse, clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNotNull(successResponse.getAccessToken());
+        Assert.assertNull(successResponse.getAuthorizationCode());
+        
+        final AccessTokenClaimsSet token =
+                AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
+        Assert.assertEquals(token.getAudience(), Collections.singletonList(issuer));
+        Assert.assertNull(token.getClaimsSet().getStringClaim("eduPersonScopedAffiliation"));
+        Assert.assertNotNull(successResponse.getIssuer());
+        Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
     }
 
     @Test
@@ -215,6 +289,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNotNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final JWTClaimsSet token =  SignedJWT.parse(successResponse.getAccessToken().getValue()).getJWTClaimsSet();
         Assert.assertEquals(token.getAudience(), List.of(resource, issuer));
@@ -263,6 +338,27 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
+    }
+
+    @Test
+    public void testWithHybridIdTokenFlowIssInResponse() throws IOException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdIssInResponse&response_type=code+id_token&scope=openid%20profile"
+                + "&redirect_uri=" + redirectUri + "&nonce=idhas3h23hi13h1o2i32");
+        storeMetadata(storageService, clientIdIssInResponse, clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(successResponse.getIssuer());
+        Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
     }
 
     @Test
@@ -281,6 +377,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -318,6 +415,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
                 AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
         Assert.assertEquals(token.getAudience(), Collections.singletonList(issuer));
         Assert.assertNull(token.getClaimsSet().getStringClaim("eduPersonScopedAffiliation"));
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -337,6 +435,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNotNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final JWTClaimsSet token =  SignedJWT.parse(successResponse.getAccessToken().getValue()).getJWTClaimsSet();
         Assert.assertEquals(token.getAudience(), List.of(resource, issuer));
@@ -359,6 +458,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNotNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final AccessTokenClaimsSet token =
                 AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
@@ -382,6 +482,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getIDToken());
         Assert.assertNotNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final JWTClaimsSet token =  SignedJWT.parse(successResponse.getAccessToken().getValue()).getJWTClaimsSet();
         Assert.assertEquals(token.getAudience(), List.of(resource, issuer));
@@ -525,8 +626,26 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, "invalid_request");
         assertErrorDescriptionContains(result, "InvalidSubject");
+        assertErrorResponseWithNoIssuer(result);
     }
-    
+
+    @Test
+    public void testWithAuthorizationCodeFlowNoScopesIssInResponse() throws IOException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdIssInResponse&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri);
+        storeMetadata(storageService, clientIdIssInResponse, clientSecret, null, redirectUri);
+
+        initializeThreadLocals();
+
+        // TODO: Speculation this should fail more explicitly if openid scope isn't valid.
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_request");
+        assertErrorDescriptionContains(result, "InvalidSubject");
+        assertErrorResponseWithIssuer(result);
+    }
+
     @Test
     public void testWithAuthorizationCodeFlowWithIDTokenClaims() throws IOException, SessionException, DataSealerException, ParseException {
         request.setMethod("GET");
@@ -544,7 +663,8 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
-        
+        Assert.assertNull(successResponse.getIssuer());
+
         final AuthorizeCodeClaimsSet code = 
                 AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
         Assert.assertNotNull(code.getClaimsRequest());
@@ -573,6 +693,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
                 AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
@@ -601,6 +722,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
     
     @Test
@@ -651,6 +773,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -698,6 +821,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
                 AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
@@ -779,6 +903,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
     }
 
     @Test
@@ -825,6 +950,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
                 AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
@@ -858,12 +984,30 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals(result.getOutcome().getId(), "ErrorView");        
     }
 
+    protected void assertErrorResponseWithNoIssuer(final FlowExecutionResult result) {
+        final Response response = parseResponse(result);
+        Assert.assertFalse(response.indicatesSuccess());
+        Assert.assertTrue(response instanceof AuthenticationErrorResponse);
+        final AuthenticationErrorResponse errorResponse = (AuthenticationErrorResponse) response;
+        Assert.assertNull(errorResponse.getIssuer());
+    }
+
+    protected void assertErrorResponseWithIssuer(final FlowExecutionResult result) {
+        final Response response = parseResponse(result);
+        Assert.assertFalse(response.indicatesSuccess());
+        Assert.assertTrue(response instanceof AuthenticationErrorResponse);
+        final AuthenticationErrorResponse errorResponse = (AuthenticationErrorResponse) response;
+        Assert.assertNotNull(errorResponse.getIssuer());
+        Assert.assertEquals(errorResponse.getIssuer().getValue(), issuer);
+    }
+
     @AfterMethod
     public void removeMetadata() throws IOException {
         removeMetadata(storageService, clientId);
         removeMetadata(storageService, "mockClientIdPKCEPlainUnforced");
         removeMetadata(storageService, "mockClientIdPKCEPlain");
         removeMetadata(storageService, "mockClientIdPKCES256");
+        removeMetadata(storageService, clientIdIssInResponse);
     }
 
 }
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index 5339dd47..5f040a41 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -58,6 +58,13 @@
     </bean>
 
     <util:list id="shibboleth.RelyingPartyOverrides">
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdIssInResponse">
+            <property name="profileConfigurations">
+                 <list>
+                     <bean parent="OIDC.SSO.MDDriven" p:includeIssuerInResponse="true"/>
+                 </list>
+            </property>
+        </bean>
         <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdNoRefreshTokensInSSOProfile">
             <property name="profileConfigurations">
                  <list>

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


More information about the commits mailing list