[java-idp-oidc] branch main updated: JOIDC-92 - Support for refresh token rotation

Henri Mikkonen henri.mikkonen at iki.fi
Tue Jun 28 09:29:43 UTC 2022


This is an automated email from the git hooks/post-receive script.

hjmikkon 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=7ea6ad22ced31ee4e94e8c3af4fc144f1eb22f56

The following commit(s) were added to refs/heads/main by this push:
     new 7ea6ad22 JOIDC-92 - Support for refresh token rotation
7ea6ad22 is described below

commit 7ea6ad22ced31ee4e94e8c3af4fc144f1eb22f56
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Jun 28 12:28:37 2022 +0300

    JOIDC-92 - Support for refresh token rotation
    
    https://shibboleth.atlassian.net/browse/JOIDC-92
    
    Exploit revocationLifetime from the revocation profile configuration whenever
    revoking the full chain.
---
 .../plugin/oidc/op/profile/impl/ValidateGrant.java | 55 ++++++++++++----------
 .../oidc/op/profile/impl/ValidateGrantTest.java    | 13 ++---
 2 files changed, 38 insertions(+), 30 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
index 38a83a8b..50d80b95 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
@@ -46,7 +46,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.profile.config.logic.RefreshTokensEnabledPredicate;
-import net.shibboleth.oidc.profile.config.navigate.RefreshTokenLifetimeLookupFunction;
+import net.shibboleth.oidc.profile.config.navigate.RevocationLifetimeLookupFunction;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -92,8 +92,8 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     /** Predicate used to indicate whether refresh tokens are enabled. */
     @Nonnull private Predicate<ProfileRequestContext> refreshTokensEnabledPredicate;
 
-    /** Strategy used to lookup the duration for lifetime of an entry in the token revocation cache. */
-    @Nonnull private Function<ProfileRequestContext, Duration> tokenRevocationLifetimeLookupStrategy;
+    /** Lookup function to supply chain revocation lifetime. */
+    @Nonnull private Function<ProfileRequestContext,Duration> chainRevocationLifetimeLookupStrategy;
 
     /** The RelyingPartyContext to operate on. */
     @Nullable private RelyingPartyContext rpCtx;
@@ -107,7 +107,7 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
         relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
         refreshTokensEnabledPredicate = new RefreshTokensEnabledPredicate();
-        tokenRevocationLifetimeLookupStrategy = new RefreshTokenLifetimeLookupFunction();
+        chainRevocationLifetimeLookupStrategy = new RevocationLifetimeLookupFunction();
     }
 
     /**
@@ -157,15 +157,13 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     }
 
     /**
-     * Set the strategy used to lookup the duration for lifetime of an entry in the token revocation cache.
+     * Set a lookup strategy for the chain revocation lifetime.
      *
-     * @param strategy The strategy to set.
+     * @param strategy What to set.
      */
-    public void setTokenRevocationLifetimeLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, Duration> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        tokenRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy,
-                "The lookup strategy cannot be null");
+    public void setChainRevocationLifetimeLookupStrategy(
+            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+        chainRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
     /** {@inheritDoc} */
@@ -203,7 +201,7 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         final AuthorizationGrant grant = getTokenRequest().getAuthorizationGrant();
         
         log.debug("{} Validating grant type: {}", getLogPrefix(),grant.getType());
-        
+
         TokenClaimsSet tokenClaimsSet = null;
         if (GrantType.AUTHORIZATION_CODE.equals(grant.getType())) {
             final AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) grant;
@@ -215,8 +213,8 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                     if (!replayCache.check(getClass().getName(), authzCodeClaimsSet.getID(),
                             authzCodeClaimsSet.getExp())) {
                         log.error("{} Replay detected of authz code {}", getLogPrefix(), authzCodeClaimsSet.getID());
-                        if (!revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE,
-                                authzCodeClaimsSet.getID())) {
+                        if (!revokeChain(authzCodeClaimsSet.getID(),
+                                chainRevocationLifetimeLookupStrategy.apply(profileRequestContext))) {
                             log.warn("{} Fatal error, unable to save replayed code to revocation cache", getLogPrefix());
                         }
                         ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
@@ -259,16 +257,8 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                             refreshTokenClaimsSet.getID())) {
                         log.error("{} The refresh token {} has been revoked. Revoking the full chain now.",
                                 getLogPrefix(), refreshTokenClaimsSet.getID());
-                        final Duration lifetime =
-                                tokenRevocationLifetimeLookupStrategy.apply(profileRequestContext);
-                        if (lifetime == null) {
-                            log.error("{} Could not resolve the token revocation lifetime, full chain not revoked",
-                                    getLogPrefix());
-                            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-                            return;
-                        }
-                        if (!revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, rootJtiToUse,
-                                lifetime)) {
+                        if (!revokeChain(rootJtiToUse,
+                                chainRevocationLifetimeLookupStrategy.apply(profileRequestContext))) {
                             log.error("{} Fatal error, unable to store revocation into the revocation cache",
                                     getLogPrefix());
                             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
@@ -308,4 +298,21 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     }
 // Checkstyle: CyclomaticComplexity|MethodLength|ReturnCount ON
 
+    /**
+     * Revokes the token chain with the given id, optionally with a given lifetime. If the given lifetime is null,
+     * the default lifetime set to the {@link RevocationCache} is used.
+     * 
+     * @param id The identifier to be revoked in {@link RevocationCacheContexts#AUTHORIZATION_CODE} context.
+     * @param lifetime The lifetime for the revocation
+     * @return The result returned by the {@link RevocationCache}
+     */
+    protected boolean revokeChain(@Nonnull final String id, @Nullable final Duration lifetime) {
+        if (lifetime == null) {
+            log.warn("{} No profile-specific revocation lifetime could be resolved, using default value",
+                    getLogPrefix());
+            return revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id);
+        }
+        return revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id, lifetime);
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
index 6fd39c12..76e91a6e 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
@@ -36,6 +36,7 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Function;
 
+import org.mockito.Mockito;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.storage.ReplayCache;
 import org.opensaml.storage.RevocationCache;
@@ -122,7 +123,7 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
         profileRequestCtx.getInboundMessageContext().setMessage(req);
         action = new ValidateGrant(getDataSealer());
         if (revocationLifetimeLookup != null) {
-            action.setTokenRevocationLifetimeLookupStrategy(revocationLifetimeLookup);
+            action.setChainRevocationLifetimeLookupStrategy(revocationLifetimeLookup);
         }
         action.setRevocationCache(revocationCache);
         final ReplayCache replayCache = new ReplayCache();
@@ -277,12 +278,12 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
     }
 
     @Test
-    public void testRefreshTokenRevokedLifetimeRequired() throws Exception {
-        final RevocationCache revocationCache = new RevocationCache();
-        revocationCache.setStorage(storageService);
+    public void testTokenRevocationViaRevokedTokenFailsReturnsInvalidProfileConfig() throws Exception {
+        RevocationCache revocationCache = Mockito.mock(RevocationCache.class);
+        Mockito.when(revocationCache.isRevoked(Mockito.matches(RevocationCacheContexts.AUTHORIZATION_CODE), Mockito.anyString())).thenReturn(false);
+        Mockito.when(revocationCache.isRevoked(Mockito.matches(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS), Mockito.anyString())).thenReturn(true);
+        Mockito.when(revocationCache.revoke(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
         init(true, revocationCache, prc -> null);
-        Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS,
-                rfClaims.getID()));
         final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
         profileRequestCtx.getInboundMessageContext().setMessage(req);
         ActionTestingSupport.assertEvent(action.execute(requestCtx), IdPEventIds.INVALID_PROFILE_CONFIG);

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list