From f78a46485da8888e78e0919d3c4bb663fbaa49ac Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 1 Jul 2024 15:38:21 +0200 Subject: [PATCH] TE should create a transient session when there is no initial session in client-to-client exchange Closes #30614 Signed-off-by: rmartinc --- .../protocol/oidc/DefaultTokenExchangeProvider.java | 12 ++++++++---- .../testsuite/oauth/ClientTokenExchangeTest.java | 7 +++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java index 09b23ff3fe..03d9ca4edf 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java @@ -408,10 +408,9 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider { authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope); if (targetUserSession == null) { - // if no session is associated with a subject_token, a stateless session is created to only allow building a token to the audience + // if no session is associated with a subject_token, a transient session is created to only allow building a token to the audience targetUserSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, targetUser, targetUser.getUsername(), - clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); - + clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT); } event.session(targetUserSession); @@ -434,8 +433,13 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider { targetUserSession.setNote(IMPERSONATOR_CLIENT.toString(), client.getId()); } + if (targetUserSession.getPersistenceState() == UserSessionModel.SessionPersistenceState.TRANSIENT) { + responseBuilder.getAccessToken().setSessionId(null); + } + if (requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE) - && OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()) { + && OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken() + && targetUserSession.getPersistenceState() != UserSessionModel.SessionPersistenceState.TRANSIENT) { responseBuilder.generateRefreshToken(); responseBuilder.getRefreshToken().issuedFor(client.getClientId()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java index 0a473a4208..2c423f5a2d 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java @@ -298,6 +298,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { String accessToken = response.getAccessToken(); TokenVerifier accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class); AccessToken token = accessTokenVerifier.parse().getToken(); + Assert.assertNotNull(token.getSessionId()); Assert.assertEquals(token.getPreferredUsername(), "user"); assertTrue(token.getRealmAccess() == null || !token.getRealmAccess().isUserInRole("example")); @@ -306,6 +307,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken(); + Assert.assertEquals(token.getSessionId(), exchangedToken.getSessionId()); Assert.assertEquals("client-exchanger", exchangedToken.getIssuedFor()); Assert.assertEquals("target", exchangedToken.getAudience()[0]); Assert.assertEquals(exchangedToken.getPreferredUsername(), "user"); @@ -318,6 +320,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken(); + Assert.assertEquals(token.getSessionId(), exchangedToken.getSessionId()); Assert.assertEquals("legal", exchangedToken.getIssuedFor()); Assert.assertEquals("target", exchangedToken.getAudience()[0]); Assert.assertEquals(exchangedToken.getPreferredUsername(), "user"); @@ -338,12 +341,16 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { oauth.clientId("my-service-account"); OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("secret"); String accessToken = response.getAccessToken(); + TokenVerifier accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class); + AccessToken token = accessTokenVerifier.parse().getToken(); + Assert.assertNull(token.getSessionId()); { response = oauth.doTokenExchange(TEST, accessToken, "target", "my-service-account", "secret"); String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken(); + Assert.assertNull(exchangedToken.getSessionId()); Assert.assertEquals("my-service-account", exchangedToken.getIssuedFor()); Assert.assertEquals("target", exchangedToken.getAudience()[0]); Assert.assertEquals(exchangedToken.getPreferredUsername(), "service-account-my-service-account");