[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