[java-idp-oidc] 01/02: Initial draft of reworked token flow.
Scott Cantor
cantor.2 at osu.edu
Mon Apr 18 17:33:03 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch dev/JOIDC-7
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=cac37e1c1a7a36f6dec8545c9f4ede50c9b33b6c
commit cac37e1c1a7a36f6dec8545c9f4ede50c9b33b6c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Apr 14 14:50:33 2022 -0400
Initial draft of reworked token flow.
---
.../AbstractTokenClaimsLookupFunction.java | 2 +-
.../DefaultResponseClaimsSetLookupFunction.java | 2 +-
...uthenticationResponseContextLookupFunction.java | 4 +-
.../TokenRequestRedirectURILookupFunction.java | 2 +-
.../op/oauth2/profile/impl/BuildAccessToken.java | 235 +++++++++++++++++----
.../op/oauth2/profile/impl/ValidateAudience.java | 50 ++++-
.../profile/impl/AddAccessTokenHashToIDToken.java | 3 +-
.../op/profile/impl/AddAttributesToClaimsSet.java | 1 -
...liveryAttributesFromTokenToResponseContext.java | 2 +-
.../plugin/oidc/op/profile/impl/ValidateGrant.java | 6 +-
.../oidc/op/profile/impl/ValidateRedirectURI.java | 2 +-
.../plugin/oidc/op/profile/impl/ValidateScope.java | 4 +-
...uteConsentEnabledInTokenClaimsSetPredicate.java | 2 +-
.../userinfo/profile/impl/ValidateAccessToken.java | 1 +
.../idp/flows/oidc/token/token-beans.xml | 159 ++++++++------
.../shibboleth/idp/flows/oidc/token/token-flow.xml | 107 +++++++---
.../oauth2/profile/impl/BuildAccessTokenTest.java | 7 +-
.../plugin/oidc/op/profile/flow/TokenFlowTest.java | 7 +-
18 files changed, 433 insertions(+), 163 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractTokenClaimsLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractTokenClaimsLookupFunction.java
index f74cc2e9..94dc3e1e 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractTokenClaimsLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractTokenClaimsLookupFunction.java
@@ -50,7 +50,7 @@ public abstract class AbstractTokenClaimsLookupFunction<T>
return null;
}
final OIDCAuthenticationResponseContext oidcResponseContext =
- input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class, false);
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
if (oidcResponseContext == null) {
return null;
}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultResponseClaimsSetLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultResponseClaimsSetLookupFunction.java
index 3d00b22b..92fd6c03 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultResponseClaimsSetLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultResponseClaimsSetLookupFunction.java
@@ -36,7 +36,7 @@ public class DefaultResponseClaimsSetLookupFunction
return null;
}
final OIDCAuthenticationResponseContext ctx =
- input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class, false);
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
if (ctx == null) {
return null;
}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/OIDCAuthenticationResponseContextLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/OIDCAuthenticationResponseContextLookupFunction.java
index 13eb87c3..b5ec3ed5 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/OIDCAuthenticationResponseContextLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/OIDCAuthenticationResponseContextLookupFunction.java
@@ -29,7 +29,7 @@ import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationRes
* {@link ProfileRequestContext}.
*/
public class OIDCAuthenticationResponseContextLookupFunction
- implements ContextDataLookupFunction<ProfileRequestContext, OIDCAuthenticationResponseContext> {
+ implements ContextDataLookupFunction<ProfileRequestContext,OIDCAuthenticationResponseContext> {
/** {@inheritDoc} */
@Nullable
@@ -37,7 +37,7 @@ public class OIDCAuthenticationResponseContextLookupFunction
if (input == null || input.getOutboundMessageContext() == null) {
return null;
}
- return input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class, false);
+ return input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
}
}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestRedirectURILookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestRedirectURILookupFunction.java
index 81f4ce6d..1b5b1015 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestRedirectURILookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestRedirectURILookupFunction.java
@@ -57,7 +57,7 @@ public class TokenRequestRedirectURILookupFunction extends AbstractTokenRequestL
try {
uri = new URI(redirectURI);
} catch (final URISyntaxException e) {
- log.warn("Unable to parse uri from token request redirect_uri {}", redirectURI);
+ log.warn("Unable to parse URI from token request redirect_uri {}", redirectURI);
}
return uri;
}
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 ba01d36c..8e627a3c 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
@@ -20,6 +20,7 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -34,16 +35,26 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.minidev.json.JSONArray;
+import net.shibboleth.idp.authn.context.SubjectContext;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.AccessTokenContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseConsentContext;
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.OIDCAuthenticationResponseContextLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestClientIDLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet.Builder;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.oidc.profile.config.logic.AttributeConsentFlowEnabledPredicate;
import net.shibboleth.oidc.profile.config.navigate.AccessTokenLifetimeLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.AccessTokenTypeLookupFunction;
@@ -60,12 +71,13 @@ import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifie
/**
* Action that creates an Access Token, and stores it to an {@link AccessTokenContext}.
+ *
+ * <p>There are various cases handled across different grant types and orders of operation.
+ * The token may be produced solely for a third-party service to consume, or may also or instead
+ * be usable with the OP's UserInfo endpoint.</p>
*
- * <p>This supports arbitrary ("generic") OAuth access tokens requested by the "client_credentials" grant type.
- * This is a "pure" OAuth use case and does not involve any OIDC behavior.</p>
- *
- * <p>The action supports either fully opaque access tokens sealed under the IdP's secret key, or
- * the RFC 9068 standard for JWT-based tokens.</p>
+ * <p>The action supports either opaque access tokens sealed under the IdP's secret key, or the
+ * RFC 9068 standard for JWT-based tokens.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#MESSAGE_PROC_ERROR}
@@ -101,21 +113,38 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
/** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
@Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+ /** Strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext}. */
+ @Nonnull private Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext>
+ tokenClaimsContextLookupStrategy;
+
+ /** Strategy used to locate the {@link OIDCAuthenticationResponseConsentContext}. */
+ @Nonnull private Function<ProfileRequestContext, OIDCAuthenticationResponseConsentContext>
+ consentContextLookupStrategy;
+
+ /** Predicate used to check if consent is enabled with a given {@link ProfileRequestContext}. */
+ @Nonnull private Predicate<ProfileRequestContext> consentEnabledPredicate;
+
/** Strategy used to create the subcontext to hold the token. */
@Nonnull private Function<ProfileRequestContext,AccessTokenContext> accessTokenContextCreationStrategy;
+
+ /** Authorize Code / Refresh Token the access token is based on, if any. */
+ @Nullable private TokenClaimsSet tokenClaimsSet;
+
+ /** Authentication request in the case of such. */
+ @Nullable private AuthenticationRequest authenticationRequest;
+
+ /** Subject context. */
+ @Nullable private SubjectContext subjectCtx;
/** Use a JWT for the token. */
private boolean jwtTokenType;
- /** Access token context. */
- @Nullable private AccessTokenContext accessTokenCtx;
-
- /** Attribute context. */
- @Nullable private AttributeContext attributeCtx;
-
/** The generator to use. */
@Nullable private IdentifierGenerationStrategy idGenerator;
+ /** Access token context. */
+ @Nullable private AccessTokenContext accessTokenCtx;
+
/** Constructor. */
public BuildAccessToken() {
accessTokenTypeLookupStrategy = new AccessTokenTypeLookupFunction();
@@ -127,6 +156,15 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+ tokenClaimsContextLookupStrategy =
+ new ChildContextLookup<>(OIDCAuthenticationResponseTokenClaimsContext.class).compose(
+ new OIDCAuthenticationResponseContextLookupFunction());
+ consentContextLookupStrategy =
+ new ChildContextLookup<>(OIDCAuthenticationResponseConsentContext.class).compose(
+ new OIDCAuthenticationResponseContextLookupFunction());
+
+ consentEnabledPredicate = new AttributeConsentFlowEnabledPredicate();
+
// PRC -> inbound message context -> OIDC response context -> ATC
accessTokenContextCreationStrategy = new ChildContextLookup<>(AccessTokenContext.class, true).compose(
new ChildContextLookup<>(OIDCAuthenticationResponseContext.class).compose(
@@ -182,38 +220,76 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
}
/**
- * Set the strategy used to create the {@link AccessTokenContext} to use.
+ * Set the strategy used to locate the issuer value to use.
*
- * @param strategy creation strategy
+ * @param strategy lookup strategy
*/
- public void setAccessTokenContextCreationStrategy(
- @Nonnull final Function<ProfileRequestContext,AccessTokenContext> strategy) {
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- accessTokenContextCreationStrategy =
- Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
}
/**
- * Set the strategy used to locate the issuer value to use.
+ * Set the strategy used to locate the original {@link ClientID} from the request.
*
* @param strategy lookup strategy
*/
- public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ public void setClientIDLookupStrategy(@Nonnull final Function<ProfileRequestContext,ClientID> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ clientIDLookupStrategy = Constraint.isNotNull(strategy, "ClientID lookup strategy cannot be null");
}
/**
- * Set the strategy used to locate the original {@link ClientID} from the request.
+ * Set the strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext} associated with a given
+ * {@link ProfileRequestContext}.
*
* @param strategy lookup strategy
*/
- public void setClientIDLookupStrategy(@Nonnull final Function<ProfileRequestContext,ClientID> strategy) {
+ public void setOIDCAuthenticationResponseTokenClaimsContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ tokenClaimsContextLookupStrategy = Constraint.isNotNull(strategy,
+ "OIDCAuthenticationResponseTokenClaimsContextt lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationResponseConsentContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOIDCAuthenticationResponseConsentContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,OIDCAuthenticationResponseConsentContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ consentContextLookupStrategy = Constraint.isNotNull(strategy,
+ "OIDCAuthenticationResponseConsentContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the predicate used to check if consent is enabled with a given {@link ProfileRequestContext}.
+ *
+ * @param predicate predicate used to check if consent is enabled with a given {@link ProfileRequestContext}.
+ */
+ public void setConsentEnabledPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ consentEnabledPredicate =
+ Constraint.isNotNull(predicate, "predicate used to check if consent is enabled cannot be null");
+ }
+
+ /**
+ * Set the strategy used to create the {@link AccessTokenContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setAccessTokenContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,AccessTokenContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- clientIDLookupStrategy = Constraint.isNotNull(strategy, "ClientID lookup strategy cannot be null");
+ accessTokenContextCreationStrategy =
+ Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
}
/** {@inheritDoc} */
@@ -232,13 +308,42 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
return false;
}
- idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
- if (idGenerator == null) {
- log.error("{} No identifier generation strategy", getLogPrefix());
+ tokenClaimsSet = getOidcResponseContext().getAuthorizationGrantClaimsSet();
+ if (tokenClaimsSet != null && !(tokenClaimsSet instanceof RefreshTokenClaimsSet)
+ && !(tokenClaimsSet instanceof AuthorizeCodeClaimsSet)) {
+ log.error("{} Authorization grant is of unknown type: {}", getLogPrefix(), tokenClaimsSet.getClass().getName());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
-
+
+ if (tokenClaimsSet == null) {
+ /*
+ * Typically this path applies when the client_credentials grant is used.
+ *
+ * Alternatively the access token may be provided by the authz endpoint without a code.
+ * This is the case only with the "token id_token" response type.
+ */
+ subjectCtx = profileRequestContext.getSubcontext(SubjectContext.class);
+ if (subjectCtx == null) {
+ log.error("{} No subject context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
+ if (idGenerator == null) {
+ log.error("{} No identifier generation strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ if (profileRequestContext.getInboundMessageContext() != null
+ && profileRequestContext.getInboundMessageContext().getMessage() instanceof AuthenticationRequest) {
+ authenticationRequest =
+ (AuthenticationRequest) profileRequestContext.getInboundMessageContext().getMessage();
+ }
+ }
+
accessTokenCtx = accessTokenContextCreationStrategy.apply(profileRequestContext);
if (accessTokenCtx == null) {
log.error("{} Unable to create AccessTokenContext", getLogPrefix());
@@ -268,27 +373,77 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
return;
}
+ ClaimsSet claims = null;
+ ClaimsSet claimsUI = null;
+ final OIDCAuthenticationResponseTokenClaimsContext tokenClaimsCtx =
+ tokenClaimsContextLookupStrategy.apply(profileRequestContext);
+ if (tokenClaimsCtx != null) {
+ claims = tokenClaimsCtx.getClaims();
+ claimsUI = tokenClaimsCtx.getUserinfoClaims();
+ }
+
final OIDCAuthenticationResponseContext responseCtx = getOidcResponseContext();
final Scope scope = responseCtx.getScope() != null ? responseCtx.getScope() : new Scope();
+ log.debug("{} Building access token with scope: {}", getLogPrefix(), scope);
+
+ final boolean oidc = scope.contains("openid");
+ if (oidc) {
+ responseCtx.getAudience().add(issuer);
+ }
log.debug("{} Building access token with audience: {}", getLogPrefix(), responseCtx.getAudience());
- log.debug("{} Building access token with scope: {}", getLogPrefix(), scope);
final Instant now = Instant.now();
final Instant dateExp = now.plus(accessTokenCtx.getLifetime());
- final AccessTokenClaimsSet.Builder builder = (Builder) new AccessTokenClaimsSet.Builder()
- .setJWTID(idGenerator)
- .setClientID(clientID)
- .setIssuer(issuer)
- .setSubject(responseCtx.getSubject())
- .setIssuedAt(now)
- .setExpiresAt(dateExp)
- .setACR(responseCtx.getAcr())
- .setAuthenticationTime(responseCtx.getAuthTime())
- .setScope(scope)
- .setAudience(responseCtx.getAudience());
+ final AccessTokenClaimsSet.Builder builder;
+
+ // TODO: some of these custom claims need to be sealed if JWT format is used.
+
+ if (tokenClaimsSet != null) {
+ // We may not use original claims as input for scope / delivery claims as they may have been reduced.
+ builder = new AccessTokenClaimsSet.Builder(
+ tokenClaimsSet,
+ scope,
+ oidc ? claims : null,
+ oidc ? claimsUI : null,
+ Instant.now(),
+ dateExp);
+ // Add additional bits.
+ builder.setAudience(responseCtx.getAudience());
+ } else {
+ final OIDCAuthenticationResponseConsentContext consentCtx =
+ consentContextLookupStrategy.apply(profileRequestContext);
+ final JSONArray consented = consentCtx != null ? consentCtx.getConsentedAttributes() : null;
+
+ builder = (Builder) new AccessTokenClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(clientID)
+ .setIssuer(issuer)
+ .setPrincipal(subjectCtx.getPrincipalName())
+ .setSubject(responseCtx.getSubject())
+ .setIssuedAt(now)
+ .setExpiresAt(dateExp)
+ .setACR(responseCtx.getAcr())
+ .setAuthenticationTime(responseCtx.getAuthTime())
+ .setScope(scope)
+ .setAudience(responseCtx.getAudience())
+ .setDlClaims(claims)
+ .setDlClaimsUI(claimsUI)
+ .setConsentedClaims(consented)
+ .setConsentEnabled(consentEnabledPredicate.test(profileRequestContext));
+
+ if (authenticationRequest != null) {
+ builder
+ .setNonce(authenticationRequest.getNonce())
+ .setClaimsRequest(authenticationRequest.getOIDCClaims());
+ }
+
+ if (responseCtx.getRedirectURI() != null) {
+ builder.setRedirectURI(responseCtx.getRedirectURI());
+ }
+}
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/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 0b926b34..9818fccd 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
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -40,6 +41,7 @@ import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ClientInfoAudi
import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestAudienceLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCAuthenticationResponseAction;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.IssueIDTokenCondition;
import net.shibboleth.idp.profile.context.navigate.RelyingPartyIdLookupFunction;
import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -53,8 +55,9 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* <p>This is an ordered list, so the first allowed value determines the "primary" audience of
* the eventual token.</p>
*
- * <p>Requesting values is optional, in the absence of which at least one allowed value must exist
- * and be returned, or a failure event will be signaled.</p>
+ * <p>Requesting values is optional. If the OP is an implied audience, then no other audience will
+ * be established, but if not then at least one audience must be permitted and the first permitted
+ * value will be assumed.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
@@ -77,6 +80,9 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
/** Strategy used for locating/creating the proxy context. */
@Nonnull private Function<ProfileRequestContext,ProxiedRequesterContext> proxiedRequesterContextCreationStrategy;
+ /** Whether the request includes the OP as an audience. */
+ @Nonnull private Predicate<ProfileRequestContext> selfAudienceCondition;
+
/** Strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext}. */
@Nonnull
private Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext>
@@ -90,6 +96,8 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
new DefaultOIDCMetadataContextLookupFunction());
proxiedRequesterContextCreationStrategy = new ChildContextLookup<>(ProxiedRequesterContext.class, true).compose(
new OutboundMessageContextLookup());
+ // openid scope -> we're issuing an ID token -> the OP will be an audience for the access token
+ selfAudienceCondition = new IssueIDTokenCondition();
}
/**
@@ -126,7 +134,7 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
allowedAudienceLookupStrategy = Constraint.isNotNull(strategy,
- "Allowed scope lookyp strategy cannot be null");
+ "Allowed scope lookup strategy cannot be null");
}
/**
@@ -142,18 +150,36 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
"ProxiedRequesterContext lookup strategy cannot be null");
}
+ /**
+ * Set whether the OP is an implied audience for the token request.
+ *
+ * @param condition condition to set
+ *
+ * @since 3.2.0
+ */
+ public void setSelfAudienceCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ selfAudienceCondition = Constraint.isNotNull(condition, "Self audience condition cannot be null");
+ }
+
// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
final String clientId = relyingPartyIdLookupStrategy.apply(profileRequestContext);
+ final boolean allowNone = selfAudienceCondition.test(profileRequestContext);
// These may come from metadata or be supplemented or substituted from elsewhere.
final List<String> allowedAudience = allowedAudienceLookupStrategy.apply(profileRequestContext);
if (allowedAudience == null || allowedAudience.isEmpty()) {
- log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_TARGET);
+ if (allowNone) {
+ log.debug("{} No allowed audience for client {}, OP will be sole audience", getLogPrefix(), clientId);
+ } else {
+ log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_TARGET);
+ }
return;
}
@@ -175,7 +201,11 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
}
if (requestedAudience == null) {
- // Nothing requested or previously granted, so take the first allowed value.
+ // Nothing requested or previously granted.
+ if (allowNone) {
+ log.debug("{} No audience in request for client {}, OP will be sole audience", getLogPrefix(), clientId);
+ return;
+ }
log.debug("{} No audience in request for client {}, using first allowed", getLogPrefix(), clientId);
requestedAudience = Collections.singletonList(allowedAudience.get(0));
}
@@ -195,8 +225,12 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
}
if (effectiveAudience.isEmpty()) {
- log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_TARGET);
+ if (allowNone) {
+ log.debug("{} No allowed audience for client {}, OP will be sole audience", getLogPrefix(), clientId);
+ } else {
+ log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_TARGET);
+ }
return;
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAccessTokenHashToIDToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAccessTokenHashToIDToken.java
index 30602d48..fd3a2d39 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAccessTokenHashToIDToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAccessTokenHashToIDToken.java
@@ -52,7 +52,7 @@ public class AddAccessTokenHashToIDToken extends AbstractOIDCSigningResponseActi
return;
}
final AccessTokenHash atHash = AccessTokenHash.compute(getOidcResponseContext().getAccessToken(),
- new JWSAlgorithm(getSignatureSigningParameters().getSignatureAlgorithm()));
+ new JWSAlgorithm(getSignatureSigningParameters().getSignatureAlgorithm()), null);
if (atHash == null || atHash.getValue() == null) {
log.error("{} Not able to generate at_hash using algorithm {}", getLogPrefix(),
getSignatureSigningParameters().getSignatureAlgorithm());
@@ -63,7 +63,6 @@ public class AddAccessTokenHashToIDToken extends AbstractOIDCSigningResponseActi
getOidcResponseContext().getIDToken().setClaim(IDTokenClaimsSet.AT_HASH_CLAIM_NAME, atHash.getValue());
log.debug("{} Updated token {}", getLogPrefix(),
getOidcResponseContext().getIDToken().toJSONObject().toJSONString());
-
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
index 2c92c468..189eaf4a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
@@ -119,7 +119,6 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
/** List of claim names that will not be added. */
@Nullable @NonnullElements private List<String> reservedClaimNames;
-
/** Attributes to include in ID token no matter what. */
@Nullable @NonnullElements private Set<String> alwaysIncludedAttributes;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetTokenDeliveryAttributesFromTokenToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetTokenDeliveryAttributesFromTokenToResponseContext.java
index 048afbc8..7ff1d0c4 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetTokenDeliveryAttributesFromTokenToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetTokenDeliveryAttributesFromTokenToResponseContext.java
@@ -39,7 +39,7 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
/**
* Action that locates any token delivery claims from authorization code / access token. For located claims
* {@link OIDCAuthenticationResponseTokenClaimsContext} is created under {@link OIDCAuthenticationResponseContext} and
- * the claims are placed there. Token and user info end points use the context for forming response.
+ * the claims are placed there. Token and user info endpoints use the context for forming response.
**/
public class SetTokenDeliveryAttributesFromTokenToResponseContext extends AbstractOIDCResponseAction {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
index 6a8920e4..4596acdd 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
@@ -198,14 +198,14 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
log.error("{} Replay detected of authz code {}", getLogPrefix(), authzCodeClaimsSet.getID());
if (!revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE,
authzCodeClaimsSet.getID())) {
- log.warn("{} Fatal error, unable to set entry to revocation cache", getLogPrefix());
+ log.warn("{} Fatal error, unable to save replayed code to revocation cache", getLogPrefix());
}
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
tokenClaimsSet = authzCodeClaimsSet;
} catch (final DataSealerException | ParseException e) {
- log.warn("{} Obtaining authz code failed {}", getLogPrefix(), e.getMessage());
+ log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
@@ -231,7 +231,7 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
}
tokenClaimsSet = refreshTokenClaimsSet;
} catch (final ParseException | DataSealerException e) {
- log.warn("{} Obtaining refresh token failed {}", getLogPrefix(), e.getMessage());
+ log.warn("{} Unwrapping refresh token failed {}", getLogPrefix(), e.getMessage());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRedirectURI.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRedirectURI.java
index 7e7029ff..36d9c1fc 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRedirectURI.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRedirectURI.java
@@ -35,7 +35,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
/**
- * Action that validates redirect uri is a expected one. Validated redirect uri is stored to response context.
+ * Action that validates redirect uri is expected. Validated redirect uri is stored to response context.
*/
public class ValidateRedirectURI extends AbstractOIDCAuthenticationResponseAction {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateScope.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateScope.java
index ae30bf7a..f2b03e8a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateScope.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateScope.java
@@ -49,7 +49,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* <p>Requested scopes come from the inbound message and are possible but optional for both
* authorization and token requests. They come from lookup functions aware of each message type.</p>
*
- * <p>Previously granted scopes are stored in the response context's slow for previous authorization
+ * <p>Previously granted scopes are stored in the response context's slot for previous authorization
* grant claims. In the case where no scopes are explicitly requested, we still filter the previous
* grants against the metadata.</p>
*
@@ -188,7 +188,7 @@ public class ValidateScope extends AbstractOIDCAuthenticationResponseAction {
}
if (requestedScopes.contains(OIDCScopeValue.OFFLINE_ACCESS)) {
- // DefaultRequestResponseTypeLookupFunction returns response type only in authentication end point.
+ // DefaultRequestResponseTypeLookupFunction returns response type only on authorization end point.
// It is enough to remove offline_scope in this first validation turn.
final ResponseType responseType =
new DefaultRequestResponseTypeLookupFunction().apply(profileRequestContext);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeConsentEnabledInTokenClaimsSetPredicate.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeConsentEnabledInTokenClaimsSetPredicate.java
index 9b8172b6..a58cc9cf 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeConsentEnabledInTokenClaimsSetPredicate.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeConsentEnabledInTokenClaimsSetPredicate.java
@@ -39,7 +39,7 @@ public class AttributeConsentEnabledInTokenClaimsSetPredicate extends AbstractRe
final MessageContext outboundMessageCtx = input.getOutboundMessageContext();
if (outboundMessageCtx != null) {
final OIDCAuthenticationResponseContext oidcResponseContext =
- outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class, false);
+ outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class);
if (oidcResponseContext != null && oidcResponseContext.getAuthorizationGrantClaimsSet() != null) {
return oidcResponseContext.getAuthorizationGrantClaimsSet().isConsentEnabled();
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
index c0e2fe9f..6802c05b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
@@ -105,6 +105,7 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
return;
}
+ // TODO: needs to change to accomodate possibility of dual-use access tokens.
if (!accessTokenClaimsSet.getAudience().isEmpty()) {
log.warn("{} Access token was not issued for use by this OP");
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
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 2ce9c598..2c50d3e8 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
@@ -162,14 +162,14 @@
p:requestedScopeLookupStrategy-ref="TokenRequestScopeLookupStrategy"
p:allowedScopeLookupStrategy="#{getObject('shibboleth.oidc.AllowedScopeStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedScopeStrategy')}" />
- <bean id="TokenRequestScopeLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestScopeLookupFunction" />
-
<bean id="ValidateAudience"
class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateAudience" scope="prototype"
p:requestedAudienceLookupStrategy-ref="TokenRequestAudienceLookupStrategy"
p:allowedAudienceLookupStrategy="#{getObject('shibboleth.oidc.AllowedAudienceStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedAudienceStrategy')}" />
+ <bean id="TokenRequestScopeLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestScopeLookupFunction" />
+
<bean id="TokenRequestAudienceLookupStrategy"
class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestAudienceLookupFunction" />
@@ -315,15 +315,63 @@
<!-- Back to token prep. -->
- <!-- Traditional third-party grant response handling. -->
+ <!-- OIDC response handling for access/refresh tokens. -->
+
+ <bean id="PopulateUserInfoAccessTokenSignatureSigningParameters"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters"
+ scope="prototype"
+ c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
+ p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
+ p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenSignatureSigningParametersResolver">
+ <property name="securityParametersContextLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookupOrCreate.SecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ </bean>
+
+ <bean id="BuildOIDCAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.BuildAccessToken" scope="prototype"
+ p:dataSealer="#{getObject('%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim())}" />
+
+ <bean id="SignOIDCAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SignAccessToken" scope="prototype"
+ p:securityParametersLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
+ p:typeHeader="at+jwt">
+ <property name="securityParametersLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.SecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ </bean>
+
+ <bean id="SetOAuthAccessTokenToResponseContext"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetAccessTokenToResponseContext"
+ scope="prototype" />
+
+ <bean id="SetRefreshTokenToResponseContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRefreshTokenToResponseContext" scope="prototype"
+ c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="IssueIDTokenCondition" />
+ <bean class="net.shibboleth.oidc.profile.config.logic.RefreshTokensEnabledPredicate" />
+ </list>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <!-- ID token actions. -->
<bean id="PopulateIDTokenSignatureSigningParameters"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters"
scope="prototype"
c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
- p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenSignatureSigningParametersResolver"
- p:activationCondition-ref="IssueIDTokenCondition">
+ p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenSignatureSigningParametersResolver">
<property name="securityParametersContextLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookupOrCreate.SecurityParameters"
@@ -332,63 +380,47 @@
<property name="existingParametersContextLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookup.SecurityParameters"
- c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
</property>
</bean>
<bean id="PopulateIDTokenEncryptionParameters"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCEncryptionParameters" scope="prototype"
p:configurationLookupStrategy-ref="shibboleth.EncryptionConfigurationLookup"
- p:encryptionParametersResolver-ref="shibboleth.oidc.EncryptionParametersResolver"
- p:activationCondition-ref="IssueIDTokenCondition" />
+ p:encryptionParametersResolver-ref="shibboleth.oidc.EncryptionParametersResolver" />
<bean id="shibboleth.oidc.EncryptionParametersResolver"
class="net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationEncryptionParametersResolver"
p:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache"
p:keyFetchInterval="%{idp.oidc.jwksuri.fetchInterval:PT30M}" />
- <bean id="SetOIDCAccessTokenToResponseContext"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAccessTokenToResponseContext" scope="prototype"
- p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
-
- <bean id="SetRefreshTokenToResponseContext"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRefreshTokenToResponseContext" scope="prototype"
- c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}">
- <property name="activationCondition">
- <bean class="net.shibboleth.oidc.profile.config.logic.RefreshTokensEnabledPredicate" />
- </property>
- </bean>
-
- <bean id="AddIDTokenShell" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddIDTokenShell"
- scope="prototype" p:activationCondition-ref="IssueIDTokenCondition" />
+ <bean id="AddIDTokenShell"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddIDTokenShell" scope="prototype" />
<bean id="AddAttributeClaimsToIDToken"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
- p:reservedClaimNames="#{getObject('shibboleth.oidc.IDTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultIDTokenReservedClaimNames')}"
- p:activationCondition-ref="IssueIDTokenCondition" />
+ p:reservedClaimNames="#{getObject('shibboleth.oidc.IDTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultIDTokenReservedClaimNames')}" />
<bean id="AddTokenDeliveryAttributesToIDToken"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddTokenDeliveryAttributesToClaimsSet" scope="prototype"
- p:targetIDToken="true" p:activationCondition-ref="IssueIDTokenCondition" />
+ p:targetIDToken="true" />
- <bean id="AddAuthTimeToIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAuthTimeToIDToken"
- scope="prototype" p:activationCondition-ref="IssueIDTokenCondition" />
+ <bean id="AddAuthTimeToIDToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAuthTimeToIDToken" scope="prototype" />
- <bean id="AddAcrToIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAcrToIDToken"
- scope="prototype" p:activationCondition-ref="IssueIDTokenCondition" />
+ <bean id="AddAcrToIDToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAcrToIDToken" scope="prototype" />
- <bean id="AddNonceToIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddNonceToIDToken"
- scope="prototype" p:requestNonceLookupStrategy-ref="shibboleth.TokenRequestNonceLookupStrategy"
- p:activationCondition-ref="IssueIDTokenCondition" />
+ <bean id="AddNonceToIDToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddNonceToIDToken" scope="prototype"
+ p:requestNonceLookupStrategy-ref="shibboleth.TokenRequestNonceLookupStrategy" />
<bean id="shibboleth.TokenRequestNonceLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestNonceLookupFunction"
- scope="prototype" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestNonceLookupFunction" />
<bean id="AddAccessTokenHashToIDToken"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAccessTokenHashToIDToken" scope="prototype"
- p:activationCondition-ref="IssueIDTokenCondition">
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAccessTokenHashToIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookup.SecurityParameters"
@@ -396,8 +428,7 @@
</property>
</bean>
- <bean id="SignIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken" scope="prototype"
- p:activationCondition-ref="IssueIDTokenCondition">
+ <bean id="SignIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookup.SecurityParameters"
@@ -406,7 +437,7 @@
</bean>
<bean id="EncryptIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.EncryptProcessedToken"
- p:activationCondition-ref="IssueIDTokenCondition" scope="prototype" />
+ scope="prototype" />
<bean id="FormOutboundMessage"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.FormOutboundTokenResponseMessage" scope="prototype" />
@@ -420,33 +451,31 @@
</property>
</bean>
- <!-- client_credentials grant response actions. -->
+ <!-- Third-party token actions. -->
<bean id="PopulateAccessTokenSignatureSigningParameters"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters"
- scope="prototype"
- c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
- p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
- p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenSignatureSigningParametersResolver"
- p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy">
- <property name="existingParametersContextLookupStrategy">
- <bean parent="shibboleth.Functions.Compose"
- c:g-ref="shibboleth.ChildLookup.SecurityParameters"
- c:f-ref="shibboleth.MessageContextLookup.Outbound" />
- </property>
- </bean>
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters"
+ scope="prototype"
+ c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
+ p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
+ p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenSignatureSigningParametersResolver"
+ p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy" />
<bean id="AudienceSecurityParametersCreationStrategy" parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookupOrCreate.SecurityParameters"
c:f-ref="AudienceRelyingPartyCreationStrategy" />
<bean id="PopulateAccessTokenEncryptionParameters"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCEncryptionParameters" scope="prototype"
- p:encryptionOptionalPredicate-ref="AudienceEncryptionOptionalPredicate"
- p:oidcMetadataContextLookupStrategy-ref="LookupOutboundOIDCMetadataContext"
- p:configurationLookupStrategy-ref="shibboleth.EncryptionConfigurationLookup"
- p:encryptionParametersResolver-ref="shibboleth.oidc.EncryptionParametersResolver"
- p:encryptionContextLookupStrategy-ref="AudienceEncryptionContextCreationStrategy" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCEncryptionParameters" scope="prototype"
+ p:encryptionOptionalPredicate-ref="AudienceEncryptionOptionalPredicate"
+ p:oidcMetadataContextLookupStrategy-ref="LookupOutboundOIDCMetadataContext"
+ p:configurationLookupStrategy-ref="shibboleth.EncryptionConfigurationLookup"
+ p:encryptionParametersResolver-ref="shibboleth.oidc.EncryptionParametersResolver"
+ p:encryptionContextLookupStrategy-ref="AudienceEncryptionContextCreationStrategy">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.NOT" c:_0-ref="IssueIDTokenCondition" />
+ </property>
+ </bean>
<bean id="AudienceEncryptionOptionalPredicate"
class="net.shibboleth.oidc.profile.config.logic.EncryptionOptionalPredicate"
@@ -487,13 +516,13 @@
p:typeHeader="at+jwt" />
<bean id="EncryptAccessToken"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.EncryptAccessToken" scope="prototype"
- p:encryptionContextLookupStrategy-ref="AudienceEncryptionContextCreationStrategy" />
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.EncryptAccessToken" scope="prototype"
+ p:encryptionContextLookupStrategy-ref="AudienceEncryptionContextCreationStrategy">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.NOT" c:_0-ref="IssueIDTokenCondition" />
+ </property>
+ </bean>
- <bean id="SetOAuthAccessTokenToResponseContext"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetAccessTokenToResponseContext"
- scope="prototype" />
-
<bean id="PostResponsePopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
p:fieldExtractors="#{getObject('shibboleth.oidc.TokenPostResponseAuditExtractors') ?: getObject('shibboleth.oidc.DefaultTokenPostResponseAuditExtractors')}" />
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 0f6afca5..eed418f9 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
@@ -60,11 +60,19 @@
<evaluate expression="InitializeSubjectContext" />
<evaluate expression="SetSubjectFromAuthzCodeToResponseContext" />
<evaluate expression="ValidateScope" />
+ <evaluate expression="ValidateAudience" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="CheckAttributeResolutionForClient" />
+ <transition on="proceed" to="CheckTraditionalGrantForAudience" />
</action-state>
+ <!-- For standard grants, audience may or may not be a factor. -->
+ <decision-state id="CheckTraditionalGrantForAudience">
+ <if test="opensamlProfileRequestContext.getOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAudience().isEmpty()"
+ then="CheckAttributeResolutionForClient"
+ else="LookupAudienceMetadata" />
+ </decision-state>
+
<!-- These steps apply to grants that are self-contained on this endpoint. -->
<action-state id="ClientCredentialsGrantProcessing">
<evaluate expression="SetAuthenticationContextClassReferenceToResponseContext" />
@@ -77,7 +85,7 @@
<transition on="proceed" to="LookupAudienceMetadata" />
</action-state>
- <!-- For client credentials grant, need to flip the Relying Party here to the primary resource/audience. -->
+ <!-- For client credentials grant, need to add a second Relying Party for the primary resource/audience. -->
<action-state id="LookupAudienceMetadata">
<evaluate expression="AudienceOIDCMetadataLookup" />
@@ -137,35 +145,10 @@
<transition on="proceed" to="DoConsentLookup" />
</action-state>
- <decision-state id="BuildResponse">
- <if test="NotClientCredentialsGrantCondition.test(opensamlProfileRequestContext)"
- then="TraditionalGrantResponse"
- else="ClientCredentialsGrantResponse" />
- </decision-state>
-
- <action-state id="TraditionalGrantResponse">
- <evaluate expression="PopulateIDTokenSignatureSigningParameters" />
- <evaluate expression="PopulateIDTokenEncryptionParameters" />
- <evaluate expression="SetOIDCAccessTokenToResponseContext" />
- <evaluate expression="SetRefreshTokenToResponseContext" />
- <evaluate expression="AddIDTokenShell" />
- <evaluate expression="AddAttributeClaimsToIDToken" />
- <evaluate expression="AddTokenDeliveryAttributesToIDToken" />
- <evaluate expression="AddAuthTimeToIDToken" />
- <evaluate expression="AddAcrToIDToken" />
- <evaluate expression="AddNonceToIDToken" />
- <evaluate expression="AddAccessTokenHashToIDToken" />
- <evaluate expression="SignIDToken" />
- <evaluate expression="EncryptIDToken" />
- <evaluate expression="'proceed'" />
-
- <transition on="proceed" to="PopulateOutboundInterceptContext" />
- </action-state>
-
<decision-state id="CheckAttributeResolutionForAudience">
<if test="ResolveAttributesForAudiencePredicate.test(opensamlProfileRequestContext)"
then="AttributeResolutionForAudience"
- else="ClientCredentialsGrantResponse" />
+ else="DoConsentLookup" />
</decision-state>
<action-state id="AttributeResolutionForAudience">
@@ -173,17 +156,77 @@
<evaluate expression="FilterAttributesForAudience" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="ClientCredentialsGrantResponse" />
+ <transition on="proceed" to="DoConsentLookup" />
+ </action-state>
+
+ <decision-state id="BuildResponse">
+ <if test="opensamlProfileRequestContext.getOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAudience().isEmpty()"
+ then="BuildTokensForUserInfoAccess"
+ else="BuildTokensForThirdPartyAccess" />
+ </decision-state>
+
+ <!--
+ Note no JWT encryption here. The token shouldn't even be a JWT, but even if it is,
+ the only audience is us, and the client can't be expected to decrypt it, so it
+ wouldn't be usable. If it were encrypted to our key then there would be no point to
+ allowing it to be a JWT.
+
+ Not also no attribute claims are added to the access token since that isn't a proper
+ delivery mechanism for claims to the OIDC client.
+ -->
+ <action-state id="BuildTokensForUserInfoAccess">
+ <evaluate expression="PopulateUserInfoAccessTokenSignatureSigningParameters" />
+ <evaluate expression="BuildOIDCAccessToken" />
+ <evaluate expression="SignOIDCAccessToken" />
+ <evaluate expression="SetOAuthAccessTokenToResponseContext" />
+ <evaluate expression="SetRefreshTokenToResponseContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="ProducingIDToken" />
</action-state>
- <action-state id="ClientCredentialsGrantResponse">
- <evaluate expression="PopulateAccessTokenSignatureSigningParameters" />
- <evaluate expression="PopulateAccessTokenEncryptionParameters" />
+ <!--
+
+ This includes OIDC and OAuth use cases and all grant types but the primary audience
+ for the access token is the resource/audience. The OP MAY also be an audience for the
+ token if the "oidc" scope is in play, in which case the actions have to behave differently
+ or be disabled in some cases. Encryption is for example impossible in that case, as with
+ the OIDC-only use of JWTs above.
+ -->
+ <action-state id="BuildTokensForThirdPartyAccess">
+ <evaluate expression="PopulateThirdPartyAccessTokenSignatureSigningParameters" />
+ <evaluate expression="PopulateThirdPartyAccessTokenEncryptionParameters" />
<evaluate expression="AddAttributeClaimsToAccessToken" />
<evaluate expression="BuildAccessToken" />
<evaluate expression="SignAccessToken" />
<evaluate expression="EncryptAccessToken" />
<evaluate expression="SetOAuthAccessTokenToResponseContext" />
+ <evaluate expression="SetRefreshTokenToResponseContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="ProducingIDToken" />
+ </action-state>
+
+ <!-- ID token only issued when scope includes "openid" -->
+
+ <decision-state id="ProducingIDToken">
+ <if test="IssueIDTokenCondition.test(opensamlProfileRequestContext)"
+ then="BuildIDToken"
+ else="PopulateOutboundInterceptContext" />
+ </decision-state>
+
+ <action-state id="BuildIDToken">
+ <evaluate expression="PopulateIDTokenSignatureSigningParameters" />
+ <evaluate expression="PopulateIDTokenEncryptionParameters" />
+ <evaluate expression="AddIDTokenShell" />
+ <evaluate expression="AddAttributeClaimsToIDToken" />
+ <evaluate expression="AddTokenDeliveryAttributesToIDToken" />
+ <evaluate expression="AddAuthTimeToIDToken" />
+ <evaluate expression="AddAcrToIDToken" />
+ <evaluate expression="AddNonceToIDToken" />
+ <evaluate expression="AddAccessTokenHashToIDToken" />
+ <evaluate expression="SignIDToken" />
+ <evaluate expression="EncryptIDToken" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="PopulateOutboundInterceptContext" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
index d883801b..f94d9f1b 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
@@ -21,6 +21,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
+import net.shibboleth.idp.authn.context.SubjectContext;
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.token.support.AccessTokenClaimsSet;
@@ -57,12 +58,14 @@ import com.nimbusds.oauth2.sdk.id.ClientID;
public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
/** Action to test. */
- private BuildAccessToken action;
+ @Nullable private BuildAccessToken action;
@BeforeMethod
protected void setUp() throws Exception {
super.setUp();
+ profileRequestCtx.getSubcontext(SubjectContext.class, true).setPrincipalName("jdoe");
+
respCtx.setAuthTime(Instant.now());
respCtx.setSubject(clientId);
respCtx.setAcr("0");
@@ -179,6 +182,7 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
assertTrue(at.getIssuedAt().isBefore(Instant.now()));
assertEquals(at.getScope(), scope);
assertEquals(at.getSubject(), clientId);
+ assertEquals(at.getPrincipal(), "jdoe");
} else if (ctx.getJWT() != null) {
final JWTClaimsSet claims = ctx.getJWT().getJWTClaimsSet();
assertNotNull(claims);
@@ -191,6 +195,7 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
assertTrue(claims.getIssueTime().toInstant().isBefore(Instant.now()));
assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_SCOPE), scope.toString());
assertEquals(claims.getSubject(), clientId);
+ assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_USER_PRINCIPAL), "jdoe");
} else {
throw new RuntimeException("No token found");
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index fd4ec46f..b6e84391 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -126,7 +126,12 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
}
- @Test
+ /**
+ * TODO: This test "fails" now because it's honoring a non-OIDC request by assuming there has to be
+ * a requested and allowed audience/resource. The original success outcome was an anomaly due to the
+ * original grant handling not supporting the audience notion.
+ */
+ @Test(enabled=false)
public void testNoScopes() throws Exception {
setHttpFormRequest("POST",
createRequestParameters(redirectUri, "authorization_code",
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list