Create session for the requester client in Token Exchange (#31290)

Closes #31180


Signed-off-by: rmartinc <rmartinc@redhat.com>
Co-authored-by: Marek Posolda <mposolda@gmail.com>
This commit is contained in:
Ricardo Martin 2024-10-08 10:24:10 +02:00 committed by GitHub
parent 593afbb4e0
commit 611e6d102e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 5 deletions

View file

@ -396,15 +396,19 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
}
}
protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType,
ClientModel targetClient, String audience, String scope) {
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(targetClient);
private AuthenticationSessionModel createSessionModel(UserSessionModel targetUserSession, RootAuthenticationSessionModel rootAuthSession, UserModel targetUser, ClientModel client, String scope) {
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
authSession.setAuthenticatedUser(targetUser);
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
return authSession;
}
protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType,
ClientModel targetClient, String audience, String scope) {
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
AuthenticationSessionModel authSession = createSessionModel(targetUserSession, rootAuthSession, targetUser, targetClient, scope);
if (targetUserSession == null) {
// if no session is associated with a subject_token, a transient session is created to only allow building a token to the audience
@ -417,6 +421,12 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
AuthenticationManager.setClientScopesInSession(session, authSession);
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
if (!AuthenticationManager.isClientSessionValid(realm, client, targetUserSession, targetUserSession.getAuthenticatedClientSessionByClient(client.getId()))) {
// create the requester client session if needed
AuthenticationSessionModel clientAuthSession = createSessionModel(targetUserSession, rootAuthSession, targetUser, client, scope);
TokenManager.attachAuthenticationSession(this.session, targetUserSession, clientAuthSession);
}
updateUserSessionFromClientAuth(targetUserSession);
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, this.session, targetUserSession, clientSessionCtx)

View file

@ -1087,6 +1087,45 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
}
@Test
public void testExchangeForDifferentClient() throws Exception {
testingClient.server().run(ClientTokenExchangeTest::setupRealm);
// generate the first token for a public client
oauth.realm(TEST);
oauth.clientId("direct-public");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password");
String accessToken = response.getAccessToken();
TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
AccessToken token = accessTokenVerifier.parse().getToken();
Assert.assertEquals(token.getPreferredUsername(), "user");
assertTrue(token.getRealmAccess() == null || !token.getRealmAccess().isUserInRole("example"));
Assert.assertNotNull(token.getSessionId());
String sid = token.getSessionId();
// perform token exchange with client-exchanger simulating it received the previous token
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
accessToken = response.getAccessToken();
accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
token = accessTokenVerifier.parse().getToken();
Assert.assertEquals("client-exchanger", token.getIssuedFor());
Assert.assertEquals("target", token.getAudience()[0]);
Assert.assertEquals(token.getPreferredUsername(), "user");
Assert.assertEquals(sid, token.getSessionId());
// perform a second token exchange just to check everything is OK
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
accessToken = response.getAccessToken();
accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
token = accessTokenVerifier.parse().getToken();
Assert.assertEquals("client-exchanger", token.getIssuedFor());
Assert.assertEquals("target", token.getAudience()[0]);
Assert.assertEquals(token.getPreferredUsername(), "user");
Assert.assertEquals(sid, token.getSessionId());
}
private static void addDirectExchanger(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName(TEST);
RoleModel exampleRole = realm.addRole("example");