[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