[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