[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant
Scott Cantor
cantor.2 at osu.edu
Thu Feb 10 16:55: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=3af70bb1cdbe5f463e45f3bbaf5f409b654a7461
The following commit(s) were added to refs/heads/main by this push:
new 3af70bb1 JOIDC-11 - Support for client_credentials grant
3af70bb1 is described below
commit 3af70bb1cdbe5f463e45f3bbaf5f409b654a7461
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Feb 10 11:55:24 2022 -0500
JOIDC-11 - Support for client_credentials grant
https://shibboleth.atlassian.net/browse/JOIDC-11
Rework flow to split off profile settings for token audience.
---
.../profile/impl/AbstractEncryptTokenAction.java | 2 +-
.../profile/impl/EnforceAudienceConfiguration.java | 183 ---------------------
.../op/oauth2/profile/impl/ValidateAudience.java | 42 ++++-
.../idp/flows/oidc/token/token-beans.xml | 6 +-
.../shibboleth/idp/flows/oidc/token/token-flow.xml | 7 -
.../idp/service/relying-party/postconfig.xml | 63 +++++--
.../oidc/op/profile/flow/AbstractOidcFlowTest.java | 12 +-
.../flow/ClientCredentialsTokenFlowTest.java | 86 +++++-----
.../src/test/resources/conf/relying-party.xml | 13 +-
9 files changed, 149 insertions(+), 265 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
index 1b077f13..98a0486a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
@@ -128,7 +128,7 @@ public abstract class AbstractEncryptTokenAction extends AbstractOIDCResponseAct
final EncryptionMethod encEnc = EncryptionMethod.parse(params.getDataEncryptionAlgorithm());
final String kid = CredentialConversionUtil.resolveKid(credential);
- log.debug("{} encrypting with key {} and params alg: {} enc: {}", getLogPrefix(), kid, encAlg.getName(),
+ log.debug("{} Encrypting with kid {} and params alg: {} enc: {}", getLogPrefix(), kid, encAlg.getName(),
encEnc.getName());
final JWEObject jweObject =
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java
deleted file mode 100644
index ee321b3b..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
-
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.profile.context.ProxiedRequesterContext;
-import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.oidc.profile.config.logic.AllowUntrustedAudiencePredicate;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/**
- * Profile action that evaluates the resolved configuration settings for the audience of a
- * token request.
- *
- * <p>This action recognizes the duality of a token request whereby the original client and
- * the primary recipient are both relying parties of the system at different stages of the
- * flow. Either or both of them may be unverified, but enforcement of this for the audience
- * depends on a profile setting applied to the client's original profile configuration.</p>
- *
- * <p>In addition, this action establishes the audience of a token request as a proxied
- * requester for the purposes of attribute filtering. While it is arguably more correct
- * in many cases to think of the audience as the requester, this is complicated by OIDC
- * and four-legged scenarios in which either no audience exists or both the client and
- * audience may be considered requesters. Treating the audience as proxied is done for
- * consistency across all those use cases.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
- * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
- *
- * @since 3.1.0
- */
-public class EnforceAudienceConfiguration extends AbstractProfileAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(EnforceAudienceConfiguration.class);
-
- /** Strategy that will return the requester's {@link RelyingPartyContext}. */
- @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> requesterContextLookupStrategy;
-
- /** Strategy that will return the audience's {@link RelyingPartyContext}. */
- @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> audienceContextLookupStrategy;
-
- /** Predicate that determines whether to allow an unverified audience. */
- @Nonnull private Predicate<ProfileRequestContext> allowUntrustedAudienceCondition;
-
- /** Original context. */
- @Nullable private RelyingPartyContext reqContext;
-
- /** New context. */
- @Nullable private RelyingPartyContext audContext;
-
- /** Constructor. */
- public EnforceAudienceConfiguration() {
- requesterContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
- audienceContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class).compose(
- new OutboundMessageContextLookup());
- allowUntrustedAudienceCondition = new AllowUntrustedAudiencePredicate();
- }
-
- /**
- * Set the strategy used to return the original requester's {@link RelyingPartyContext}.
- *
- * @param strategy lookup strategy
- */
- public void setRequesterContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- requesterContextLookupStrategy =
- Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
- }
-
- /**
- * Set the strategy used to return the audience's {@link RelyingPartyContext}.
- *
- * @param strategy lookup strategy
- */
- public void setAudienceContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- audienceContextLookupStrategy =
- Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
- }
-
- /**
- * Set the condition used to establish whether to allow an unverified audience.
- *
- * @param condition condition to set
- */
- public void setUntrustedAudienceCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- allowUntrustedAudienceCondition = Constraint.isNotNull(condition,
- "Untrusted audience condition cannot be null");
- }
-
- /** {@inheritDoc}. */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- reqContext = requesterContextLookupStrategy.apply(profileRequestContext);
- audContext = audienceContextLookupStrategy.apply(profileRequestContext);
- if (reqContext == null ||audContext == null) {
- log.error("{} Requester or audience RelyingPartyContext missing", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
- return false;
- }
-
- return true;
- }
-
- /** {@inheritDoc}. */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- if (audContext.isVerified() && audContext.getProfileConfig() == null) {
- log.warn("{} Token profile not enabled for verified audience: {}", getLogPrefix(),
- audContext.getRelyingPartyId());
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
- return;
- }
-
- if (!audContext.isVerified()) {
- final boolean allow = allowUntrustedAudienceCondition.test(profileRequestContext);
- if (!allow) {
- log.warn("{} Client {} not permitted to request access token for untrusted audience {}", getLogPrefix(),
- reqContext.getRelyingPartyId(), audContext.getRelyingPartyId());
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
- return;
- }
-
- if (audContext.getProfileConfig() == null) {
- log.debug("{} Applying previously resolved Token profile configuration to untrusted audience",
- getLogPrefix());
- audContext.setProfileConfig(reqContext.getProfileConfig());
- }
- }
-
- final ProxiedRequesterContext proxyCtx = new ProxiedRequesterContext();
- proxyCtx.getRequesters().add(audContext.getRelyingPartyId());
- profileRequestContext.getOutboundMessageContext().addSubcontext(proxyCtx);
-
- log.debug("{} Authorized token issuance from {} client ({}) to {} audience ({})", getLogPrefix(),
- reqContext.isVerified() ? "verified" : "unverified", reqContext.getRelyingPartyId(),
- audContext.isVerified() ? "verified" : "unverified", audContext.getRelyingPartyId());
- }
-
-}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
index c73bb8f5..eaa4a8e0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
@@ -25,12 +25,16 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.ProxiedRequesterContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseTokenClaimsContext;
import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ClientInfoAudienceLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction;
@@ -42,7 +46,8 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
/**
* Action that validates requested resource/audience values against a computed set of "allowed"
- * values.
+ * values and populates the resulting set into the {@link OIDCAuthenticationResponseContext}
+ * and a {@link ProxiedRequesterContext}.
*
* <p>This is an ordered list, so the first allowed value determines the "primary" audience of
* the eventual token.</p>
@@ -51,6 +56,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* and be returned, or a failure event will be signaled.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link EventIds#ACCESS_DENIED}
*/
public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
@@ -67,6 +73,9 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
/** Strategy used to obtain the audience allowed for the client. */
@Nonnull private Function<ProfileRequestContext,List<String>> allowedAudienceLookupStrategy;
+ /** Strategy used for locating/creating the proxy context. */
+ @Nonnull private Function<ProfileRequestContext,ProxiedRequesterContext> proxiedRequesterContextCreationStrategy;
+
/** Strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext}. */
@Nonnull
private Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext>
@@ -78,6 +87,8 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
relyingPartyIdLookupStrategy = new RelyingPartyIdLookupFunction();
allowedAudienceLookupStrategy = new ClientInfoAudienceLookupFunction().compose(
new DefaultOIDCMetadataContextLookupFunction());
+ proxiedRequesterContextCreationStrategy = new ChildContextLookup<>(ProxiedRequesterContext.class, true).compose(
+ new OutboundMessageContextLookup());
}
/**
@@ -117,6 +128,19 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
"Allowed scope lookyp strategy cannot be null");
}
+ /**
+ * Set the strategy used to locate or create the ProxiedRequesterContext.
+ *
+ * @param strategy lookup/creation strategy
+ */
+ public void setProxiedRequesterContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,ProxiedRequesterContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ proxiedRequesterContextCreationStrategy = Constraint.isNotNull(strategy,
+ "ProxiedRequesterContext lookup strategy cannot be null");
+ }
+
// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
@@ -172,10 +196,20 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
if (effectiveAudience.isEmpty()) {
log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
- } else {
- log.debug("{} Computed audience for client {}: {}", getLogPrefix(), clientId, effectiveAudience);
- getOidcResponseContext().getAudience().addAll(effectiveAudience);
+ return;
}
+
+ log.debug("{} Computed audience for client {}: {}", getLogPrefix(), clientId, effectiveAudience);
+ getOidcResponseContext().getAudience().addAll(effectiveAudience);
+
+ final ProxiedRequesterContext proxyCtx = proxiedRequesterContextCreationStrategy.apply(profileRequestContext);
+ if (proxyCtx == null) {
+ log.error("{} Unable to locate/create ProxiedRequesterContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ proxyCtx.getRequesters().addAll(effectiveAudience);
}
// Checkstyle: CyclomaticComplexity ON
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 0cd017fd..f6a04206 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
@@ -280,11 +280,7 @@
<bean id="SelectAudienceProfileConfiguration"
class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy"
- p:failIfMissing="false" />
-
- <bean id="EnforceAudienceConfiguration"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.EnforceAudienceConfiguration"
- scope="prototype" />
+ p:profileId="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration).PROFILE_ID}" />
<bean id="ResolveAttributesForAudience" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
c:resolverService-ref="shibboleth.AttributeResolverService"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index b8222360..ee791e85 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -119,13 +119,6 @@
<evaluate expression="SelectAudienceProfileConfiguration" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="EnforceAudienceConfiguration" />
- </action-state>
-
- <action-state id="EnforceAudienceConfiguration">
- <evaluate expression="EnforceAudienceConfiguration" />
- <evaluate expression="'proceed'" />
-
<transition on="proceed" to="CheckAttributeResolutionForAudience" />
</action-state>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 960407b3..629616c2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -25,7 +25,6 @@
p:encryptionOptional="%{idp.oidc.encryptionOptional:true}"
p:forcePKCE="%{idp.oidc.forcePKCE:false}"
p:allowPKCEPlain="%{idp.oidc.allowPKCEPlain:false}"
- p:allowUntrustedAudience="%{idp.oauth2.allowUntrustedAudience:false}"
p:iDTokenLifetime="%{idp.oidc.idToken.defaultLifetime:PT1H}"
p:accessTokenLifetime="%{idp.oidc.accessToken.defaultLifetime:PT10M}"
p:refreshTokenLifetime="%{idp.oidc.refreshToken.defaultLifetime:PT2H}"
@@ -64,6 +63,13 @@
<bean id="OIDC.Keyset" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.config.OIDCPublishKeySetConfiguration"
p:securityConfiguration-ref="shibboleth.oidc.PublishKeySetSecurityConfiguration" />
+
+ <bean id="OAUTH2.TokenAudience" parent="AbstractOIDCProfile" lazy-init="true"
+ class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration"
+ p:issuer-ref="issuer"
+ p:encryptionOptional="%{idp.oauth2.encryptionOptional:true}"
+ p:accessTokenType="%{idp.oauth2.accessToken.type:}"
+ p:accessTokenLifetime="%{idp.oauth2.accessToken.defaultLifetime:PT10M}" />
<bean id="OAUTH2.Introspection" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration"
@@ -187,14 +193,6 @@
<constructor-arg value="%{idp.oidc.encryptionOptional:true}" />
</bean>
</property>
- <property name="allowUntrustedAudiencePredicate">
- <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
- <constructor-arg>
- <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="allowUntrustedAudience" />
- </constructor-arg>
- <constructor-arg value="%{idp.oauth2.allowUntrustedAudience:false}" />
- </bean>
- </property>
<property name="iDTokenLifetimeLookupStrategy">
<bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="iDTokenLifetime"
p:defaultValue="%{idp.oidc.idToken.defaultLifetime:PT1H}" />
@@ -372,9 +370,40 @@
p:defaultValue-ref="shibboleth.oidc.PublishKeySetSecurityConfiguration" />
</property>
</bean>
-
- <bean id="OAUTH2.Revocation.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
- class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration">
+
+ <bean id="OAUTH2.TokenAudience.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
+ class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration">
+ <property name="issuerLookupStrategy">
+ <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="issuer" p:defaultValue-ref="issuer" />
+ </property>
+ <property name="resolveAttributesPredicate">
+ <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="resolveAttributes" />
+ </constructor-arg>
+ <constructor-arg value="true" />
+ </bean>
+ </property>
+ <property name="encryptionOptionalPredicate">
+ <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="encryptionOptional" />
+ </constructor-arg>
+ <constructor-arg value="%{idp.oauth2.encryptionOptional:true}" />
+ </bean>
+ </property>
+ <property name="accessTokenTypeLookupStrategy">
+ <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="accessTokenType"
+ p:defaultValue="%{idp.oauth2.accessToken.type:}" />
+ </property>
+ <property name="accessTokenLifetimeLookupStrategy">
+ <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="accessTokenLifetime"
+ p:defaultValue="%{idp.oauth2.accessToken.defaultLifetime:PT10M}" />
+ </property>
+ </bean>
+
+ <bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
+ class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration">
<property name="tokenEndpointAuthMethodsLookupStrategy">
<bean parent="shibboleth.MDDrivenSetProperty" p:propertyName="tokenEndpointAuthMethods">
<property name="defaultValue">
@@ -388,12 +417,12 @@
<property name="issuedClaimsValidatorLookupStrategy">
<bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
- p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
+ p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
</property>
</bean>
-
- <bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
- class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration">
+
+ <bean id="OAUTH2.Revocation.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
+ class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration">
<property name="tokenEndpointAuthMethodsLookupStrategy">
<bean parent="shibboleth.MDDrivenSetProperty" p:propertyName="tokenEndpointAuthMethods">
<property name="defaultValue">
@@ -407,7 +436,7 @@
<property name="issuedClaimsValidatorLookupStrategy">
<bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
- p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
+ p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
</property>
</bean>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 51f597ec..402f5de4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -200,11 +200,13 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
final HashSet<URI> uris = new HashSet<>();
- for (final String uri : redirectUri) {
- try {
- uris.add(new URI(uri));
- } catch (final URISyntaxException e) {
- e.printStackTrace();
+ if (redirectUri != null) {
+ for (final String uri : redirectUri) {
+ try {
+ uris.add(new URI(uri));
+ } catch (final URISyntaxException e) {
+ throw new IOException(e);
+ }
}
}
final HashSet<ResponseType> responseTypes = new HashSet<>();
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 5cb9d1ce..b61b5048 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,6 +24,7 @@ import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
import java.text.ParseException;
import java.time.Instant;
import java.util.Collection;
@@ -42,7 +43,13 @@ import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEDecrypter;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.crypto.RSADecrypter;
+import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
@@ -129,20 +136,6 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
Collections.singletonList(resource), "eduPersonScopedAffiliation");
}
- @Test
- public void testNoScopeJWT() throws Exception {
- setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
- storeMetadata(storageService, clientId + "JWT", clientSecret, null);
- setBasicAuth(clientId + "JWT", 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(), new Scope(),
- Collections.singletonList(resource), "eduPersonScopedAffiliation");
- }
-
@Test
public void testRequestedScope() throws Exception {
setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
@@ -158,9 +151,10 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
}
@Test
- public void testRequestedScopeJWTUnverifiedAudience() throws Exception {
+ public void testRequestedScopeJWTVerifiedAudience() throws Exception {
setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
+ storeMetadata(storageService, resource, null, null);
setBasicAuth(clientId + "JWT", clientSecret);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
@@ -172,20 +166,22 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
}
@Test
- public void testRequestedScopeJWTVerifiedAudience() throws Exception {
+ public void testRequestedScopeJWTVerifiedAudienceEncrypted() throws Exception {
setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
- storeMetadata(storageService, resource, null, null);
+ 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);
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("JWE", response.getTokens().getBearerAccessToken(), scope,
Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
}
-
+
@Test
public void testInvalidSecretJWTAuthn() throws Exception {
final ClientSecretJWT clientAuth = buildSecretJwtAuth(clientSecret + "invalid");
@@ -276,10 +272,12 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
* @throws DataSealerException
* @throws ParseException
* @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)
- throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException {
+ throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException,
+ JOSEException {
if (type == null) {
final AccessTokenClaimsSet at = AccessTokenClaimsSet.parse(token.getValue(),
@@ -300,25 +298,39 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
assertNull(at.getClaimsSet().getClaim(c));
}
}
+ return;
+ }
+
+ 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)) {
- final JWTClaimsSet claims = SignedJWT.parse(token.getValue()).getJWTClaimsSet();
- assertNotNull(claims);
- 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 + type);
- 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 + type);
- if (customClaims != null) {
- for (final String c : customClaims) {
- assertNotNull(claims.getClaim(c));
- }
- }
+ claims = SignedJWT.parse(token.getValue()).getJWTClaimsSet();
+ clientIdSuffix = type;
} else {
- throw new RuntimeException("Bad token format");
+ throw new RuntimeException("Bad token type");
+ }
+
+ assertNotNull(claims);
+ 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.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);
+ if (customClaims != null) {
+ for (final String c : customClaims) {
+ assertNotNull(claims.getClaim(c));
+ }
}
}
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 9a562883..3739d367 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
@@ -23,9 +23,10 @@
<bean id="shibboleth.UnverifiedRelyingParty" parent="RelyingParty">
<property name="profileConfigurations">
<list>
- <ref bean="OIDC.Keyset.MDDriven" />
- <ref bean="OIDC.Registration.MDDriven" />
- <ref bean="OIDC.Configuration.MDDriven" />
+ <ref bean="OIDC.Keyset" />
+ <ref bean="OIDC.Registration" />
+ <ref bean="OIDC.Configuration" />
+ <ref bean="OAUTH2.TokenAudience" />
</list>
</property>
</bean>
@@ -44,7 +45,7 @@
<ref bean="SAML2.AttributeQuery.MDDriven" />
<ref bean="SAML2.ArtifactResolution.MDDriven" />
<ref bean="OIDC.SSO.MDDriven" />
- <bean parent="OIDC.Token.MDDriven" p:allowUntrustedAudience="true" />
+ <ref bean="OIDC.Token.MDDriven" />
<ref bean="OIDC.UserInfo.MDDriven" />
<ref bean="OIDC.Registration.MDDriven" />
<ref bean="OAUTH2.Revocation.MDDriven" />
@@ -81,14 +82,14 @@
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdJWT">
<property name="profileConfigurations">
<list>
- <bean parent="OIDC.Token.MDDriven" p:accessTokenType="JWT" p:allowUntrustedAudience="true" />
+ <bean parent="OIDC.Token.MDDriven"/>
</list>
</property>
</bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="https://rp.example.org">
<property name="profileConfigurations">
<list>
- <bean parent="OIDC.Token.MDDriven" p:accessTokenType="JWT" />
+ <bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" />
</list>
</property>
</bean>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list