[java-idp-oidc] branch main updated: JOIDC-90 - Revocation of individual tokens

Henri Mikkonen henri.mikkonen at iki.fi
Mon Jun 27 12:15:29 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=eacef6053533d411174b3a931cb57380c8ef98ef

The following commit(s) were added to refs/heads/main by this push:
     new eacef605 JOIDC-90 - Revocation of individual tokens
eacef605 is described below

commit eacef6053533d411174b3a931cb57380c8ef98ef
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Jun 27 15:13:18 2022 +0300

    JOIDC-90 - Revocation of individual tokens
    
    https://shibboleth.atlassian.net/browse/JOIDC-90
    
    After a code review decided to change the revocation lifetime logic when revoking a single token:
    in those cases the revocation lifetime is taken from the token expiration time.
    Affected SWF actions: RevokeToken and SetRefreshTokenToResponseContext
    
    Also added global configuration property idp.oauth2.revocationMethod and wired it to the revocation
    configuration. Config was also missing wiring of idp.oidc.revocationCache.authorizeCode.lifetime.
---
 .../oidc/op/oauth2/profile/impl/RevokeToken.java   | 56 ++++++++++++------
 .../impl/SetRefreshTokenToResponseContext.java     | 23 +++++++-
 ...faultTokenRevocationLifetimeLookupStrategy.java | 66 ++++++++++++++++++++++
 .../idp/service/relying-party/postconfig.xml       | 16 +++++-
 .../idp/plugin/oidc/op/conf/oidc.properties        |  5 +-
 .../op/oauth2/profile/impl/RevokeTokenTest.java    | 27 ++++++++-
 .../impl/SetRefreshTokenToResponseContextTest.java | 28 ++++++++-
 7 files changed, 197 insertions(+), 24 deletions(-)

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 fb7df393..65aed21e 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
@@ -34,6 +34,7 @@ import com.nimbusds.jwt.JWTClaimsSet;
 
 import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultRootTokenIdentifierLookupStrategy;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultTokenRevocationLifetimeLookupStrategy;
 import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
@@ -71,8 +72,11 @@ public class RevokeToken extends AbstractProfileAction {
      */
     @Nonnull private Function<ProfileRequestContext,OAuth2TokenRevocationMethod> revocationMethodLookupStrategy;
 
-    /** Lookup function to supply revocation lifetime. */
-    @Nonnull private Function<ProfileRequestContext,Duration> revocationLifetimeLookupStrategy;
+    /** Lookup function to supply chain revocation lifetime. */
+    @Nonnull private Function<ProfileRequestContext,Duration> chainRevocationLifetimeLookupStrategy;
+
+    /** Lookup function to supply token revocation lifetime. */
+    @Nonnull private Function<JWTClaimsSet,Duration> tokenRevocationLifetimeLookupStrategy;
 
     /** Lookup function to supply root token identifier. */
     @Nonnull private Function<JWTClaimsSet,String> rootTokenIdentifierLookupStrategy;
@@ -83,12 +87,16 @@ public class RevokeToken extends AbstractProfileAction {
     /** Revocation lifetime to use. */
     private Duration revocationLifetime;
 
+    /** The claims set to operate on. */
+    private JWTClaimsSet claimsSet;
+
     /**
      * Constructor.
      */
     public RevokeToken() {
         revocationMethodLookupStrategy = new RevocationMethodLookupFunction();
-        revocationLifetimeLookupStrategy = new RevocationLifetimeLookupFunction();
+        chainRevocationLifetimeLookupStrategy = new RevocationLifetimeLookupFunction();
+        tokenRevocationLifetimeLookupStrategy = new DefaultTokenRevocationLifetimeLookupStrategy();
         rootTokenIdentifierLookupStrategy = new DefaultRootTokenIdentifierLookupStrategy();
     }
 
@@ -113,13 +121,23 @@ public class RevokeToken extends AbstractProfileAction {
     }
 
     /**
-     * Set a lookup strategy for the revocation lifetime.
+     * Set a lookup strategy for the chain revocation lifetime.
      *
      * @param strategy What to set.
      */
-    public void setRevocationLifetimeLookupStrategy(
+    public void setChainRevocationLifetimeLookupStrategy(
             @Nullable final Function<ProfileRequestContext,Duration> strategy) {
-        revocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+        chainRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /**
+     * Set a lookup strategy for the token revocation lifetime.
+     *
+     * @param strategy What to set.
+     */
+    public void setTokenRevocationLifetimeLookupStrategy(
+            @Nullable final Function<JWTClaimsSet,Duration> strategy) {
+        tokenRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
     /**
@@ -155,8 +173,21 @@ public class RevokeToken extends AbstractProfileAction {
             return false;            
         }
 
-        revocationLifetime = revocationLifetimeLookupStrategy.apply(profileRequestContext);
-        if (revocationLifetime == null) {
+        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 false;
+        }
+        claimsSet = ctx.getTokenClaimsSet();
+
+        if (OAuth2TokenRevocationMethod.CHAIN.equals(revocationMethod)) {
+            revocationLifetime = chainRevocationLifetimeLookupStrategy.apply(profileRequestContext);
+        } else if (OAuth2TokenRevocationMethod.TOKEN.equals(revocationMethod)) {
+            revocationLifetime = tokenRevocationLifetimeLookupStrategy.apply(claimsSet);
+        }
+
+        if (revocationLifetime == null || Duration.ZERO.equals(revocationLifetime)) {
             log.error("{} Unable to obtain revocation lifetime to use", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
             return false;
@@ -167,15 +198,6 @@ public class RevokeToken extends AbstractProfileAction {
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        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;
-        }
-        
-        final JWTClaimsSet claimsSet = ctx.getTokenClaimsSet();
         final String jti = claimsSet.getJWTID(); 
         if (jti == null) {
             log.error("{} No ID found in token claims set (this should be impossible)", getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
index b3e151cd..2ce9ebd8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
@@ -38,6 +38,7 @@ import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultTokenRevocationLifetimeLookupStrategy;
 import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
@@ -94,6 +95,9 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
     /** Strategy used to determine whether to revoke refresh tokens once they're used. */
     @Nonnull private Predicate<ProfileRequestContext> enforceRefreshTokenRotationCondition;
 
+    /** Lookup function to supply token revocation lifetime. */
+    @Nonnull private Function<JWTClaimsSet,Duration> tokenRevocationLifetimeLookupStrategy;
+
     /** Authorize Code / Refresh Token the refresh token will be based on. */
     @Nullable private TokenClaimsSet tokenClaimsSet;
     
@@ -115,6 +119,7 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
                 new RefreshTokenClaimsSetManipulationStrategyLookupFunction();
         idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
         enforceRefreshTokenRotationCondition = new EnforceRefreshTokenRotationPredicate();
+        tokenRevocationLifetimeLookupStrategy = new DefaultTokenRevocationLifetimeLookupStrategy();
     }
 
     /**
@@ -178,6 +183,16 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
         enforceRefreshTokenRotationCondition = Constraint.isNotNull(condition, "Condition cannot be null");
     }
 
+    /**
+     * Set a lookup strategy for the token revocation lifetime.
+     *
+     * @param strategy What to set.
+     */
+    public void setTokenRevocationLifetimeLookupStrategy(
+            @Nullable final Function<JWTClaimsSet,Duration> strategy) {
+        tokenRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -274,8 +289,14 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
         if (enforceRefreshTokenRotationCondition.test(profileRequestContext) && 
                 tokenClaimsSet instanceof RefreshTokenClaimsSet) {
             final String jti = tokenClaimsSet.getID();
+            final Duration lifetime = tokenRevocationLifetimeLookupStrategy.apply(tokenClaimsSet.getClaimsSet());
+            if (lifetime == null || Duration.ZERO.equals(lifetime)) {
+                log.error("{} Unable to fetch lifetime for the single token revocation", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+                return;                
+            }
             log.debug("{} Revoking the refresh token {} used for issuing the new one", getLogPrefix(), jti);
-            if (!revocationCache.revoke(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, jti)) {
+            if (!revocationCache.revoke(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, jti, lifetime)) {
                 log.error("{} Unable to store revocation into the revocation cache", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
                 return;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultTokenRevocationLifetimeLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultTokenRevocationLifetimeLookupStrategy.java
new file mode 100644
index 00000000..66ba4f52
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultTokenRevocationLifetimeLookupStrategy.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.logic;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+/**
+ * Default lookup function for fetching the token revocation lifetime from the given claims set. If an expiration
+ * time is found from the claims set, a difference between now and it is returned. If the expiration time is in the
+ * past, a {@link Duration#ZERO} is returned. If no expiration time is found, null is returned.
+ */
+public class DefaultTokenRevocationLifetimeLookupStrategy implements Function<JWTClaimsSet, Duration> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultTokenRevocationLifetimeLookupStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public Duration apply(@Nullable final JWTClaimsSet claimsSet) {
+        if (claimsSet == null) {
+            log.error("The given claims set was null, returning null");
+            return null;
+        }
+        final Date expiration = claimsSet.getExpirationTime();
+        if (expiration == null) {
+            log.debug("No token expiration time found from the claims set, returning null");
+            return null;
+        }
+        final Instant now = Instant.now();
+        final Instant exp = expiration.toInstant();
+        if (now.isAfter(exp)) {
+            log.debug("Token expiration time was in the past, returning ZERO");
+            return Duration.ZERO;
+        }
+        return Duration.between(now, exp);
+    }
+    
+}
\ 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 c0ffe0ab..33144287 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
@@ -85,7 +85,9 @@
         p:issuer-ref="shibboleth.oidc.issuer"
         p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
         p:claimsValidator-ref="DefaultJWTClaimsValidator"
-        p:issuedClaimsValidator-ref="DefaultRevocationJWTClaimsValidator" />        
+        p:issuedClaimsValidator-ref="DefaultRevocationJWTClaimsValidator"
+        p:revocationMethod="%{idp.oauth2.revocationMethod:CHAIN}"
+        p:revocationLifetime="%{idp.oidc.revocationCache.authorizeCode.lifetime:PT6H}"/>
 
     <!-- Metadata-driven variants. -->
     
@@ -486,8 +488,20 @@
                 p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
                 p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
         </property>
+        <property name="revocationMethodLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="revocationMethod"
+                p:propertyType="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration$OAuth2TokenRevocationMethod)}"
+                p:defaultValue-ref="RevocationMethod.%{idp.oauth2.revocationMethod:CHAIN}" />
+        </property>
+        <property name="revocationLifetimeLookupStrategy">
+            <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="revocationLifetime"
+                p:defaultValue="%{idp.oidc.revocationCache.authorizeCode.lifetime:PT6H}" />
+        </property>
     </bean>
 
+    <util:constant id="RevocationMethod.CHAIN" static-field="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration$OAuth2TokenRevocationMethod.CHAIN" />
+    <util:constant id="RevocationMethod.TOKEN" static-field="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration$OAuth2TokenRevocationMethod.TOKEN" />
+
     <!-- Default client-auth JWT validation wiring. -->
 
     <bean id="AdaptedRelyingPartyIdLookup" class="net.shibboleth.utilities.java.support.logic.BiFunctionSupport"
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index 49699e11..1df268c2 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -135,4 +135,7 @@ idp.oidc.subject.salt = this_too_should_be_ch4ng3d
 #idp.oauth2.authn.flows = OAuth2Client
 
 # Set true to enforce refresh token rotation (defaults to false)
-#idp.oauth2.enforceRefreshTokenRotation = true
\ No newline at end of file
+#idp.oauth2.enforceRefreshTokenRotation = true
+
+# Revocation method: set to TOKEN to revoke single tokens (defaults to full chain (value = CHAIN))
+#idp.oauth2.revocationMethod = TOKEN
\ No newline at end of file
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 6fed64f8..8e95531d 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
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
 import java.time.Duration;
+import java.time.Instant;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.storage.RevocationCache;
@@ -33,6 +34,7 @@ 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.IdPEventIds;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
@@ -61,8 +63,13 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
     private ProfileRequestContext prc;
     
     private OAuth2TokenMgmtResponseContext tokenCtx;
-    
+
     protected void setUp(final OAuth2TokenRevocationMethod method, final String rootTokenId) throws Exception {
+        setUp(method, rootTokenId, exp);
+    }
+
+    protected void setUp(final OAuth2TokenRevocationMethod method, final String rootTokenId, final Instant exp)
+            throws Exception {
 
         storageService = new MemoryStorageService();
         storageService.setId("test");
@@ -98,7 +105,7 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
         action.setRevocationCache(revocationCache);
         action.setRevocationMethodLookupStrategy(
                 prc -> method);
-        action.setRevocationLifetimeLookupStrategy(prc -> Duration.ofHours(1));
+        action.setChainRevocationLifetimeLookupStrategy(prc -> Duration.ofHours(1));
         action.initialize();
 
         src = new RequestContextBuilder().buildRequestContext();
@@ -182,7 +189,21 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
         setUp(OAuth2TokenRevocationMethod.TOKEN, null);
         ActionTestingSupport.assertProceedEvent(action.execute(src));
     }
-    
+
+    @Test
+    public void testSingleToken_ExpiredAccessToken() throws Exception {
+        setUp(OAuth2TokenRevocationMethod.TOKEN, null, Instant.now().minus(Duration.ofMinutes(10)));
+        tokenCtx.setTokenClaimsSet(atClaimsSet.getClaimsSet());
+        ActionTestingSupport.assertEvent(action.execute(src), IdPEventIds.INVALID_PROFILE_CONFIG);
+    }
+
+    @Test
+    public void testSingleToken_ExpiredRefreshToken() throws Exception {
+        setUp(OAuth2TokenRevocationMethod.TOKEN, null, Instant.now().minus(Duration.ofMinutes(10)));
+        tokenCtx.setTokenClaimsSet(rfClaimsSet.getClaimsSet());
+        ActionTestingSupport.assertEvent(action.execute(src), IdPEventIds.INVALID_PROFILE_CONFIG);
+    }
+
     @Test
     public void testSingleToken_RevokeAccessToken() throws Exception {
         setUp(OAuth2TokenRevocationMethod.TOKEN, null);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContextTest.java
index 405e3d8e..99362102 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContextTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContextTest.java
@@ -33,6 +33,7 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
 import java.text.ParseException;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Map;
 import java.util.function.BiFunction;
@@ -217,7 +218,7 @@ public class SetRefreshTokenToResponseContextTest extends BaseOIDCResponseAction
                 .setPrincipal("userPrin")
                 .setSubject("subject")
                 .setIssuedAt(Instant.now())
-                .setExpiresAt(Instant.now())
+                .setExpiresAt(Instant.now().plus(Duration.ofHours(1)))
                 .setAuthenticationTime(Instant.now())
                 .setRedirectURI(new URI("http://example.com"))
                 .setScope(new Scope())
@@ -238,6 +239,31 @@ public class SetRefreshTokenToResponseContextTest extends BaseOIDCResponseAction
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, jit));
     }
 
+    @Test
+    public void testFailWithExpiredRefreshRotationEnforced() throws ComponentInitializationException, NoSuchAlgorithmException, URISyntaxException,
+            ParseException, DataSealerException {
+        final String rootTokenId = new SecureRandomIdentifierGenerationStrategy().generateIdentifier();
+        final TokenClaimsSet claims = new RefreshTokenClaimsSet.Builder()
+                .setJWTID(idGenerator)
+                .setClientID(new ClientID())
+                .setIssuer("issuer")
+                .setPrincipal("userPrin")
+                .setSubject("subject")
+                .setIssuedAt(Instant.now())
+                .setExpiresAt(Instant.now())
+                .setAuthenticationTime(Instant.now())
+                .setRedirectURI(new URI("http://example.com"))
+                .setScope(new Scope())
+                .setACR(new ACR("0"))
+                .setRootTokenIdentifier(rootTokenId)
+                .build();
+        final String jit = claims.getID();
+        respCtx.setAuthorizationGrantClaimsSet(claims);
+        enforceRotation = true;
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, IdPEventIds.INVALID_PROFILE_CONFIG);
+    }
+
     /**
      * There is no offline_access scope.
      * 

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


More information about the commits mailing list