[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant
Scott Cantor
cantor.2 at osu.edu
Wed Feb 2 20:36:03 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=21bc27b5fbf2fd824f7cc20bce1fc0499120b214
The following commit(s) were added to refs/heads/main by this push:
new 21bc27b5 JOIDC-11 - Support for client_credentials grant
21bc27b5 is described below
commit 21bc27b5fbf2fd824f7cc20bce1fc0499120b214
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 2 15:36:00 2022 -0500
JOIDC-11 - Support for client_credentials grant
https://shibboleth.atlassian.net/browse/JOIDC-11
Implement redesigned revocation flow.
---
...xt.java => OAuth2TokenMgmtResponseContext.java} | 9 +-
.../idp/plugin/oidc/op/profile/OidcEventIds.java | 12 +-
...ection.java => AbstractProcessTokenAction.java} | 56 +++--
.../FormOutboundIntrospectionResponseMessage.java | 10 +-
.../FormOutboundRevokeTokenResponseMessage.java | 7 +-
...zeOutboundTokenMgmtResponseMessageContext.java} | 11 +-
.../profile/impl/ProcessTokenForIntrospection.java | 239 +--------------------
...Context.java => ProcessTokenForRevocation.java} | 22 +-
.../oidc/op/oauth2/profile/impl/RevokeToken.java | 76 +++----
.../profile/impl/AbstractOIDCResponseAction.java | 4 +-
.../oauth2/introspection/introspection-beans.xml | 2 +-
.../oauth2/introspection/introspection-flow.xml | 4 +
.../flows/oauth2/revocation/revocation-beans.xml | 11 +-
.../flows/oauth2/revocation/revocation-flow.xml | 10 +-
.../oidc/abstract-api/oidc-abstract-api-beans.xml | 9 +-
.../idp/service/relying-party/postconfig.xml | 50 ++++-
.../op/oauth2/profile/impl/RevokeTokenTest.java | 89 +++-----
.../oidc/op/profile/flow/RevocationFlowTest.java | 2 -
18 files changed, 210 insertions(+), 413 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
similarity index 85%
rename from idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java
rename to idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
index 21b3a96e..9958a0c2 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
@@ -26,13 +26,14 @@ import org.opensaml.messaging.context.MessageContext;
import com.nimbusds.jwt.JWTClaimsSet;
/**
- * Subcontext carrying information for an OAuth token introspection response.
+ * Subcontext carrying information for an OAuth token management responses such as introspection
+ * or revocation.
*
* <p>This context appears as a subcontext of a {@link MessageContext}.</p>
*
* @since 3.1.0
*/
-public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
+public class OAuth2TokenMgmtResponseContext extends BaseContext {
/** The token claim set. */
@Nullable private JWTClaimsSet tokenClaimsSet;
@@ -42,7 +43,7 @@ public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
*
* @return token claims set
*/
- @Nullable public JWTClaimsSet getTokenClaimSet() {
+ @Nullable public JWTClaimsSet getTokenClaimsSet() {
return tokenClaimsSet;
}
@@ -53,7 +54,7 @@ public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
*
* @return this context
*/
- @Nonnull public OAuth2TokenIntrospectionResponseContext setTokenClaimsSet(
+ @Nonnull public OAuth2TokenMgmtResponseContext setTokenClaimsSet(
@Nullable final JWTClaimsSet claims) {
tokenClaimsSet = claims;
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
index 631ff895..e4312805 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
@@ -24,7 +24,10 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
/**
* OpenID Connect -specific constants to use for {@link org.opensaml.profile.action.ProfileAction}
* {@link org.opensaml.profile.context.EventContext}s.
+ *
+ * @deprecated
*/
+ at Deprecated(since="3.1.0", forRemoval=true)
public final class OidcEventIds {
/**
@@ -76,7 +79,14 @@ public final class OidcEventIds {
* The request uri is invalid.
*/
@Nonnull @NotEmpty public static final String INVALID_REQUEST_URI = "InvalidRequestUri";
-
+
+ /**
+ * The revocation attempt failed.
+ *
+ * @since 3.1.0
+ */
+ @Nonnull @NotEmpty public static final String REVOCATION_FAILED = "RevocationFailed";
+
/**
* Constructor.
*/
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
similarity index 84%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
index 55a2215b..3d6b8715 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
@@ -38,12 +38,11 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.TokenIntrospectionRequest;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.oauth2.sdk.token.Token;
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
@@ -63,24 +62,24 @@ import net.shibboleth.utilities.java.support.security.DataSealer;
import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
- * Action that processes a token for introspection by validating it and populating the resulting {@link JWTClaimsSet}
- * into an {@link OAuth2TokenIntrospectionResponseContext} placed beneath the outbound {@link MessageContext}.
+ * Action that processes a token by validating it and populating the resulting {@link JWTClaimsSet}
+ * into an {@link OAuth2TokenMgmtResponseContext} placed beneath the outbound {@link MessageContext}.
*
- * <p>If the token is invalid, revoked, or unintelligible, the context is not created or populated.</p>
+ * <p>If the token can't be validated, the context is not populated.</p>
+ *
+ * @param <T> request message type
*
* @since 3.1.0
*
- * @pre ProfileRequestContext.getInboundMessageContext().getMessage() instanceof {@link TokenIntrospectionRequest}
- * @post If the token is valid for the requester, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
- * OAuth2TokenIntrospectionResponseContext.class) != null and the context contains the token's
- * {@link JWTClaimsSet}.
+ * @post If the token is valid for use, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
+ * OAuth2TokenMgmtResponseContext.class) != null and the context contains the token's {@link JWTClaimsSet}.
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
*/
-public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<TokenIntrospectionRequest> {
+public abstract class AbstractProcessTokenAction<T> extends AbstractOIDCRequestAction<T> {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(ProcessTokenForIntrospection.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(AbstractProcessTokenAction.class);
/** Data sealer for unwrapping token. */
@Nullable private DataSealer dataSealer;
@@ -98,7 +97,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
@Nullable private SignedJWT signedJWT;
/** Constructor. */
- public ProcessTokenForIntrospection() {
+ public AbstractProcessTokenAction() {
claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
}
@@ -150,23 +149,29 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
return true;
}
-// Checkstyle: CyclomaticComplexity OFF
+// Checkstyle: CyclomaticComplexity|MethodLength OFF
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- log.debug("{} Token to introspect: {}", getLogPrefix(), getRequest().getToken().getValue());
+ final Token token = getToken(profileRequestContext);
+ if (token == null) {
+ log.error("{} Token missing from request", getLogPrefix());
+ return;
+ }
+ log.debug("{} Token to introspect: {}", getLogPrefix(), token.getValue());
+
JWTClaimsSet tokenClaimsSet;
- if (getRequest().getToken() instanceof AccessToken) {
- tokenClaimsSet = parseAccessToken(getRequest().getToken());
- } else if (getRequest().getToken() instanceof RefreshToken) {
- tokenClaimsSet = parseRefreshToken(getRequest().getToken());
+ if (token instanceof AccessToken) {
+ tokenClaimsSet = parseAccessToken(token);
+ } else if (token instanceof RefreshToken) {
+ tokenClaimsSet = parseRefreshToken(token);
} else {
// No token hint, have to try both.
- tokenClaimsSet = parseAccessToken(getRequest().getToken());
+ tokenClaimsSet = parseAccessToken(token);
if (tokenClaimsSet == null) {
- tokenClaimsSet = parseRefreshToken(getRequest().getToken());
+ tokenClaimsSet = parseRefreshToken(token);
}
}
@@ -218,7 +223,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
// Populate outbound tree.
profileRequestContext.getOutboundMessageContext().getSubcontext(
- OAuth2TokenIntrospectionResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
+ OAuth2TokenMgmtResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
}
// Checkstyle: CyclomaticComplexity ON
@@ -268,4 +273,13 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
return null;
}
+ /**
+ * Get the token to process.
+ *
+ * @param profileRequestContext current profile request context
+ *
+ * @return the token to process
+ */
+ @Nullable protected abstract Token getToken(@Nonnull final ProfileRequestContext profileRequestContext);
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
index 46f40e99..d2beb1bc 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
@@ -36,7 +36,7 @@ import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.oauth2.sdk.token.AccessTokenType;
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
@@ -52,18 +52,18 @@ public class FormOutboundIntrospectionResponseMessage extends AbstractOIDCReques
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- final OAuth2TokenIntrospectionResponseContext ctx =
+ final OAuth2TokenMgmtResponseContext ctx =
profileRequestContext.getOutboundMessageContext().getSubcontext(
- OAuth2TokenIntrospectionResponseContext.class);
+ OAuth2TokenMgmtResponseContext.class);
- if (ctx == null || ctx.getTokenClaimSet() == null) {
+ if (ctx == null || ctx.getTokenClaimsSet() == null) {
log.debug("{} Introspection of token failed, token was not valid", getLogPrefix());
profileRequestContext.getOutboundMessageContext().setMessage(
new TokenIntrospectionSuccessResponse.Builder(false).build());
return;
}
- final JWTClaimsSet tokenClaimsSet = ctx.getTokenClaimSet();
+ final JWTClaimsSet tokenClaimsSet = ctx.getTokenClaimsSet();
try {
String clientID = tokenClaimsSet.getStringClaim(TokenClaimsSet.KEY_CLIENTID);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
index 532a0cdc..a34e4f24 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
@@ -21,18 +21,21 @@ import javax.annotation.Nonnull;
import org.opensaml.profile.context.ProfileRequestContext;
+import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
+
import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.OAuth2RevocationSuccessResponse;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
/**
* Action that forms outbound token revocation success message. Formed message is set to
* {@link ProfileRequestContext#getOutboundMessageContext()}.
*/
-public class FormOutboundRevokeTokenResponseMessage extends AbstractOIDCResponseAction {
+public class FormOutboundRevokeTokenResponseMessage extends AbstractOIDCRequestAction<TokenRevocationRequest> {
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
profileRequestContext.getOutboundMessageContext().setMessage(new OAuth2RevocationSuccessResponse());
}
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
similarity index 80%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
index 0776a1a9..46cd96b1 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
@@ -20,21 +20,20 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.profile.context.ProfileRequestContext;
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractInitializeOutboundResponseMessageContext;
/**
- * Action that adds an outbound {@link MessageContext} and related contexts to the {@link ProfileRequestContext}
- * not knowing the relying party yet.
+ * Action that adds an outbound {@link MessageContext} and related contexts to the {@link ProfileRequestContext}.
*
* @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
*/
-public class InitializeOutboundTokenIntrospectionResponseMessageContext
+public class InitializeOutboundTokenMgmtResponseMessageContext
extends AbstractInitializeOutboundResponseMessageContext {
/** Constructor. */
- public InitializeOutboundTokenIntrospectionResponseMessageContext() {
- setContextType(OAuth2TokenIntrospectionResponseContext.class);
+ public InitializeOutboundTokenMgmtResponseMessageContext() {
+ setContextType(OAuth2TokenMgmtResponseContext.class);
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
index 55a2215b..b2e65aa0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
@@ -17,255 +17,24 @@
package net.shibboleth.idp.plugin.oidc.op.oauth2.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.messaging.context.MessageContext;
-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.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.TokenIntrospectionRequest;
-import com.nimbusds.oauth2.sdk.token.AccessToken;
-import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.oauth2.sdk.token.Token;
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
-import net.shibboleth.idp.profile.ActionSupport;
-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.NotEmpty;
-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 processes a token for introspection by validating it and populating the resulting {@link JWTClaimsSet}
- * into an {@link OAuth2TokenIntrospectionResponseContext} placed beneath the outbound {@link MessageContext}.
- *
- * <p>If the token is invalid, revoked, or unintelligible, the context is not created or populated.</p>
+ * Action that processes a token for introspection.
*
* @since 3.1.0
- *
- * @pre ProfileRequestContext.getInboundMessageContext().getMessage() instanceof {@link TokenIntrospectionRequest}
- * @post If the token is valid for the requester, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
- * OAuth2TokenIntrospectionResponseContext.class) != null and the context contains the token's
- * {@link JWTClaimsSet}.
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
*/
-public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<TokenIntrospectionRequest> {
+public class ProcessTokenForIntrospection extends AbstractProcessTokenAction<TokenIntrospectionRequest> {
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(ProcessTokenForIntrospection.class);
-
- /** Data sealer for unwrapping token. */
- @Nullable private DataSealer dataSealer;
-
- /** 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;
-
- /** Constructor. */
- public ProcessTokenForIntrospection() {
- claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
- }
-
- /**
- * Set the data sealer instance to use.
- *
- * @param sealer data sealer to use
- */
- public void setDataSealer(@Nullable final DataSealer sealer) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- dataSealer = sealer;
- }
-
- /**
- * 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.
- *
- * @param resolver signing key resolver
- */
- public void setCredentialResolver(@Nullable final CredentialResolver resolver) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- credentialResolver = resolver;
- }
-
/** {@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;
- }
-
-// Checkstyle: CyclomaticComplexity OFF
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- log.debug("{} Token to introspect: {}", getLogPrefix(), getRequest().getToken().getValue());
-
- JWTClaimsSet tokenClaimsSet;
- if (getRequest().getToken() instanceof AccessToken) {
- tokenClaimsSet = parseAccessToken(getRequest().getToken());
- } else if (getRequest().getToken() instanceof RefreshToken) {
- tokenClaimsSet = parseRefreshToken(getRequest().getToken());
- } else {
- // No token hint, have to try both.
- tokenClaimsSet = parseAccessToken(getRequest().getToken());
- if (tokenClaimsSet == null) {
- tokenClaimsSet = parseRefreshToken(getRequest().getToken());
- }
- }
-
- if (tokenClaimsSet == null) {
- log.warn("{} Unable to parse/decode token for introspection", getLogPrefix());
- return;
- }
-
- 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) {
- log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), tokenClaimsSet.getJWTID());
- 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());
- return;
- }
-
- // Populate outbound tree.
- profileRequestContext.getOutboundMessageContext().getSubcontext(
- OAuth2TokenIntrospectionResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
- }
-// Checkstyle: CyclomaticComplexity ON
-
- /**
- * Attempt to parse token.
- *
- * @param token the token
- *
- * @return parsed claim set or null
- */
- @Nullable protected JWTClaimsSet parseAccessToken(@Nonnull @NotEmpty final Token token) {
-
- // Try parsing as a JWT.
- try {
- signedJWT = SignedJWT.parse(token.getValue());
- return signedJWT.getJWTClaimsSet();
- } catch (final ParseException e1) {
-
- }
-
- // Fall back to opaque.
- try {
- return AccessTokenClaimsSet.parse(token.getValue(), dataSealer).getClaimsSet();
- } catch (final DataSealerException | ParseException e) {
-
- }
-
- return null;
- }
-
- /**
- * Attempt to parse refresh token.
- *
- * @param token the token
- *
- * @return parsed claim set or null
- */
- @Nullable protected JWTClaimsSet parseRefreshToken(@Nonnull @NotEmpty final Token token) {
-
- // All refresh tokens are opaque.
- try {
- return RefreshTokenClaimsSet.parse(token.getValue(), dataSealer).getClaimsSet();
- } catch (final DataSealerException | ParseException e) {
-
- }
-
- return null;
+ protected Token getToken(@Nonnull final ProfileRequestContext profileRequestContext) {
+ return getRequest().getToken();
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
similarity index 62%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
index 09aafc11..091edc3a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
@@ -17,20 +17,24 @@
package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
-import org.opensaml.messaging.context.MessageContext;
+import javax.annotation.Nonnull;
+
import org.opensaml.profile.context.ProfileRequestContext;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractInitializeOutboundResponseMessageContext;
+import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
+import com.nimbusds.oauth2.sdk.token.Token;
/**
- * Action that adds an outbound {@link MessageContext} and related OIDC contexts to the {@link ProfileRequestContext}
- * not knowing the relying party yet.
+ * Action that processes a token for revocation.
*
- * TODO: This class can be eliminated now that generics are gone.
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @since 3.1.0
*/
-public class InitializeOutboundRevokeTokenResponseMessageContext
- extends AbstractInitializeOutboundResponseMessageContext {
+public class ProcessTokenForRevocation extends AbstractProcessTokenAction<TokenRevocationRequest> {
+
+ /** {@inheritDoc} */
+ @Override
+ protected Token getToken(@Nonnull final ProfileRequestContext profileRequestContext) {
+ return getRequest().getToken();
+ }
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
index dead3564..91e7da31 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
@@ -17,55 +17,39 @@
package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
-import java.text.ParseException;
import javax.annotation.Nonnull;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.storage.RevocationCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.security.DataSealer;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
- * Action revokes all tokens based on authorization grant of the token to be revoked. If token is access token or
- * refresh token, the id of the authorization code they are derived from is marked as revoked and so invalidating all
+ * Action revokes a token. If the token is was derived from an authorization code, the id of the
+ * authorization code they are derived from is marked as revoked and so invalidating all
* tokens based on it.
*
- * If the token to be revoked is not decodable or the revocation fails, the actions still returns success status.
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link OidcEventIds#REVOCATION_FAILED}
*/
-public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationRequest> {
+public class RevokeToken extends AbstractProfileAction {
/** Class logger. */
- @Nonnull
- private Logger log = LoggerFactory.getLogger(RevokeToken.class);
-
- /** Data sealer for unwrapping token. */
- @NonnullAfterInit private DataSealer dataSealer;
+ @Nonnull private Logger log = LoggerFactory.getLogger(RevokeToken.class);
/** Message revocation cache instance to use. */
@NonnullAfterInit private RevocationCache revocationCache;
-
- /**
- * Set the data sealer instance to use.
- *
- * @param sealer data 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.
@@ -82,7 +66,7 @@ public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationReques
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (revocationCache == null || dataSealer == null) {
+ if (revocationCache == null) {
throw new ComponentInitializationException("RevocationCache and DataSealer cannot be null");
}
}
@@ -90,30 +74,26 @@ public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationReques
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- TokenClaimsSet tokenClaimsSet = null;
- log.debug("{} token to revoke {}", getLogPrefix(), getRequest().getToken().getValue());
- try {
- tokenClaimsSet = AccessTokenClaimsSet.parse(getRequest().getToken().getValue(), dataSealer);
- log.debug("{} access token unwrapped {}", getLogPrefix(), tokenClaimsSet.serialize());
- } catch (final DataSealerException | ParseException e) {
- log.debug("{} token to revoke is not valid access token", getLogPrefix(), e);
- }
- if (tokenClaimsSet == null) {
- try {
- tokenClaimsSet = RefreshTokenClaimsSet.parse(getRequest().getToken().getValue(), dataSealer);
- log.debug("{} refresh token unwrapped {}", getLogPrefix(), tokenClaimsSet.serialize());
- } catch (final DataSealerException | ParseException e) {
- log.debug("{} token to revoke is not valid refresh token", getLogPrefix());
- }
+
+ final OAuth2TokenMgmtResponseContext ctx = profileRequestContext.getOutboundMessageContext().getSubcontext(
+ OAuth2TokenMgmtResponseContext.class);
+ if (ctx == null || ctx.getTokenClaimsSet() == null) {
+ log.debug("{} No token validated for revocation, assumed to be invalid", getLogPrefix());
+ return;
}
- if (tokenClaimsSet == null) {
- log.debug("{} unable to decode token to revoke, nothing to do", getLogPrefix());
+
+ final String id = ctx.getTokenClaimsSet().getJWTID();
+ if (id == null) {
+ log.error("{} No ID found in token claims set (this should be impossible)", getLogPrefix());
return;
}
- if (revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, tokenClaimsSet.getID())) {
- log.debug("{} revoked all tokens based on authorize code {}", getLogPrefix(), tokenClaimsSet.getID());
+
+ if (revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id)) {
+ log.debug("{} Revoked all tokens based on ID '{}'", getLogPrefix(), id);
} else {
- log.warn("{} failed to revoke tokens based on authorize code {}", getLogPrefix(), tokenClaimsSet.getID());
+ log.warn("{} Failed to revoke tokens based on ID '{}'", getLogPrefix(), id);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.REVOCATION_FAILED);
}
}
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
index cd211031..8ef562a8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
@@ -80,14 +80,14 @@ public abstract class AbstractOIDCResponseAction extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
- oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class, false);
+ oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class);
if (oidcResponseContext == null) {
log.error("{} No oidc response context", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
oidcMetadataContext =
- profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class, false);
+ profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class);
if (oidcMetadataContext == null) {
log.error("{} No metadata found for relying party", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
index 4fb590f2..67ada2bc 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
@@ -24,7 +24,7 @@
scope="prototype" />
<bean id="InitializeOutboundMessageContext"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenIntrospectionResponseMessageContext"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenMgmtResponseMessageContext"
scope="prototype" />
<bean id="ProcessTokenForIntrospection"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
index dbdd7c1d..5bd77f7e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
@@ -9,6 +9,7 @@
<evaluate expression="FlowStartPopulateAuditContext" />
<evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="DecodeMessage" />
</action-state>
@@ -16,6 +17,7 @@
<evaluate expression="DecodeMessage" />
<evaluate expression="PostDecodePopulateAuditContext" />
<evaluate expression="'proceed'" />
+
<!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
<transition on="proceed" to="DoMetadataLookup" />
</action-state>
@@ -25,6 +27,7 @@
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="PostLookupPopulateAuditContext" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="AuthenticationSetup" />
</action-state>
@@ -33,6 +36,7 @@
<action-state id="ResumeAfterAuthentication">
<evaluate expression="ProcessTokenForIntrospection" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="BuildResponseMessage" />
</action-state>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
index ed8a563a..334b5849 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
@@ -24,11 +24,18 @@
scope="prototype" />
<bean id="InitializeOutboundMessageContext"
- class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundRevokeTokenResponseMessageContext"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenMgmtResponseMessageContext"
scope="prototype" />
- <bean id="RevokeToken" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.RevokeToken" scope="prototype"
+ <bean id="ProcessTokenForRevocation"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ProcessTokenForRevocation" scope="prototype"
p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+ p:credentialResolver-ref="SigningCredentialsResolver" />
+
+ <bean id="SigningCredentialsResolver" class="net.shibboleth.idp.relyingparty.impl.SigningCredentialsResolver"
+ c:_0-ref="shibboleth.RelyingPartyResolverService" />
+
+ <bean id="RevokeToken" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.RevokeToken" scope="prototype"
p:revocationCache-ref="shibboleth.oidc.RevocationCache" />
<bean id="FormOutboundMessage"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
index 775360af..5ad69304 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
@@ -9,6 +9,7 @@
<evaluate expression="FlowStartPopulateAuditContext" />
<evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="DecodeMessage" />
</action-state>
@@ -16,6 +17,7 @@
<evaluate expression="DecodeMessage" />
<evaluate expression="PostDecodePopulateAuditContext" />
<evaluate expression="'proceed'" />
+
<!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
<transition on="proceed" to="DoMetadataLookup" />
</action-state>
@@ -25,19 +27,17 @@
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="PostLookupPopulateAuditContext" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="AuthenticationSetup" />
</action-state>
<!-- Authentication subflow happens here. -->
<action-state id="ResumeAfterAuthentication">
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="RevokeToken" />
- </action-state>
-
- <action-state id="RevokeToken">
+ <evaluate expression="ProcessTokenForRevocation" />
<evaluate expression="RevokeToken" />
<evaluate expression="'proceed'" />
+
<transition on="proceed" to="BuildResponseMessage" />
</action-state>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
index 24e4a783..e2fc9f96 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
@@ -36,12 +36,15 @@
<entry key="#{T(org.opensaml.profile.action.EventIds).ACCESS_DENIED}"
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).ACCESS_DENIED}" />
- <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_GRANT_TYPE}"
+ <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_GRANT_TYPE}"
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).UNAUTHORIZED_CLIENT}" />
- <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_GRANT}"
+ <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_GRANT}"
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).INVALID_GRANT}" />
- <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_REDIRECT_URI}"
+ <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_REDIRECT_URI}"
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).INVALID_REQUEST}" />
+
+ <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).REVOCATION_FAILED}"
+ value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).SERVER_ERROR}" />
</util:map>
</beans>
\ No newline at end of file
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 d14da7d0..46762697 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
@@ -66,13 +66,13 @@
class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration"
p:issuer-ref="issuer"
p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
- p:claimsValidator-ref="DefaultIssuedJWTClaimsValidator" />
+ p:claimsValidator-ref="DefaultIntrospectionJWTClaimsValidator" />
<bean id="OAUTH2.Revocation" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration"
p:issuer-ref="issuer"
p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
- p:claimsValidator-ref="DefaultIssuedJWTClaimsValidator" />
+ p:claimsValidator-ref="DefaultRevocationJWTClaimsValidator" />
<!-- Metadata-driven variants. -->
@@ -117,11 +117,6 @@
<property name="issuerLookupStrategy">
<bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="issuer" p:defaultValue-ref="issuer" />
</property>
- <property name="issuedClaimsValidatorLookupStrategy">
- <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
- p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
- p:defaultValue-ref="DefaultIssuedJWTClaimsValidator" />
- </property>
</bean>
<bean id="AbstractMDDrivenOIDCFlowAwareProfile" parent="AbstractMDDrivenOAuthClientAuthenticatableProfile" abstract="true">
@@ -364,6 +359,11 @@
</property>
</bean>
</property>
+ <property name="issuedClaimsValidatorLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
+ p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+ p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
+ </property>
</bean>
<bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
@@ -378,6 +378,11 @@
</property>
</bean>
</property>
+ <property name="issuedClaimsValidatorLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
+ p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+ p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
+ </property>
</bean>
<!-- Default client-auth JWT validation wiring. -->
@@ -437,9 +442,13 @@
<!-- Default issued JWT validation wiring (for introspection/revocation). -->
- <bean id="DefaultIssuedJWTClaimsValidator"
+ <bean id="DefaultIntrospectionJWTClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
- p:claimValidators-ref="IssuedClaimsValidators" />
+ p:claimValidators-ref="IntrospectionClaimsValidators" />
+
+ <bean id="DefaultRevocationJWTClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="IntrospectionClaimsValidators" />
<bean id="SelfIssuedClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
@@ -451,6 +460,10 @@
</property>
</bean>
+ <bean id="RequiredClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="jti" />
+
<bean id="ClientIDClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
p:claimName="client_id" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction" />
@@ -468,7 +481,8 @@
p:revocationCache-ref="shibboleth.oidc.RevocationCache"
p:context="#{T(net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts).AUTHORIZATION_CODE}" />
- <util:list id="IssuedClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <util:list id="IntrospectionClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="RequiredClaimsValidator" />
<ref bean="ExpiryClaimsValidator" />
<ref bean="NotBeforeClaimsValidator" />
<ref bean="SelfIssuedClaimsValidator" />
@@ -485,6 +499,22 @@
<ref bean="JWTIDRevocationClaimsValidator" />
</util:list>
+ <util:list id="RevocationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="RequiredClaimsValidator" />
+ <ref bean="ExpiryClaimsValidator" />
+ <ref bean="SelfIssuedClaimsValidator" />
+ <!-- For issued tokens, ensure that the requester is either the client_id or the audience. -->
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator" p:requireAll="false">
+ <property name="claimValidators">
+ <list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="ClientIDClaimsValidator" />
+ <ref bean="LegacyClientIDClaimsValidator" />
+ <ref bean="ClientIDInAudienceClaimsValidator" />
+ </list>
+ </property>
+ </bean>
+ </util:list>
+
<!--
Auto-wiring exposers for credentials to get them loaded into the IdP's relying party config resolver.
The qualifiers control which auto-wiring point is used.
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
index 82b7eb76..6c4f4fc1 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
@@ -17,11 +17,9 @@
package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.NoSuchAlgorithmException;
import java.time.Duration;
+import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.storage.RevocationCache;
import org.opensaml.storage.impl.MemoryStorageService;
import org.springframework.webflow.execution.RequestContext;
@@ -30,19 +28,15 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
-
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
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.testing.BaseTokenClaimsSetTest;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.idp.profile.testing.RequestContextBuilder;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy;
// Checkstyle: ThrowsCount OFF
@@ -60,13 +54,13 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
private RefreshTokenClaimsSet rfClaimsSet;
- TokenRevocationRequest revokeAccessToken;
-
- TokenRevocationRequest revokeRefreshToken;
-
- TokenRevocationRequest unknownToken;
-
private RevocationCache revocationCache;
+
+ private RequestContext src;
+
+ private ProfileRequestContext prc;
+
+ private OAuth2TokenMgmtResponseContext tokenCtx;
@BeforeMethod
protected void setUp() throws Exception {
@@ -79,19 +73,7 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
revocationCache.setEntryExpiration(Duration.ofHours(1));
revocationCache.setStorage(storageService);
revocationCache.initialize();
- }
- @AfterMethod
- protected void tearDown() {
- revocationCache.destroy();
- revocationCache = null;
-
- storageService.destroy();
- storageService = null;
- }
-
- protected void init()
- throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
// init tokens
final AuthorizeCodeClaimsSet acClaimsSet = new AuthorizeCodeClaimsSet.Builder()
.setJWTID(new SecureRandomIdentifierGenerationStrategy())
@@ -107,50 +89,43 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
build();
atClaimsSet = new AccessTokenClaimsSet.Builder(acClaimsSet, scope, dlClaims, dlClaimsUI, iat, exp).build();
rfClaimsSet = new RefreshTokenClaimsSet.Builder(acClaimsSet, iat, exp).build();
+
// init action
action = new RevokeToken();
- action.setDataSealer(sealer);
action.setRevocationCache(revocationCache);
action.initialize();
- revokeAccessToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
- new BearerAccessToken(atClaimsSet.serialize(sealer)));
- // Using BearerAccessToken for refresh token has no relevance to test
- revokeRefreshToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
- new BearerAccessToken(rfClaimsSet.serialize(sealer)));
- unknownToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
- new BearerAccessToken("sometoken"));
+
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+ tokenCtx = prc.getOutboundMessageContext().getSubcontext(OAuth2TokenMgmtResponseContext.class, true);
+ }
+
+ @AfterMethod
+ protected void tearDown() {
+ action.destroy();
+ revocationCache.destroy();
+ storageService.destroy();
}
@Test
- public void testRevokeAccessToken()
- throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
- init();
- final RequestContext requestContext =
- new RequestContextBuilder().setInboundMessage(revokeAccessToken).buildRequestContext();
+ public void testNoToken() {
+ ActionTestingSupport.assertProceedEvent(action.execute(src));
+ }
+
+ @Test
+ public void testRevokeAccessToken() {
Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, atClaimsSet.getID()));
- ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ tokenCtx.setTokenClaimsSet(atClaimsSet.getClaimsSet());
+ ActionTestingSupport.assertProceedEvent(action.execute(src));
Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, atClaimsSet.getID()));
}
@Test
- public void testRevokeRefreshToken()
- throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
- init();
- final RequestContext requestContext =
- new RequestContextBuilder().setInboundMessage(revokeRefreshToken).buildRequestContext();
+ public void testRevokeRefreshToken() {
Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rfClaimsSet.getID()));
- ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ tokenCtx.setTokenClaimsSet(rfClaimsSet.getClaimsSet());
+ ActionTestingSupport.assertProceedEvent(action.execute(src));
Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rfClaimsSet.getID()));
}
- @Test
- public void testRevokeSomeUnknownToken()
- throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
- init();
- final RequestContext requestContext =
- new RequestContextBuilder().setInboundMessage(unknownToken).buildRequestContext();
- ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
-
- }
-
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
index eb84b277..c6f5d53e 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
@@ -28,7 +28,6 @@ import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.webflow.executor.FlowExecutionResult;
-import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -37,7 +36,6 @@ import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope;
-import com.nimbusds.oauth2.sdk.TokenIntrospectionErrorResponse;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.OAuth2RevocationSuccessResponse;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list