[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