Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public interface Details {
String REASON = "reason";
String GRANTED_CLIENT = "granted_client";
String REVOKED_CLIENT = "revoked_client";
String TOKEN_EXCHANGE_REVOKED_CLIENTS = "token_exchange_revoked_clients";
String AUDIENCE = "audience";
String PERMISSION = "permission";
String SCOPE = "scope";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,6 @@ public final class Constants {
public static final String REQUESTED_AUDIENCE = "req-aud";
// Note in clientSessionContext specifying token grant type used
public static final String GRANT_TYPE = OAuth2Constants.GRANT_TYPE;

// Note in client session to know the subject client
public static final String TOKEN_EXCHANGE_SUBJECT_CLIENT = "token_exchange_subject_client";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

package org.keycloak.protocol.oidc.endpoints;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.OPTIONS;
Expand All @@ -37,6 +40,7 @@
import org.keycloak.http.HttpRequest;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider;
Expand Down Expand Up @@ -256,6 +260,9 @@ private void revokeClientSession() {
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession != null) {
TokenManager.dettachClientSession(clientSession);

revokeTokenExchangeSession(userSession);

// TODO: Might need optimization to prevent loading client sessions from cache in getAuthenticatedClientSessions()
if (userSession.getAuthenticatedClientSessions().isEmpty()) {
session.sessions().removeUserSession(realm, userSession);
Expand All @@ -269,5 +276,29 @@ private void revokeAccessToken() {
int currentTime = Time.currentTime();
long lifespanInSecs = Math.max(token.getExp() - currentTime + 1, 10);
singleUseStore.put(token.getId() + SingleUseObjectProvider.REVOKED_KEY, lifespanInSecs, Collections.emptyMap());
revokeTokenExchangeSession();
}

private void revokeTokenExchangeSession() {
if (token.getSessionId() != null) {
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionId());
if (userSession != null) {
revokeTokenExchangeSession(userSession);
}
}
}

private void revokeTokenExchangeSession(UserSessionModel userSession) {
Map<String, AuthenticatedClientSessionModel> clientSessionModelMap = userSession.getAuthenticatedClientSessions();
List<String> revokedClients = new ArrayList<>();
clientSessionModelMap.forEach((key, clientSessionModel) -> {
if (clientSessionModel.getNote(Constants.TOKEN_EXCHANGE_SUBJECT_CLIENT + token.getIssuedFor()) != null) {
revokedClients.add(clientSessionModel.getClient().getClientId());
TokenManager.dettachClientSession(clientSessionModel);
}
});
if (!revokedClients.isEmpty()) {
event.detail(Details.TOKEN_EXCHANGE_REVOKED_CLIENTS, String.join(",", revokedClients));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ protected Response exchangeClientToClient(UserModel targetUser, UserSessionModel
try {
setClientToContext(targetAudienceClients);
if (getSupportedOAuthResponseTokenTypes().contains(requestedTokenType))
return exchangeClientToOIDCClient(targetUser, targetUserSession, requestedTokenType, targetAudienceClients, scope);
return exchangeClientToOIDCClient(targetUser, targetUserSession, requestedTokenType, targetAudienceClients, scope, token);
else if (OAuth2Constants.SAML2_TOKEN_TYPE.equals(requestedTokenType)) {
return exchangeClientToSAML2Client(targetUser, targetUserSession, requestedTokenType, targetAudienceClients);
}
Expand Down Expand Up @@ -383,7 +383,7 @@ protected ClientModel getTargetClient(List<ClientModel> targetAudienceClients) {
}

protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType,
List<ClientModel> targetAudienceClients, String scope) {
List<ClientModel> targetAudienceClients, String scope, AccessToken subjectToken) {
ClientModel targetClient = getTargetClient(targetAudienceClients);
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
AuthenticationSessionModel authSession = createSessionModel(targetUserSession, rootAuthSession, targetUser, targetClient, scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,26 @@
import java.util.StringJoiner;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.TokenExchangeContext;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.encode.AccessTokenContext;
import org.keycloak.protocol.oidc.encode.TokenContextEncoderProvider;
import org.keycloak.rar.AuthorizationRequestContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.cors.Cors;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.UserSessionManager;
Expand Down Expand Up @@ -206,7 +204,7 @@ protected void setClientToContext(List<ClientModel> targetAudienceClients) {

@Override
protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType,
List<ClientModel> targetAudienceClients, String scope) {
List<ClientModel> targetAudienceClients, String scope, AccessToken subjectToken) {
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
AuthenticationSessionModel authSession = createSessionModel(targetUserSession, rootAuthSession, targetUser, client, scope);

Expand Down Expand Up @@ -248,6 +246,25 @@ protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionM
validateConsents(targetUser, clientSessionCtx);
clientSessionCtx.setAttribute(Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);

TokenContextEncoderProvider encoder = session.getProvider(TokenContextEncoderProvider.class);
AccessTokenContext subjectTokenContext = encoder.getTokenContextFromTokenId(subjectToken.getId());

//copy subject client from the client session notes if the subject token used has already been exchanged
if (OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE.equals(subjectTokenContext.getGrantType())) {
ClientModel subjectClient = session.clients().getClientByClientId(realm, subjectToken.getIssuedFor());
if (subjectClient != null) {
AuthenticatedClientSessionModel subjectClientSession = targetUserSession.getAuthenticatedClientSessionByClient(subjectClient.getId());
if (subjectClientSession != null) {
subjectClientSession.getNotes().entrySet().stream()
.filter(note -> note.getKey().startsWith(Constants.TOKEN_EXCHANGE_SUBJECT_CLIENT))
.forEach(note -> clientSessionCtx.getClientSession().setNote(note.getKey(), note.getValue()));
}
}
}

//store client id of the subject token
clientSessionCtx.getClientSession().setNote(Constants.TOKEN_EXCHANGE_SUBJECT_CLIENT + subjectToken.getIssuedFor(), subjectToken.getId());

TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, client, event, session,
clientSessionCtx.getClientSession().getUserSession(), clientSessionCtx).generateAccessToken();

Expand Down
Loading
Loading