[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant
Scott Cantor
cantor.2 at osu.edu
Thu Jan 20 15:54:33 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=1604839ed8f026c4344a00249ef7b8052d7df6cf
The following commit(s) were added to refs/heads/main by this push:
new 1604839e JOIDC-11 - Support for client_credentials grant
1604839e is described below
commit 1604839ed8f026c4344a00249ef7b8052d7df6cf
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Jan 20 10:54:30 2022 -0500
JOIDC-11 - Support for client_credentials grant
https://shibboleth.atlassian.net/browse/JOIDC-11
Exclude custom claims from opaque tokens.
Add proper typ header to JWTs.
---
.../op/oauth2/profile/impl/BuildAccessToken.java | 2 +-
.../op/profile/impl/AbstractSignJWTAction.java | 28 +++++++++-
.../idp/flows/oidc/token/token-beans.xml | 3 +-
.../flow/ClientCredentialsTokenFlowTest.java | 59 ++++++++--------------
.../src/test/resources/conf/attribute-resolver.xml | 15 ++----
5 files changed, 55 insertions(+), 52 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
index 1a54f307..1b5e2d89 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
@@ -402,7 +402,7 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
.setScope(scope)
.setAudience(audience);
- if (responseCtx.getAccessTokenClaimSet() != null) {
+ if (jwtTokenType && responseCtx.getAccessTokenClaimSet() != null) {
builder.setCustomClaims(responseCtx.getAccessTokenClaimSet().toJSONObject());
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractSignJWTAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractSignJWTAction.java
index fc3bce2d..4bb16d9e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractSignJWTAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractSignJWTAction.java
@@ -29,6 +29,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
@@ -40,6 +41,9 @@ import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
* Abstract action for signing JWT. The extending class is expected to set claims set by implementing
@@ -51,9 +55,25 @@ public abstract class AbstractSignJWTAction extends AbstractOIDCSigningResponseA
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(AbstractSignJWTAction.class);
+ /** "typ" header to insert while signing. */
+ @Nullable @NotEmpty private String typeHeader;
+
/** resolved credential. */
@Nullable private Credential credential;
+ /**
+ * Sets the value to be inserted as a "typ" header for the JWS.
+ *
+ * @param type header value
+ *
+ * @since 3.1.0
+ */
+ public void setTypeHeader(@Nullable @NotEmpty final String type) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ typeHeader = StringSupport.trimOrNull(type);
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -131,8 +151,12 @@ public abstract class AbstractSignJWTAction extends AbstractOIDCSigningResponseA
try {
final Algorithm jwsAlgorithm = resolveAlgorithm();
final JWSSigner signer = getSigner(jwsAlgorithm);
- jwt = new SignedJWT(new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
- .keyID(CredentialConversionUtil.resolveKid(credential)).build(), jwtClaimSet);
+ final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
+ .keyID(CredentialConversionUtil.resolveKid(credential));
+ if (typeHeader != null) {
+ headerBuilder.type(new JOSEObjectType(typeHeader));
+ }
+ jwt = new SignedJWT(headerBuilder.build(), jwtClaimSet);
jwt.sign(signer);
} catch (final JOSEException e) {
log.error("{} Error signing claim set: {}", getLogPrefix(), e.getMessage());
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 193c182b..ea18db2e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -279,7 +279,8 @@
p:audienceAttribute="#{'%{idp.oauth.accessToken.audienceAttribute:audience}'.trim()}" />
<bean id="SignAccessToken"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SignAccessToken" scope="prototype">
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SignAccessToken" scope="prototype"
+ p:typeHeader="at+jwt">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookup.SecurityParameters"
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 c12233cb..5a938be8 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
@@ -24,12 +24,10 @@ import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
-import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
@@ -55,21 +53,11 @@ import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
-import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
-import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
-import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
import com.nimbusds.oauth2.sdk.token.AccessToken;
-import com.nimbusds.oauth2.sdk.token.RefreshToken;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-import net.minidev.json.JSONObject;
-import net.shibboleth.idp.plugin.oidc.op.messaging.context.AccessTokenContext;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantTest;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.collection.Pair;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -126,7 +114,7 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
}
@Test
- public void testNoScopes() throws Exception {
+ public void testNoScope() throws Exception {
setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
storeMetadata(storageService, clientId, clientSecret, null);
setBasicAuth(clientId, clientSecret);
@@ -140,7 +128,7 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
}
@Test
- public void testNoScopesJWT() throws Exception {
+ public void testNoScopeJWT() throws Exception {
setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
storeMetadata(storageService, clientId + "JWT", clientSecret, null);
setBasicAuth(clientId + "JWT", clientSecret);
@@ -152,39 +140,36 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
verifyClaims("JWT", response.getTokens().getBearerAccessToken(), new Scope(),
Collections.singletonList(resource));
}
-
- protected void initializeGrantAndRequest(final String clientId, final Map<String, String> requestParameters)
- throws IOException {
- setHttpFormRequest("POST", requestParameters);
+
+ @Test
+ public void testRequestedScope() throws Exception {
+ setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
storeMetadata(storageService, clientId, clientSecret, scope);
setBasicAuth(clientId, clientSecret);
- }
-
- /*
- @Test
- public void testValidGrant() throws Exception {
- initializeGrantAndRequest(clientId, createRequestParameters(clientId, scope, resource));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
- Assert.assertNotNull(response.getTokens().getAccessToken());
+ 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,
+ Collections.singletonList(resource));
}
@Test
- public void testValidGrantWithRequestedScope() throws Exception {
- final Map<String,String> params = createRequestParameters(clientId, scope, resource);
- params.put("scope", "openid profile");
- initializeGrantAndRequest(clientId, params);
+ public void testRequestedScopeJWT() throws Exception {
+ setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
+ storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
+ setBasicAuth(clientId + "JWT", clientSecret);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
- Assert.assertNotNull(response.getTokens().getAccessToken());
-
- final ValidateGrantTest test = new ValidateGrantTest();
- final AccessTokenClaimsSet token =
- AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), test.getDataSealer());
- Assert.assertTrue(token.getScope().contains("openid"));
- Assert.assertTrue(token.getScope().contains("profile"));
- Assert.assertFalse(token.getScope().contains("email"));
+ 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,
+ Collections.singletonList(resource));
}
+
+ /*
@Test
public void testValidSecretJWT() throws Exception {
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/attribute-resolver.xml b/idp-oidc-extension-impl/src/test/resources/conf/attribute-resolver.xml
index 131ab679..d93be7c9 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/attribute-resolver.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/attribute-resolver.xml
@@ -34,8 +34,6 @@
-->
<AttributeDefinition id="eduPersonPrincipalName" xsi:type="Scoped" scope="%{idp.scope}">
<InputAttributeDefinition ref="uid" />
- <AttributeEncoder xsi:type="SAML1ScopedString" name="urn:mace:dir:attribute-def:eduPersonPrincipalName" encodeType="false" />
- <AttributeEncoder xsi:type="SAML2ScopedString" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" friendlyName="eduPersonPrincipalName" encodeType="false" />
<AttributeEncoder xsi:type="oidc:OIDCScopedString" name="eppn" />
</AttributeDefinition>
@@ -44,10 +42,7 @@
representing a local username, but you should generally *never*
expose uid to federated services, as it is rarely globally unique.
-->
- <AttributeDefinition id="uid" xsi:type="PrincipalName">
- <AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:uid" encodeType="false" />
- <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" encodeType="false" />
- </AttributeDefinition>
+ <AttributeDefinition id="uid" xsi:type="PrincipalName" />
<!--
In the rest of the world, the email address is the standard identifier,
@@ -56,8 +51,6 @@
-->
<AttributeDefinition id="mail" xsi:type="Template">
<InputAttributeDefinition ref="uid" />
- <AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:mail" encodeType="false" />
- <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.3" friendlyName="mail" encodeType="false" />
<AttributeEncoder xsi:type="oidc:OIDCString" name="email" />
<Template>
<![CDATA[
@@ -71,13 +64,13 @@
-->
<AttributeDefinition id="eduPersonScopedAffiliation" xsi:type="Scoped" scope="%{idp.scope}">
<InputDataConnector ref="staticAttributes" attributeNames="affiliation" />
- <AttributeEncoder xsi:type="SAML1ScopedString" name="urn:mace:dir:attribute-def:eduPersonScopedAffiliation" encodeType="false" />
- <AttributeEncoder xsi:type="SAML2ScopedString" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" friendlyName="eduPersonScopedAffiliation" encodeType="false" />
</AttributeDefinition>
<!-- Subject Identifier is a attribute that must always be resolved.
There has to be exactly one resolved and filtered attribute that would be encoded as 'sub'.
- This example attribute (the data connector actually ) will generate public or pairwise 'sub' depending on client registration data. -->
+ This example attribute (the data connector actually ) will generate public or pairwise 'sub'
+ depending on client registration data.
+ -->
<AttributeDefinition id="subject" xsi:type="Simple" activationConditionRef="shibboleth.oidc.Conditions.SubjectRequired">
<InputDataConnector ref="computedSubjectId" attributeNames="subjectId"/>
<AttributeEncoder xsi:type="oidc:OIDCString" name="sub" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list