From 6e471a84773a6952a8cb3d87efcb5d61ac643ecd Mon Sep 17 00:00:00 2001 From: Ricardo Martin Date: Wed, 2 Oct 2024 09:44:25 +0200 Subject: [PATCH] Add the nonce attribute when the client session context is recreated (#33422) Closes #33355 Signed-off-by: rmartinc Co-authored-by: Tomas Kralik --- .../keycloak/protocol/oidc/TokenManager.java | 7 +++-- .../NonceBackwardsCompatibleMapperTest.java | 31 ++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 108f68c4a1..3f8d95dfaf 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -99,7 +99,6 @@ import org.keycloak.util.TokenUtil; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1180,8 +1179,12 @@ public class TokenManager { String scope = oldRefreshToken.getScope(); Object reuseId = oldRefreshToken.getOtherClaims().get(Constants.REUSE_ID); boolean offlineTokenRequested = Arrays.asList(scope.split(" ")).contains(OAuth2Constants.OFFLINE_ACCESS) ; - if (offlineTokenRequested) + if (offlineTokenRequested) { clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scope, session); + if (oldRefreshToken.getNonce() != null) { + clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, oldRefreshToken.getNonce()); + } + } generateRefreshToken(offlineTokenRequested); if (realm.isRevokeRefreshToken()) { refreshToken.getOtherClaims().put(Constants.REUSE_ID, reuseId); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/NonceBackwardsCompatibleMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/NonceBackwardsCompatibleMapperTest.java index 1f68b50e50..0c1504559d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/NonceBackwardsCompatibleMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/NonceBackwardsCompatibleMapperTest.java @@ -46,6 +46,7 @@ import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.util.TokenUtil; /** * @@ -62,7 +63,7 @@ public class NonceBackwardsCompatibleMapperTest extends AbstractTestRealmKeycloa @Test public void testNonceWithoutMapper() throws JsonProcessingException { - testNonce(false); + testNonce(false, false); } @Test @@ -70,7 +71,18 @@ public class NonceBackwardsCompatibleMapperTest extends AbstractTestRealmKeycloa ClientResource testApp = ApiUtil.findClientByClientId(testRealm(), "test-app"); String mapperId = createNonceMapper(testApp); try { - testNonce(true); + testNonce(true, false); + } finally { + testApp.getProtocolMappers().delete(mapperId); + } + } + + @Test + public void testOfflineSessionNonceWithMapper() throws JsonProcessingException { + ClientResource testApp = ApiUtil.findClientByClientId(testRealm(), "test-app"); + String mapperId = createNonceMapper(testApp); + try { + testNonce(true, true); } finally { testApp.getProtocolMappers().delete(mapperId); } @@ -142,9 +154,12 @@ public class NonceBackwardsCompatibleMapperTest extends AbstractTestRealmKeycloa testIntrospection(idTokenString, nonce, true); } - private void testNonce(boolean mapper) throws JsonProcessingException { + private void testNonce(boolean mapper, boolean offlineSession) throws JsonProcessingException { String nonce = KeycloakModelUtils.generateId(); oauth.nonce(nonce); + if (offlineSession) { + oauth.scope(OAuth2Constants.OFFLINE_ACCESS); + } oauth.doLogin("test-user@localhost", "password"); EventRepresentation loginEvent = events.expectLogin().assertEvent(); @@ -158,12 +173,14 @@ public class NonceBackwardsCompatibleMapperTest extends AbstractTestRealmKeycloa RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken()); checkNonce(nonce, refreshToken.getNonce(), mapper); - EventRepresentation tokenEvent = events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), - loginEvent.getSessionId()).assertEvent(); + EventRepresentation tokenEvent = events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()) + .detail(Details.REFRESH_TOKEN_TYPE, offlineSession? TokenUtil.TOKEN_TYPE_OFFLINE : TokenUtil.TOKEN_TYPE_REFRESH) + .assertEvent(); response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password"); - events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), - loginEvent.getSessionId()).assertEvent(); + events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), loginEvent.getSessionId()) + .detail(Details.REFRESH_TOKEN_TYPE, offlineSession? TokenUtil.TOKEN_TYPE_OFFLINE : TokenUtil.TOKEN_TYPE_REFRESH) + .assertEvent(); token = oauth.verifyToken(response.getAccessToken()); checkNonce(nonce, token.getNonce(), mapper);