[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant

Scott Cantor cantor.2 at osu.edu
Mon Feb 14 20:30:15 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=5358986a7ac747be4905719f8d5a1a03fbd04f45

The following commit(s) were added to refs/heads/main by this push:
     new 5358986a JOIDC-11 - Support for client_credentials grant
5358986a is described below

commit 5358986a7ac747be4905719f8d5a1a03fbd04f45
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Feb 14 15:30:12 2022 -0500

    JOIDC-11 - Support for client_credentials grant
    
    https://shibboleth.atlassian.net/browse/JOIDC-11
    
    Fixes and tests for unverified client support.
---
 .../AbstractOIDCAuthenticationResponseAction.java  | 15 +++----
 .../profile/impl/AbstractOIDCResponseAction.java   |  9 +---
 .../impl/SetRequestObjectToResponseContext.java    |  4 +-
 .../op/profile/impl/ValidateRequestObject.java     | 24 +++++++----
 .../oidc/op/profile/impl/ValidateResponseType.java | 10 +++--
 .../authn/OAuth2Client/OAuth2Client-beans.xml      | 13 ++++--
 .../flow/ClientCredentialsTokenFlowTest.java       | 48 ++++++++++++++++++++++
 .../conf/authn/oauth2client-authn-config.xml       | 26 ++++++++++++
 .../src/test/resources/conf/oidc.properties        |  3 +-
 .../src/test/resources/conf/relying-party.xml      |  1 +
 .../src/test/resources/credentials/htpasswd.txt    |  1 +
 11 files changed, 118 insertions(+), 36 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCAuthenticationResponseAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCAuthenticationResponseAction.java
index 33703ce6..4909be86 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCAuthenticationResponseAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCAuthenticationResponseAction.java
@@ -83,19 +83,16 @@ public abstract class AbstractOIDCAuthenticationResponseAction extends AbstractO
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
-        oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class, false);
+        
+        oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class);
         if (oidcResponseContext == null) {
-            log.error("{} No oidc response context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return false;
-        }
-        oidcMetadataContext =
-                profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class, false);
-        if (oidcMetadataContext == null) {
-            log.error("{} No metadata found for relying party", getLogPrefix());
+            log.error("{} No OIDC response context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
+        
+        oidcMetadataContext = profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class);
+        
         return true;
     }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
index 8ef562a8..498a9e5e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
@@ -86,13 +86,8 @@ public abstract class AbstractOIDCResponseAction extends AbstractProfileAction {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
-        oidcMetadataContext =
-                profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class);
-        if (oidcMetadataContext == null) {
-            log.error("{} No metadata found for relying party", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return false;
-        }
+        oidcMetadataContext = profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class);
+        
         return true;
     }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRequestObjectToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRequestObjectToResponseContext.java
index 2364de01..69433df5 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRequestObjectToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRequestObjectToResponseContext.java
@@ -41,7 +41,7 @@ import com.nimbusds.jwt.JWTParser;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -137,7 +137,7 @@ public class SetRequestObjectToResponseContext extends AbstractOIDCAuthenticatio
         
         // Request URI must be found in metadata.
         boolean authorized = false;
-        if (getMetadataContext().getClientInformation() != null) {
+        if (getMetadataContext() != null && getMetadataContext().getClientInformation() != null) {
             final OIDCClientMetadata metadata = getMetadataContext().getClientInformation().getOIDCMetadata();
             if (metadata != null) {
                 final Set<URI> allowedURIs = metadata.getRequestObjectURIs();
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
index fe2fde82..71a0b258 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
@@ -37,9 +37,9 @@ import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ResponseType;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 
-import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
 import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
 import net.shibboleth.oidc.security.impl.OIDCSignatureValidationParameters;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -144,16 +144,22 @@ public class ValidateRequestObject extends AbstractOIDCAuthenticationResponseAct
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
         // We let "none" to be used only if nothing else has been registered.
-        if (requestObject instanceof PlainJWT
-                && getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg() != null
-                && !"none".equals(getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg()
-                        .getName())) {
-            log.error("{} Request object is not signed evethough registered alg is {}", getLogPrefix(),
-                    getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg().getName());
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
-            return;
+        if (requestObject instanceof PlainJWT) {
+            if (getMetadataContext() == null) {
+                log.error("{} Request object unsigned, no client metadata", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
+                return;
+            } else if (getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg() != null
+                    && !"none".equals(getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg().getName())) {
+                log.error("{} Request object is not signed, registered alg is {}", getLogPrefix(),
+                        getMetadataContext().getClientInformation().getOIDCMetadata().getRequestObjectJWSAlg().getName());
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
+                return;
+            }
         }
+        
         // Signature of signed request object must be verified
         if (!(requestObject instanceof PlainJWT)) {
             // Verify req object is signed with correct algorithm
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateResponseType.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateResponseType.java
index 09965ad3..e3055390 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateResponseType.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateResponseType.java
@@ -26,8 +26,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import com.nimbusds.oauth2.sdk.ResponseType;
 
-import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseTypeLookupFunction;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 
 /**
  * An action that validates the requested response_type is registered to the requesting RP.
@@ -40,12 +40,14 @@ public class ValidateResponseType extends AbstractOIDCAuthenticationResponseActi
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final Set<ResponseType> registeredTypes =
-                getMetadataContext().getClientInformation().getMetadata().getResponseTypes();
+        final Set<ResponseType> registeredTypes = getMetadataContext() != null
+                    ? getMetadataContext().getClientInformation().getMetadata().getResponseTypes()
+                            : null;
         final ResponseType requestedType = new DefaultRequestResponseTypeLookupFunction().apply(profileRequestContext);
         if (registeredTypes == null || registeredTypes.isEmpty() || !registeredTypes.contains(requestedType)) {
             log.warn("{} The response type {} is not registered for this RP", getLogPrefix(), requestedType);
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_RESPONSE_TYPE);
         }
     }
-}
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
index fd0cdfcf..247cc10f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
@@ -57,13 +57,18 @@
     <!-- Default validators equivalent to previous versions. -->
     
     <util:list id="DefaultOAuth2ClientValidators">
-        <bean class="net.shibboleth.idp.plugin.oidc.op.authn.impl.OIDCClientInfoCredentialValidator"
-            p:id="oauth2-clientinfo" />
-        <bean class="net.shibboleth.idp.plugin.oidc.op.authn.impl.JWTCredentialValidator"
-            p:id="oauth2-jwt" />
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
     </util:list>
         
     <!-- Validator parent beans -->
+
+    <bean id="shibboleth.OIDCClientInfoValidator"
+        class="net.shibboleth.idp.plugin.oidc.op.authn.impl.OIDCClientInfoCredentialValidator"
+        p:id="oauth2-clientinfo" />
+        
+    <bean id="shibboleth.JWTValidator" class="net.shibboleth.idp.plugin.oidc.op.authn.impl.JWTCredentialValidator"
+        p:id="oauth2-jwt" />
     
     <bean id="shibboleth.JAASValidator"
         class="net.shibboleth.idp.authn.impl.JAASCredentialValidator" abstract="true"
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 b45a17b9..04fff47c 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
@@ -137,6 +137,27 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
         verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, new Scope(),
                 Collections.singletonList(resource), "eduPersonScopedAffiliation");
     }
+    
+    @Test
+    public void testNoScopeUnverifiedClient() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
+        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.assertNull(response.getTokens().getBearerAccessToken().getScope());
+        verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, new Scope(),
+                Collections.singletonList(resource), "eduPersonScopedAffiliation");
+    }
+
+    @Test
+    public void testNoScopeUnverifiedClientBadAudience() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource + "/invalid"));
+        setBasicAuth(clientId, clientSecret);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.ACCESS_DENIED_CODE);
+    }
 
     @Test
     public void testRequestedScope() throws Exception {
@@ -152,6 +173,19 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
 
+    @Test
+    public void testRequestedScopeUnverifiedClient() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
+        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.assertNull(response.getTokens().getBearerAccessToken().getScope());
+        verifyClaims(null, response.getTokens().getBearerAccessToken(), clientId, new Scope(),
+                Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
+    }
+
     @Test
     public void testRequestedScopeNoAudienceJWT() throws Exception {
         setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, null));
@@ -182,6 +216,20 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
 
+    @Test
+    public void testRequestedScopeJWTUnverifiedClient() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
+        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.assertNull(response.getTokens().getBearerAccessToken().getScope());
+        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), clientId, new Scope(),
+                Collections.singletonList(resource), "eduPersonScopedAffiliation");
+    }
+    
     @Test
     public void testRequestedScopeJWTEncrypted() throws Exception {
         setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml b/idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 00000000..7a2f7316
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+                           
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+    
+    <!-- Ordered list of CredentialValidators to apply to a request. -->
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <bean parent="shibboleth.HTPasswdValidator">
+            <property name="resource">
+                <bean class="org.springframework.core.io.ClassPathResource" c:path="/credentials/htpasswd.txt" />
+            </property>
+        </bean>
+    </util:list>
+    
+</beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
index f7bc0e70..f45d1415 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
@@ -8,4 +8,5 @@ idp.oidc.discovery.template = src/test/resources/conf/openid-configuration.json
 
 idp.oidc.dynreg.defaultMetadataPolicyFile = src/test/resources/conf/metadata-policy1.json
 
-idp.oauth2.grantTypes = authorization_code,refresh_token,client_credentials
\ No newline at end of file
+idp.oauth2.grantTypes = authorization_code,refresh_token,client_credentials
+idp.oauth2.defaultAllowedAudience = https://rp.example.org
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 7502aa34..35a78db0 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
@@ -26,6 +26,7 @@
                 <ref bean="OIDC.Keyset" />
                 <ref bean="OIDC.Registration" />
                 <ref bean="OIDC.Configuration" />
+                <ref bean="OIDC.Token" />
                 <ref bean="OAUTH2.TokenAudience" /> 
             </list>
         </property>
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/htpasswd.txt b/idp-oidc-extension-impl/src/test/resources/credentials/htpasswd.txt
new file mode 100644
index 00000000..5e845616
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/htpasswd.txt
@@ -0,0 +1 @@
+mockClientId:$apr1$KzVBs.G.$hyoWClhnINTCZlS9rDcAx0

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


More information about the commits mailing list