Skip to content
Closed
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 @@ -602,8 +602,24 @@ public AccessToken createClientAccessToken(KeycloakSession session, RealmModel r
return token;
}


public static ClientSessionContext attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession) {
// Retrieve client scopes from the "scope" parameter
Set<ClientScopeModel> clientScopes;
ClientModel client = authSession.getClient();
String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
session.getContext().setClient(client);
clientScopes = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, scopeParam)
.collect(Collectors.toSet());
} else {
clientScopes = getRequestedClientScopes(session, scopeParam, client, userSession.getUser())
.collect(Collectors.toSet());
}

return attachAuthenticationSession(session, userSession, authSession, clientScopes);
}

public static ClientSessionContext attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession, Set<ClientScopeModel> clientScopes) {
ClientModel client = authSession.getClient();

AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
Expand All @@ -618,18 +634,6 @@ public static ClientSessionContext attachAuthenticationSession(KeycloakSession s
clientSession.setRedirectUri(authSession.getRedirectUri());
clientSession.setProtocol(authSession.getProtocol());

String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
Set<ClientScopeModel> clientScopes;

if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
session.getContext().setClient(client);
clientScopes = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, scopeParam)
.collect(Collectors.toSet());
} else {
clientScopes = getRequestedClientScopes(session, scopeParam, client, userSession.getUser())
.collect(Collectors.toSet());
}

Map<String, String> transferredNotes = authSession.getClientNotes();
for (Map.Entry<String, String> entry : transferredNotes.entrySet()) {
clientSession.setNote(entry.getKey(), entry.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,17 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv

private static final Logger logger = Logger.getLogger(AbstractTokenExchangeProvider.class);

private TokenExchangeContext.Params params;
private MultivaluedMap<String, String> formParams;
private KeycloakSession session;
private Cors cors;
private RealmModel realm;
private ClientModel client;
private EventBuilder event;
private ClientConnection clientConnection;
private HttpHeaders headers;
private TokenManager tokenManager;
private Map<String, String> clientAuthAttributes;
protected TokenExchangeContext.Params params;
protected MultivaluedMap<String, String> formParams;
protected KeycloakSession session;
protected Cors cors;
protected RealmModel realm;
protected ClientModel client;
protected EventBuilder event;
protected ClientConnection clientConnection;
protected HttpHeaders headers;
protected TokenManager tokenManager;
protected Map<String, String> clientAuthAttributes;
protected TokenExchangeContext context;

@Override
Expand Down Expand Up @@ -282,46 +282,16 @@ protected Response exchangeClientToClient(UserModel targetUser, UserSessionModel
}
}

ClientModel targetClient = targetAudienceClients.get(0);
// TODO Remove once more audiences are properly supported
if (targetAudienceClients.size() > 1) {
logger.warnf("Only one value of audience parameter currently supported for token exchange. Using audience '%s' and ignoring the other audiences provided", targetClient.getClientId());
}

String scope = formParams.getFirst(OAuth2Constants.SCOPE);
if (token != null && token.getScope() != null && scope == null) {
scope = token.getScope();

Set<String> targetClientScopes = new HashSet<String>();
targetClientScopes.addAll(targetClient.getClientScopes(true).keySet());
targetClientScopes.addAll(targetClient.getClientScopes(false).keySet());
//from return scope remove scopes that are not default or optional scopes for targetClient
scope = Arrays.stream(scope.split(" ")).filter(s -> "openid".equals(s) || (targetClientScopes.contains(Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES) ? s.split(":")[0] : s))).collect(Collectors.joining(" "));
} else if (token != null && token.getScope() != null) {
String subjectTokenScopes = token.getScope();
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
Set<String> subjectTokenScopesSet = Arrays.stream(subjectTokenScopes.split(" ")).map(s -> s.split(":")[0]).collect(Collectors.toSet());
scope = Arrays.stream(scope.split(" ")).filter(sc -> subjectTokenScopesSet.contains(sc.split(":")[0])).collect(Collectors.joining(" "));
} else {
Set<String> subjectTokenScopesSet = Arrays.stream(subjectTokenScopes.split(" ")).collect(Collectors.toSet());
scope = Arrays.stream(scope.split(" ")).filter(sc -> subjectTokenScopesSet.contains(sc)).collect(Collectors.joining(" "));
}

Set<String> targetClientScopes = new HashSet<String>();
targetClientScopes.addAll(targetClient.getClientScopes(true).keySet());
targetClientScopes.addAll(targetClient.getClientScopes(false).keySet());
//from return scope remove scopes that are not default or optional scopes for targetClient
scope = Arrays.stream(scope.split(" ")).filter(s -> "openid".equals(s) || (targetClientScopes.contains(Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES) ? s.split(":")[0] : s))).collect(Collectors.joining(" "));
}
String scope = getRequestedScope(token, targetAudienceClients);

try {
session.getContext().setClient(targetClient);
setClientToContext(targetAudienceClients);
switch (requestedTokenType) {
case OAuth2Constants.ACCESS_TOKEN_TYPE:
case OAuth2Constants.REFRESH_TOKEN_TYPE:
return exchangeClientToOIDCClient(targetUser, targetUserSession, requestedTokenType, targetClient, scope);
return exchangeClientToOIDCClient(targetUser, targetUserSession, requestedTokenType, targetAudienceClients, scope);
case OAuth2Constants.SAML2_TOKEN_TYPE:
return exchangeClientToSAML2Client(targetUser, targetUserSession, requestedTokenType, targetClient);
return exchangeClientToSAML2Client(targetUser, targetUserSession, requestedTokenType, targetAudienceClients);
}
} finally {
session.getContext().setClient(client);
Expand All @@ -346,7 +316,7 @@ private void forbiddenIfClientIsNotTokenHolder(boolean disallowOnHolderOfTokenMi
}
}

private AuthenticationSessionModel createSessionModel(UserSessionModel targetUserSession, RootAuthenticationSessionModel rootAuthSession, UserModel targetUser, ClientModel client, String scope) {
protected 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);
Expand All @@ -355,8 +325,54 @@ private AuthenticationSessionModel createSessionModel(UserSessionModel targetUse
return authSession;
}

protected String getRequestedScope(AccessToken token, List<ClientModel> targetAudienceClients) {
ClientModel targetClient = targetAudienceClients.get(0);
// TODO Remove once more audiences are properly supported
if (targetAudienceClients.size() > 1) {
logger.warnf("Only one value of audience parameter currently supported for token exchange. Using audience '%s' and ignoring the other audiences provided", targetClient.getClientId());
}

String scope = formParams.getFirst(OAuth2Constants.SCOPE);
if (token != null && token.getScope() != null && scope == null) {
scope = token.getScope();

Set<String> targetClientScopes = new HashSet<String>();
targetClientScopes.addAll(targetClient.getClientScopes(true).keySet());
targetClientScopes.addAll(targetClient.getClientScopes(false).keySet());
//from return scope remove scopes that are not default or optional scopes for targetClient
scope = Arrays.stream(scope.split(" ")).filter(s -> "openid".equals(s) || (targetClientScopes.contains(Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES) ? s.split(":")[0] : s))).collect(Collectors.joining(" "));
} else if (token != null && token.getScope() != null) {
String subjectTokenScopes = token.getScope();
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
Set<String> subjectTokenScopesSet = Arrays.stream(subjectTokenScopes.split(" ")).map(s -> s.split(":")[0]).collect(Collectors.toSet());
scope = Arrays.stream(scope.split(" ")).filter(sc -> subjectTokenScopesSet.contains(sc.split(":")[0])).collect(Collectors.joining(" "));
} else {
Set<String> subjectTokenScopesSet = Arrays.stream(subjectTokenScopes.split(" ")).collect(Collectors.toSet());
scope = Arrays.stream(scope.split(" ")).filter(sc -> subjectTokenScopesSet.contains(sc)).collect(Collectors.joining(" "));
}

Set<String> targetClientScopes = new HashSet<String>();
targetClientScopes.addAll(targetClient.getClientScopes(true).keySet());
targetClientScopes.addAll(targetClient.getClientScopes(false).keySet());
//from return scope remove scopes that are not default or optional scopes for targetClient
scope = Arrays.stream(scope.split(" ")).filter(s -> "openid".equals(s) || (targetClientScopes.contains(Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES) ? s.split(":")[0] : s))).collect(Collectors.joining(" "));
}
return scope;
}

protected void setClientToContext(List<ClientModel> targetAudienceClients) {
ClientModel targetClient = getTargetClient(targetAudienceClients);
session.getContext().setClient(targetClient);
}

protected ClientModel getTargetClient(List<ClientModel> targetAudienceClients) {
// Make just first client into consideration TODO: Move this method to V1 only as V2 will properly support more audiences
return targetAudienceClients.get(0);
}

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

Expand Down Expand Up @@ -423,7 +439,9 @@ protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionM
return cors.add(Response.ok(res, MediaType.APPLICATION_JSON_TYPE));
}

protected Response exchangeClientToSAML2Client(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType, ClientModel targetClient) {
protected Response exchangeClientToSAML2Client(UserModel targetUser, UserSessionModel targetUserSession, String requestedTokenType, List<ClientModel> targetAudienceClients) {
ClientModel targetClient = getTargetClient(targetAudienceClients);

// Create authSession with target SAML 2.0 client and authenticated user
LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory()
.getProviderFactory(LoginProtocol.class, SamlProtocol.LOGIN_PROTOCOL);
Expand Down Expand Up @@ -627,7 +645,7 @@ protected UserModel importUserFromExternalIdentity(BrokeredIdentityContext conte
}

// TODO: move to utility class
private void updateUserSessionFromClientAuth(UserSessionModel userSession) {
protected void updateUserSessionFromClientAuth(UserSessionModel userSession) {
for (Map.Entry<String, String> attr : clientAuthAttributes.entrySet()) {
userSession.setNote(attr.getKey(), attr.getValue());
}
Expand Down
Loading