[java-idp-oidc] branch main updated: Add SAML metadata test for client_credentials grant.

Scott Cantor cantor.2 at osu.edu
Thu Feb 10 21:42:27 UTC 2022


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

scantor 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=5a05c2f24a21e7e9dcb2968a6a4896463fdf9a59

The following commit(s) were added to refs/heads/main by this push:
     new 5a05c2f2 Add SAML metadata test for client_credentials grant.
5a05c2f2 is described below

commit 5a05c2f24a21e7e9dcb2968a6a4896463fdf9a59
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Feb 10 16:42:24 2022 -0500

    Add SAML metadata test for client_credentials grant.
---
 .../flow/ClientCredentialsTokenFlowTest.java       | 84 +++++++++++++++-------
 .../src/test/resources/conf/attribute-filter.xml   | 10 ++-
 .../src/test/resources/conf/global.xml             |  4 ++
 .../src/test/resources/conf/metadata-providers.xml |  7 +-
 .../src/test/resources/conf/relying-party.xml      | 10 +--
 .../impl/EntityDescriptor-with-oauth2-resource.xml | 27 +++++++
 .../EntityDescriptor-with-oidcmd-clientsecret.xml  | 10 +--
 7 files changed, 110 insertions(+), 42 deletions(-)

diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
index b61b5048..b45a17b9 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
@@ -80,6 +80,8 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     
     private final String resource = "https://rp.example.org";
     
+    private final String resourceSaml = "https://resource.example.org";
+    
     @Autowired
     @Qualifier("shibboleth.StorageService")
     StorageService storageService;
@@ -92,7 +94,6 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     @AfterMethod
     public void tearDown() throws IOException {
         removeMetadata(storageService, clientId);
-        removeMetadata(storageService, clientId + "JWT");
         removeMetadata(storageService, resource);
     }
 
@@ -115,11 +116,12 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     }
 
     @Test
-    public void testInvalidGrantType() throws ParseException, IOException {
-        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
-        setBasicAuth(clientIdSaml, clientSecretSaml);
+    public void testInvalidAudience() throws ParseException, IOException {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource + "/invalid"));
+        storeMetadata(storageService, clientId, clientSecret, null);
+        setBasicAuth(clientId, clientSecret);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.ACCESS_DENIED_CODE);
     }
 
     @Test
@@ -132,7 +134,7 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertNull(response.getTokens().getBearerAccessToken().getScope());
-        verifyClaims(null, response.getTokens().getBearerAccessToken(), new Scope(),
+        verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, new Scope(),
                 Collections.singletonList(resource), "eduPersonScopedAffiliation");
     }
 
@@ -146,39 +148,54 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
-        verifyClaims(null, response.getTokens().getBearerAccessToken(), scope,
+        verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, scope,
+                Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
+    }
+
+    @Test
+    public void testRequestedScopeNoAudienceJWT() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, null));
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        storeMetadata(storageService, resource, null, null);
+        setBasicAuth(clientId, clientSecret);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+        Assert.assertNotNull(response.getTokens().getBearerAccessToken());
+        Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
+        Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
+        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), clientId, scope,
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
 
     @Test
-    public void testRequestedScopeJWTVerifiedAudience() throws Exception {
-        setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
-        storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
+    public void testRequestedScopeJWT() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
+        storeMetadata(storageService, clientId, clientSecret, scope);
         storeMetadata(storageService, resource, null, null);
-        setBasicAuth(clientId + "JWT", clientSecret);
+        setBasicAuth(clientId, clientSecret);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
-        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), scope,
+        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), clientId, scope,
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
 
     @Test
-    public void testRequestedScopeJWTVerifiedAudienceEncrypted() throws Exception {
-        setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
-        storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
+    public void testRequestedScopeJWTEncrypted() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
+        storeMetadata(storageService, clientId, clientSecret, scope);
         storeMetadata(storageService, resource, null, null, JWSAlgorithm.RS256, JWEAlgorithm.RSA_OAEP_256,
                 EncryptionMethod.A128GCM, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, JWSAlgorithm.RS256,
                 rsaPublicKey, (String[]) null);
-        setBasicAuth(clientId + "JWT", clientSecret);
+        setBasicAuth(clientId , clientSecret);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
-        verifyClaims("JWE", response.getTokens().getBearerAccessToken(), scope,
+        verifyClaims("JWE", response.getTokens().getBearerAccessToken(), clientId, scope,
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
 
@@ -197,10 +214,24 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
-        verifyClaims(null, response.getTokens().getBearerAccessToken(), scope,
+        verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, scope,
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
     
+    @Test
+    public void testSaml() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientIdSaml, scope, resourceSaml));
+        setBasicAuth(clientIdSaml, clientSecretSaml);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+        Assert.assertNotNull(response.getTokens().getBearerAccessToken());
+        Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
+        Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
+        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), clientIdSaml, scope,
+                Collections.singletonList(resourceSaml), "email", "eduPersonScopedAffiliation");
+    }
+    
+    
     private AccessTokenClaimsSet unwrapAccessToken(final AccessTokenResponse tokenResponse) {
         final AccessToken accessToken = tokenResponse.getTokens().getAccessToken();
         Assert.assertNotNull(accessToken);
@@ -264,6 +295,7 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     * 
     * @param type token type/format
     * @param token access token
+    * @param cid client ID
     * @param s scope to check for
     * @param audiences audiences to check for
     * @param customClaims custom claim names to check for
@@ -274,8 +306,9 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     * @throws NoSuchAlgorithmException 
     * @throws JOSEException 
     */
-   private void verifyClaims(@Nullable final String type, @Nonnull final AccessToken token, @Nonnull final Scope s,
-           @Nonnull @NonnullElements final Collection<String> audiences, @Nullable final String...customClaims)
+   private void verifyClaims(@Nullable final String type, @Nonnull final AccessToken token, @Nonnull final String cid,
+           @Nonnull final Scope s, @Nonnull @NonnullElements final Collection<String> audiences,
+           @Nullable final String...customClaims)
            throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException,
                JOSEException {
        
@@ -286,12 +319,12 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
            assertEquals(at.getACR(), null);
            assertEquals(at.getAudience(), audiences);
            assertTrue(at.getAuthenticationTime().isBefore(Instant.now()));
-           assertEquals(at.getClientID().getValue(), clientId);
+           assertEquals(at.getClientID().getValue(), cid);
            assertEquals(at.getExp(), at.getIssuedAt().plusSeconds(600));
            assertEquals(at.getIssuer(), "https://op.example.org");
            assertTrue(at.getIssuedAt().isBefore(Instant.now()));
            assertEquals(at.getScope(), s);
-           assertEquals(at.getSubject(), clientId);
+           assertEquals(at.getSubject(), cid);
            if (customClaims != null) {
                for (final String c : customClaims) {
                    // These should be absent in opaque tokens.
@@ -302,17 +335,14 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
        }
 
        final JWTClaimsSet claims;
-       final String clientIdSuffix;
        
        if ("JWE".equals(type)) {
            final EncryptedJWT encrypted = EncryptedJWT.parse(token.getValue());
            final JWEDecrypter decrypter = new RSADecrypter((PrivateKey) rsaPrivateKey);
            encrypted.decrypt(decrypter);
            claims = SignedJWT.parse(encrypted.getPayload().toString()).getJWTClaimsSet();
-           clientIdSuffix = "JWT";
        } else if ("JWT".equals(type)) {
            claims = SignedJWT.parse(token.getValue()).getJWTClaimsSet();
-           clientIdSuffix = type;
        } else {
            throw new RuntimeException("Bad token type");
        }
@@ -321,12 +351,12 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
        assertEquals(claims.getClaim(TokenClaimsSet.KEY_ACR), null);
        assertEquals(claims.getAudience(), audiences);
        assertTrue(claims.getDateClaim(TokenClaimsSet.KEY_AUTH_TIME).toInstant().isBefore(Instant.now()));
-       assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_CLIENTID), clientId + clientIdSuffix);
+       assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_CLIENTID), cid);
        assertEquals(claims.getExpirationTime().toInstant(), claims.getIssueTime().toInstant().plusSeconds(600));
        assertEquals(claims.getIssuer(), "https://op.example.org");
        assertTrue(claims.getIssueTime().toInstant().isBefore(Instant.now()));
        assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_SCOPE), s.toString());
-       assertEquals(claims.getSubject(), clientId + clientIdSuffix);
+       assertEquals(claims.getSubject(), cid);
        if (customClaims != null) {
            for (final String c : customClaims) {
                assertNotNull(claims.getClaim(c));
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/attribute-filter.xml b/idp-oidc-extension-impl/src/test/resources/conf/attribute-filter.xml
index 840230b3..7b3ed91d 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/attribute-filter.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/attribute-filter.xml
@@ -91,8 +91,14 @@
         <PolicyRequirementRule xsi:type="OR">
             <Rule xsi:type="Requester" value="https://sp.example.org" />
             <Rule xsi:type="AND">
-                <Rule xsi:type="Requester" value="mockClientIdJWT" />
-                <Rule xsi:type="ProxiedRequester" value="https://rp.example.org" />
+                <Rule xsi:type="OR">
+                    <Rule xsi:type="Requester" value="mockClientId" />
+                    <Rule xsi:type="Requester" value="mockSamlClientId" />
+                </Rule>
+                <Rule xsi:type="OR">
+                    <Rule xsi:type="ProxiedRequester" value="https://rp.example.org" />
+                    <Rule xsi:type="ProxiedRequester" value="https://resource.example.org" />
+                </Rule>
             </Rule>
             <Rule xsi:type="Requester" value="https://another.example.org/shibboleth" />
         </PolicyRequirementRule>
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
index a6efaae1..80c4d163 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
@@ -20,6 +20,10 @@
     <bean id="exampleMetadata-saml-oidc-clientsecret" class="org.springframework.core.io.ClassPathResource">
         <constructor-arg value="/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oidcmd-clientsecret.xml"/>
     </bean>
+
+    <bean id="exampleMetadata-saml-oauth2-resource" class="org.springframework.core.io.ClassPathResource">
+        <constructor-arg value="/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oauth2-resource.xml"/>
+    </bean>
     
     <util:set id="testbed.MetadataIndexes">
         <bean class="org.opensaml.saml.metadata.resolver.index.impl.SAMLArtifactMetadataIndex" />
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml b/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml
index 4f2e0c02..3b78e27f 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml
@@ -17,7 +17,12 @@
         <!-- Metadata Configuration -->
         <!-- ========================================== -->
 
-    <MetadataProvider id="SP123MD" xsi:type="ResourceBackedMetadataProvider" maxRefreshDelay="PT5M" indexesRef="testbed.MetadataIndexes"
+    <MetadataProvider id="SP1MD" xsi:type="ResourceBackedMetadataProvider"
+        maxRefreshDelay="PT5M" indexesRef="testbed.MetadataIndexes"
         resourceRef="exampleMetadata-saml-oidc-clientsecret" />
 
+    <MetadataProvider id="SP2MD" xsi:type="ResourceBackedMetadataProvider"
+        maxRefreshDelay="PT5M" indexesRef="testbed.MetadataIndexes"
+        resourceRef="exampleMetadata-saml-oauth2-resource" />
+
 </MetadataProvider>
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 3739d367..7502aa34 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
@@ -79,14 +79,8 @@
                  </list>
             </property>
         </bean>
-        <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdJWT">
-            <property name="profileConfigurations">
-                 <list>
-                     <bean parent="OIDC.Token.MDDriven"/>
-                 </list>
-            </property>
-        </bean>
-        <bean parent="RelyingPartyByName" c:relyingPartyIds="https://rp.example.org">
+        <bean parent="RelyingPartyByName"
+                c:relyingPartyIds="#{{'https://rp.example.org', 'https://resource.example.org'}}">
             <property name="profileConfigurations">
                  <list>
                      <bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" />
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oauth2-resource.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oauth2-resource.xml
new file mode 100644
index 00000000..0d23d5c1
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oauth2-resource.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://resource.example.org">
+    <md:Extensions xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
+        <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+            <saml:Attribute Name="http://shibboleth.net/ns/attributes/releaseAllValues"
+                NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>eduPersonScopedAffiliation</saml:AttributeValue>
+            </saml:Attribute>
+        </mdattr:EntityAttributes>
+    </md:Extensions>
+    <md:SPSSODescriptor xmlns:oidcmd="urn:mace:shibboleth:metadata:oidc:1.0"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            protocolSupportEnumeration="http://openid.net/specs/openid-connect-core-1_0.html">
+        <md:Extensions>
+            <mdui:UIInfo xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
+                <mdui:DisplayName xml:lang="en">OAuth 2.0 Sample</mdui:DisplayName>
+            </mdui:UIInfo>
+            <oidcmd:OAuthRPExtensions
+                application_type="web"
+                token_endpoint_auth_method="client_secret_basic" />
+        </md:Extensions>
+        <md:AssertionConsumerService
+                Binding="dummy"
+                Location="dummy"
+                index="1"/>
+    </md:SPSSODescriptor>
+</md:EntityDescriptor>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oidcmd-clientsecret.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oidcmd-clientsecret.xml
index a02c5508..cd68e8fa 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oidcmd-clientsecret.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/EntityDescriptor-with-oidcmd-clientsecret.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="mockSamlClientId">
+<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" entityID="mockSamlClientId">
     <md:Extensions xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
-        <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <mdattr:EntityAttributes>
             <saml:Attribute Name="http://shibboleth.net/ns/profiles/oidc/token/alwaysIncludedAttributes"
                 NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
                 <saml:AttributeValue>mail</saml:AttributeValue>
@@ -20,11 +20,13 @@
                 <mdui:DisplayName xml:lang="en">OIDC Sample</mdui:DisplayName>
             </mdui:UIInfo>
             <oidcmd:OAuthRPExtensions
-                grant_types="authorization_code"
+                grant_types="authorization_code client_credentials"
                 response_types="code"
                 application_type="web"
                 token_endpoint_auth_method="client_secret_basic"
-                scopes="openid profile email" />
+                scopes="openid profile email">
+                <saml:Audience>https://resource.example.org</saml:Audience>    
+            </oidcmd:OAuthRPExtensions>
         </md:Extensions>
         <md:AssertionConsumerService
                 Binding="https://tools.ietf.org/html/rfc6749#section-3.1.2"

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


More information about the commits mailing list