[java-idp-oidc] branch dev/JOIDC-7 updated: WIP to fix UserInfo access token handling.
Scott Cantor
cantor.2 at osu.edu
Wed Apr 20 19:54:43 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=764e6c0a9ad4b4961a2dc351d18aa34d94b4cbcf
The following commit(s) were added to refs/heads/dev/JOIDC-7 by this push:
new 764e6c0a WIP to fix UserInfo access token handling.
764e6c0a is described below
commit 764e6c0a9ad4b4961a2dc351d18aa34d94b4cbcf
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Apr 20 15:54:39 2022 -0400
WIP to fix UserInfo access token handling.
---
.../plugin/oidc/op/profile/impl/ValidateScope.java | 2 +-
...idateAccessToken.java => ParseAccessToken.java} | 100 +++--------
.../userinfo/profile/impl/ValidateAccessToken.java | 177 ++------------------
.../idp/flows/oidc/userinfo/userinfo-beans.xml | 16 +-
.../idp/flows/oidc/userinfo/userinfo-flow.xml | 2 +-
.../idp/service/relying-party/postconfig.xml | 10 --
...essTokenTest.java => ParseAccessTokenTest.java} | 183 +++++++++++----------
.../profile/impl/ValidateAccessTokenTest.java | 35 ----
...onfig.xml => oauth2client-authn-config.xml.off} | 0
pom.xml | 2 +-
10 files changed, 146 insertions(+), 381 deletions(-)
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 f2b03e8a..1b801ea1 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
@@ -155,7 +155,7 @@ public class ValidateScope extends AbstractOIDCAuthenticationResponseAction {
return;
}
- // These come from a previous authorization grant (authz code or refresh token).
+ // These come from a previous authorization grant (authz code or access/refresh token).
Scope previouslyGrantedScopes = null;
if (getOidcResponseContext().getAuthorizationGrantClaimsSet() != null) {
previouslyGrantedScopes = getOidcResponseContext().getAuthorizationGrantClaimsSet().getScope();
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/ParseAccessToken.java
similarity index 69%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessToken.java
index b0f0c20c..d2d5bbf7 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/ParseAccessToken.java
@@ -20,18 +20,17 @@ package net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
-import org.opensaml.storage.RevocationCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,10 +41,6 @@ import com.nimbusds.oauth2.sdk.token.AccessToken;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
-import net.shibboleth.oidc.jwt.claims.JWTValidationException;
-import net.shibboleth.oidc.profile.config.navigate.IssuedClaimsValidatorLookupFunction;
import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -59,27 +54,28 @@ import net.shibboleth.utilities.java.support.security.DataSealer;
import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
- * Action that validates an access token as usable for access to the OP's endpoints.
+ * Action that parses an access token and initially populates the claims for later
+ * validation.
*
- * <p>The validated token is stored to the response context retrievable as claims via
- * {@link OIDCAuthenticationResponseContext#getTokenClaimsSet()}.
+ * <p>Signed JWTs are also signature-checked here.</p>
+ *
+ * <p>The parsed token is stored to the response context retrievable as claims via
+ * {@link OIDCAuthenticationResponseContext#getTokenClaimsSet()}. Claims validation takes
+ * place later in order to allow for metadata and relying-party/profile config
+ * lookup to allow for pluggable validation, an overridden OP/issuer name, etc.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link OidcEventIds#INVALID_GRANT}
+ *
+ * @since 3.2.0
*/
-public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseAction {
+public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(ValidateAccessToken.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(ParseAccessToken.class);
/** Data sealer for unwrapping authorization code. */
@NonnullAfterInit private DataSealer dataSealer;
-
- /** Message revocation cache instance to use. */
- @NonnullAfterInit private RevocationCache revocationCache;
-
- /** Lookup strategy for claims validator. */
- @Nonnull private Function<ProfileRequestContext,ClaimsValidator> claimsValidatorLookupStrategy;
-
- /** The claims validator to use. */
- @Nullable private ClaimsValidator claimsValidator;
/** Source of signing keys. */
@Nullable private CredentialResolver credentialResolver;
@@ -89,11 +85,6 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
/** Our local type used for opaque tokens. */
@Nullable private AccessTokenClaimsSet opaqueClaimsSet;
-
- /** Constructor. */
- public ValidateAccessToken() {
- claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
- }
/**
* Set the data sealer instance to use.
@@ -105,27 +96,6 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
}
- /**
- * Set the revocation cache instance to use.
- *
- * @param cache revocation cache to set
- */
- public void setRevocationCache(@Nonnull final RevocationCache cache) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
- }
-
- /**
- * Set the claims validator lookup strategy.
- *
- * @param strategy lookup strategy
- */
- public void setClaimsValidatorLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,ClaimsValidator> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- claimsValidatorLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
- }
-
/**
* Set the source of signing keys to use for JWT signature verification.
*
@@ -141,28 +111,10 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (revocationCache == null || dataSealer == null) {
+ if (dataSealer == null) {
throw new ComponentInitializationException("RevocationCache and DataSealer cannot be null");
}
}
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- claimsValidator = claimsValidatorLookupStrategy.apply(profileRequestContext);
- if (claimsValidator == null) {
- log.error("{} Unable to obtain ClaimsValidator to apply", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
- return false;
- }
-
- return true;
- }
-
/** {@inheritDoc} */
@Override
@@ -175,7 +127,7 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
return;
}
- JWTClaimsSet tokenClaimsSet = parseAccessToken(token);
+ final JWTClaimsSet tokenClaimsSet = parseAccessToken(token);
if (tokenClaimsSet == null) {
log.warn("{} Unable to parse/decode token for validation", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
@@ -189,11 +141,13 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
final JOSEObjectType typ = signedJWT.getHeader().getType();
if (typ == null || !"at+jwt".equals(typ.getType())) {
log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
if (credentialResolver == null) {
log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
@@ -207,27 +161,19 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
}
} catch (final ResolverException e) {
log.error("{} Failure resolving signing credentials, can't verify JWT signature", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
final String errorEventId = JWTSignatureValidationUtil.validateSignatureEx(credList, signedJWT,
OidcEventIds.INVALID_GRANT);
if (errorEventId != null) {
- ActionSupport.buildEvent(profileRequestContext, errorEventId);
log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), tokenClaimsSet.getJWTID());
+ ActionSupport.buildEvent(profileRequestContext, errorEventId);
return;
}
}
- log.debug("{} Validating parsed/decoded claims set: {}", getLogPrefix(), tokenClaimsSet.toString());
- try {
- claimsValidator.validate(tokenClaimsSet, profileRequestContext);
- } catch (final JWTValidationException e) {
- log.warn("{} Claims validation failed, token is invalid: {}", getLogPrefix(), e.getMessage());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
- return;
- }
-
- log.debug("{} Access token {} validated", getLogPrefix(), tokenClaimsSet.getJWTID());
+ log.debug("{} Access token {} parsed", getLogPrefix(), tokenClaimsSet.getJWTID());
if (opaqueClaimsSet == null) {
// Wraps the JWT claims in a custom object.
opaqueClaimsSet = new AccessTokenClaimsSet(tokenClaimsSet);
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 b0f0c20c..2941b8f1 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
@@ -17,103 +17,54 @@
package net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.security.credential.Credential;
-import org.opensaml.security.credential.CredentialResolver;
-import org.opensaml.security.credential.UsageType;
-import org.opensaml.security.criteria.UsageCriterion;
-import org.opensaml.storage.RevocationCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.jose.JOSEObjectType;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.token.AccessToken;
-
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
import net.shibboleth.oidc.jwt.claims.JWTValidationException;
import net.shibboleth.oidc.profile.config.navigate.IssuedClaimsValidatorLookupFunction;
import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
-import net.shibboleth.utilities.java.support.resolver.ResolverException;
-import net.shibboleth.utilities.java.support.security.DataSealer;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
- * Action that validates an access token as usable for access to the OP's endpoints.
+ * Action that validates the claims pulled from an access token as usable for access
+ * to the OP's UserInfo endpoint.
*
- * <p>The validated token is stored to the response context retrievable as claims via
- * {@link OIDCAuthenticationResponseContext#getTokenClaimsSet()}.
+ * <p>The parsed claims are pulled from
+ * {@link OIDCAuthenticationResponseContext#getAuthorizationGrantClaimsSet()}.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ * @event {@link OidcEventIds#INVALID_GRANT}
*/
public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseAction {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(ValidateAccessToken.class);
-
- /** Data sealer for unwrapping authorization code. */
- @NonnullAfterInit private DataSealer dataSealer;
-
- /** Message revocation cache instance to use. */
- @NonnullAfterInit private RevocationCache revocationCache;
/** Lookup strategy for claims validator. */
@Nonnull private Function<ProfileRequestContext,ClaimsValidator> claimsValidatorLookupStrategy;
/** The claims validator to use. */
@Nullable private ClaimsValidator claimsValidator;
-
- /** Source of signing keys. */
- @Nullable private CredentialResolver credentialResolver;
-
- /** Copy of signed JWT for non-opaque access tokens. */
- @Nullable private SignedJWT signedJWT;
-
- /** Our local type used for opaque tokens. */
- @Nullable private AccessTokenClaimsSet opaqueClaimsSet;
/** Constructor. */
public ValidateAccessToken() {
claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
}
-
- /**
- * Set the data sealer instance to use.
- *
- * @param sealer sealer to use
- */
- public void setDataSealer(@Nonnull final DataSealer sealer) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
- }
-
- /**
- * Set the revocation cache instance to use.
- *
- * @param cache revocation cache to set
- */
- public void setRevocationCache(@Nonnull final RevocationCache cache) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
- }
/**
* Set the claims validator lookup strategy.
@@ -125,26 +76,6 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
claimsValidatorLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
-
- /**
- * Set the source of signing keys to use for JWT signature verification.
- *
- * @param resolver signing key resolver
- */
- public void setCredentialResolver(@Nullable final CredentialResolver resolver) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- credentialResolver = resolver;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (revocationCache == null || dataSealer == null) {
- throw new ComponentInitializationException("RevocationCache and DataSealer cannot be null");
- }
- }
/** {@inheritDoc} */
@Override
@@ -167,100 +98,24 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- final AccessToken token = getUserInfoRequest().getAccessToken();
- if (token == null) {
- log.error("{} Token missing from request", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
- return;
- }
- JWTClaimsSet tokenClaimsSet = parseAccessToken(token);
- if (tokenClaimsSet == null) {
- log.warn("{} Unable to parse/decode token for validation", getLogPrefix());
+ final TokenClaimsSet tokenClaims = getOidcResponseContext().getAuthorizationGrantClaimsSet();
+ if (!(tokenClaims instanceof AccessTokenClaimsSet) || tokenClaims.getClaimsSet() == null) {
+ log.error("{} Claims validation failed, unable to locate access token claims set to validate", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
-
- log.debug("{} Access token unwrapped: {}", getLogPrefix(), tokenClaimsSet.toString());
-
- if (signedJWT != null) {
- // Check typ header.
- final JOSEObjectType typ = signedJWT.getHeader().getType();
- if (typ == null || !"at+jwt".equals(typ.getType())) {
- log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
- return;
- }
-
- if (credentialResolver == null) {
- log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
- return;
- }
-
- log.debug("{} Checking JWT signature", getLogPrefix());
- final Collection<Credential> credList = new ArrayList<>();
- final CriteriaSet criteriaSet = new CriteriaSet(new UsageCriterion(UsageType.SIGNING));
- try {
- final Iterable<Credential> creds = credentialResolver.resolve(criteriaSet);
- if (creds != null) {
- creds.forEach(credList::add);
- }
- } catch (final ResolverException e) {
- log.error("{} Failure resolving signing credentials, can't verify JWT signature", getLogPrefix(), e);
- return;
- }
- final String errorEventId = JWTSignatureValidationUtil.validateSignatureEx(credList, signedJWT,
- OidcEventIds.INVALID_GRANT);
- if (errorEventId != null) {
- ActionSupport.buildEvent(profileRequestContext, errorEventId);
- log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), tokenClaimsSet.getJWTID());
- return;
- }
- }
- log.debug("{} Validating parsed/decoded claims set: {}", getLogPrefix(), tokenClaimsSet.toString());
+ log.debug("{} Validating parsed/decoded claims set: {}", getLogPrefix(), tokenClaims.getClaimsSet().toString());
try {
- claimsValidator.validate(tokenClaimsSet, profileRequestContext);
+ claimsValidator.validate(tokenClaims.getClaimsSet(), profileRequestContext);
} catch (final JWTValidationException e) {
log.warn("{} Claims validation failed, token is invalid: {}", getLogPrefix(), e.getMessage());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
return;
}
- log.debug("{} Access token {} validated", getLogPrefix(), tokenClaimsSet.getJWTID());
- if (opaqueClaimsSet == null) {
- // Wraps the JWT claims in a custom object.
- opaqueClaimsSet = new AccessTokenClaimsSet(tokenClaimsSet);
- }
- getOidcResponseContext().setAuthorizationGrantClaimsSet(opaqueClaimsSet);
- }
-
- /**
- * Attempt to parse token.
- *
- * @param token the token
- *
- * @return parsed claim set or null
- */
- @Nullable protected JWTClaimsSet parseAccessToken(@Nonnull @NotEmpty final AccessToken token) {
-
- // Try parsing as a JWT.
- try {
- signedJWT = SignedJWT.parse(token.getValue());
- return signedJWT.getJWTClaimsSet();
- } catch (final ParseException e1) {
-
- }
-
- // Fall back to opaque.
- try {
- opaqueClaimsSet = AccessTokenClaimsSet.parse(token.getValue(), dataSealer);
- return opaqueClaimsSet.getClaimsSet();
- } catch (final DataSealerException | ParseException e) {
-
- }
-
- return null;
+ log.debug("{} Access token {} validated", getLogPrefix(), tokenClaims.getID());
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 86ecda28..04cce6cb 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -26,17 +26,19 @@
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundUserInfoResponseMessageContext"
scope="prototype" />
- <bean id="ValidateAccessToken"
- class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.ValidateAccessToken" scope="prototype"
- p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
- p:revocationCache-ref="shibboleth.oidc.RevocationCache" />
-
+ <!-- Used for metadata lookup. -->
<bean id="shibboleth.ClientIDLookupStrategy"
class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction" />
- <bean id="shibboleth.UserInfoRequestClientIDLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction" />
+ <bean id="ParseAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.ParseAccessToken" scope="prototype"
+ p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+ p:revocationCache-ref="shibboleth.oidc.RevocationCache" />
+ <bean id="ValidateAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.ValidateAccessToken" scope="prototype"
+ p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
+
<bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype"
p:allowedScopeLookupStrategy="#{getObject('shibboleth.oidc.AllowedScopeStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedScopeStrategy')}">
<property name="requestedScopeLookupStrategy">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-flow.xml
index e22cf7bb..b0f4318c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-flow.xml
@@ -15,7 +15,6 @@
<action-state id="DecodeMessage">
<evaluate expression="DecodeMessage" />
- <evaluate expression="ValidateAccessToken" />
<evaluate expression="PostDecodePopulateAuditContext" />
<evaluate expression="'proceed'" />
@@ -44,6 +43,7 @@
</subflow-state>
<action-state id="OutboundContextsAndSecurityParameters">
+ <evaluate expression="ValidateAccessToken" />
<evaluate expression="ValidateScope" />
<evaluate expression="SetRequestedClaimsToResponseContext" />
<evaluate expression="SetTokenDeliveryAttributesFromTokenToResponseContext" />
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 a5b4a38f..ee263edd 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
@@ -599,16 +599,6 @@
</property>
</bean>
- <bean id="SelfIssuedClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
- p:claimName="iss">
- <property name="valueToMatchLookupStrategy">
- <bean class="net.shibboleth.utilities.java.support.logic.BiFunctionSupport"
- factory-method="forFunctionOfFirstArg"
- c:_0-ref="shibboleth.ResponderIdLookup.Simple" />
- </property>
- </bean>
-
<util:list id="UserInfoClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
<ref bean="RequiredClaimsValidator" />
<ref bean="ExpiryClaimsValidator" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessTokenTest.java
similarity index 59%
copy from idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java
copy to idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessTokenTest.java
index e2d4be55..65e99628 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessTokenTest.java
@@ -23,17 +23,35 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
import net.shibboleth.utilities.java.support.security.DataSealerException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.Collections;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
@@ -41,32 +59,51 @@ import com.nimbusds.openid.connect.sdk.UserInfoRequest;
// Checkstyle: ThrowsCount OFF
-/** {@link ValidateAccessToken} unit test. */
-public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
+/** {@link ParseAccessToken} unit test. */
+public class ParseAccessTokenTest extends BaseOIDCResponseActionTest {
- /** Action to test. */
- private ValidateAccessToken action;
+ /** Private key for JWT signing. */
+ RSAPrivateKey rsaPrivateKey;
- @BeforeMethod
- private void init() throws ComponentInitializationException, NoSuchAlgorithmException {
- action = new ValidateAccessToken();
- action.setDataSealer(getDataSealer());
- action.setRevocationCache(new MockRevocationCache(false, true));
- action.initialize();
- }
+ /** Public key for JWT signing. */
+ RSAPublicKey rsaPublicKey;
+
+ /** Action to test. */
+ private ParseAccessToken action;
/**
- * Test that action throws error if revocation cache is not set.
+ * Init keys for JWT signing.
*
- * @throws NoSuchAlgorithmException
- * @throws ComponentInitializationException
+ * @throws NoSuchAlgorithmException on error
*/
- @Test(expectedExceptions = ComponentInitializationException.class)
- public void testNoRevocationCache() throws NoSuchAlgorithmException, ComponentInitializationException {
- action = new ValidateAccessToken();
+ @BeforeClass
+ public void initKeys() throws NoSuchAlgorithmException {
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ final KeyPair keyPair = keyGen.genKeyPair();
+ rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+ rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+ }
+
+ @BeforeMethod
+ private void init() throws ComponentInitializationException, NoSuchAlgorithmException {
+ action = new ParseAccessToken();
action.setDataSealer(getDataSealer());
+ action.setCredentialResolver(new CredentialResolver() {
+
+ public Iterable<Credential> resolve(CriteriaSet criteria) throws ResolverException {
+ return Collections.singletonList(resolveSingle(criteria));
+ }
+
+ public Credential resolveSingle(CriteriaSet criteria) throws ResolverException {
+ final BasicJWKCredential cred = new BasicJWKCredential();
+ cred.setPublicKey(rsaPublicKey);
+ cred.setPrivateKey(rsaPrivateKey);
+ return cred;
+ }
+
+ });
action.initialize();
- action.execute(requestCtx);
}
/**
@@ -99,17 +136,19 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
ActionTestingSupport.assertProceedEvent(event);
}
+
/**
- * Fails due to access token containing an audience (ours never do).
+ * Basic success case with signed JWT.
*
* @throws NoSuchAlgorithmException
* @throws ComponentInitializationException
* @throws URISyntaxException
* @throws DataSealerException
+ * @throws JOSEException
*/
@Test
- public void testFailsAudience()
- throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
+ public void testJWT()
+ throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException, JOSEException {
final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
.setJWTID(idGenerator)
.setClientID(new ClientID())
@@ -121,77 +160,49 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
.setAuthenticationTime(Instant.now())
.setRedirectURI(new URI("http://example.com"))
.setScope(new Scope())
- .setAudience(Collections.singletonList("foo"))
.build();
- final BearerAccessToken token = new BearerAccessToken(claims.serialize(getDataSealer()));
- final UserInfoRequest req = new UserInfoRequest(new URI("http://example.com"), token);
+
+ JWSSigner signer = new RSASSASigner(rsaPrivateKey);
+ JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm("RS256")).type(new JOSEObjectType("at+jwt"));
+ SignedJWT jwt = new SignedJWT(headerBuilder.build(), claims.getClaimsSet());
+ jwt.sign(signer);
+
+ BearerAccessToken token = new BearerAccessToken(jwt.serialize());
+ UserInfoRequest req = new UserInfoRequest(new URI("http://example.com"), token);
setUserInfoRequest(req);
- final Event event = action.execute(requestCtx);
- ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_GRANT);
- }
-
- /**
- * Fails due to access token being substituted with authorize code.
- *
- * @throws NoSuchAlgorithmException
- * @throws ComponentInitializationException
- * @throws URISyntaxException
- * @throws DataSealerException
- */
- @Test
- public void testFailsNotAccessToken()
- throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
- final TokenClaimsSet claims = new AuthorizeCodeClaimsSet.Builder()
- .setJWTID(idGenerator)
- .setClientID(new ClientID())
- .setIssuer("issuer")
- .setPrincipal("userPrin")
- .setSubject("subject")
- .setIssuedAt(Instant.now())
- .setExpiresAt(Instant.now().plusSeconds(1))
- .setAuthenticationTime(Instant.now())
- .setRedirectURI(new URI("http://example.com"))
- .setScope(new Scope())
- .build();
- final BearerAccessToken token = new BearerAccessToken(claims.serialize(getDataSealer()));
- final UserInfoRequest req = new UserInfoRequest(new URI("http://example.com"), token);
+
+ Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Wrong token type.
+ headerBuilder = new JWSHeader.Builder(new JWSAlgorithm("RS256"));
+ jwt = new SignedJWT(headerBuilder.build(), claims.getClaimsSet());
+ jwt.sign(signer);
+ token = new BearerAccessToken(jwt.serialize());
+ req = new UserInfoRequest(new URI("http://example.com"), token);
setUserInfoRequest(req);
- final Event event = action.execute(requestCtx);
+
+ event = action.execute(requestCtx);
ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_GRANT);
- }
- /**
- * Fails due token expiration.
- *
- * @throws NoSuchAlgorithmException
- * @throws ComponentInitializationException
- * @throws URISyntaxException
- * @throws DataSealerException
- */
- @Test
- public void testFailsExpired()
- throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
- final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
- .setJWTID(idGenerator)
- .setClientID(new ClientID())
- .setIssuer("issuer")
- .setPrincipal("userPrin")
- .setSubject("subject")
- .setIssuedAt(Instant.now())
- .setExpiresAt(Instant.now().minusMillis(1))
- .setAuthenticationTime(Instant.now())
- .setRedirectURI(new URI("http://example.com"))
- .setScope(new Scope())
- .build();
- final BearerAccessToken token = new BearerAccessToken(claims.serialize(getDataSealer()));
- final UserInfoRequest req = new UserInfoRequest(new URI("http://example.com"), token);
+ // Sign with wrong key.
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ final KeyPair keyPair = keyGen.genKeyPair();
+ signer = new RSASSASigner(keyPair.getPrivate());
+ headerBuilder = new JWSHeader.Builder(new JWSAlgorithm("RS256")).type(new JOSEObjectType("at+jwt"));
+ jwt = new SignedJWT(headerBuilder.build(), claims.getClaimsSet());
+ jwt.sign(signer);
+ token = new BearerAccessToken(jwt.serialize());
+ req = new UserInfoRequest(new URI("http://example.com"), token);
setUserInfoRequest(req);
- final Event event = action.execute(requestCtx);
+
+ event = action.execute(requestCtx);
ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_GRANT);
}
/**
- * Fails due token authz code is revoked. Test not 100% as it really does not test passing id to revocation cache.
+ * Fails due to access token being substituted with authorize code.
*
* @throws NoSuchAlgorithmException
* @throws ComponentInitializationException
@@ -199,13 +210,9 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
* @throws DataSealerException
*/
@Test
- public void testFailsRevoked()
+ public void testFailsNotAccessToken()
throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
- action = new ValidateAccessToken();
- action.setDataSealer(getDataSealer());
- action.setRevocationCache(new MockRevocationCache(true, true));
- action.initialize();
- final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ final TokenClaimsSet claims = new AuthorizeCodeClaimsSet.Builder()
.setJWTID(idGenerator)
.setClientID(new ClientID())
.setIssuer("issuer")
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java
index e2d4be55..a2f609e9 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessTokenTest.java
@@ -50,8 +50,6 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
@BeforeMethod
private void init() throws ComponentInitializationException, NoSuchAlgorithmException {
action = new ValidateAccessToken();
- action.setDataSealer(getDataSealer());
- action.setRevocationCache(new MockRevocationCache(false, true));
action.initialize();
}
@@ -64,7 +62,6 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
@Test(expectedExceptions = ComponentInitializationException.class)
public void testNoRevocationCache() throws NoSuchAlgorithmException, ComponentInitializationException {
action = new ValidateAccessToken();
- action.setDataSealer(getDataSealer());
action.initialize();
action.execute(requestCtx);
}
@@ -129,36 +126,6 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
final Event event = action.execute(requestCtx);
ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_GRANT);
}
-
- /**
- * Fails due to access token being substituted with authorize code.
- *
- * @throws NoSuchAlgorithmException
- * @throws ComponentInitializationException
- * @throws URISyntaxException
- * @throws DataSealerException
- */
- @Test
- public void testFailsNotAccessToken()
- throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
- final TokenClaimsSet claims = new AuthorizeCodeClaimsSet.Builder()
- .setJWTID(idGenerator)
- .setClientID(new ClientID())
- .setIssuer("issuer")
- .setPrincipal("userPrin")
- .setSubject("subject")
- .setIssuedAt(Instant.now())
- .setExpiresAt(Instant.now().plusSeconds(1))
- .setAuthenticationTime(Instant.now())
- .setRedirectURI(new URI("http://example.com"))
- .setScope(new Scope())
- .build();
- final BearerAccessToken token = new BearerAccessToken(claims.serialize(getDataSealer()));
- final UserInfoRequest req = new UserInfoRequest(new URI("http://example.com"), token);
- setUserInfoRequest(req);
- final Event event = action.execute(requestCtx);
- ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_GRANT);
- }
/**
* Fails due token expiration.
@@ -202,8 +169,6 @@ public class ValidateAccessTokenTest extends BaseOIDCResponseActionTest {
public void testFailsRevoked()
throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException, DataSealerException {
action = new ValidateAccessToken();
- action.setDataSealer(getDataSealer());
- action.setRevocationCache(new MockRevocationCache(true, true));
action.initialize();
final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
.setJWTID(idGenerator)
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml b/idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml.off
similarity index 100%
rename from idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml
rename to idp-oidc-extension-impl/src/test/resources/conf/authn/oauth2client-authn-config.xml.off
diff --git a/pom.xml b/pom.xml
index 24689273..2b8332af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
<properties>
<shib.idp.version>4.2.0</shib.idp.version>
<opensaml.version>4.2.0</opensaml.version>
- <oidc.common.version>2.0.0</oidc.common.version>
+ <oidc.common.version>2.0.1-SNAPSHOT</oidc.common.version>
<gson.version>2.8.6</gson.version>
<commons.io.version>2.6</commons.io.version>
<checkstyle.configLocation>${project.basedir}/resources/checkstyle.xml</checkstyle.configLocation>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list