8000 Remove FGAP:v1 from external-internal token exchange by graziang · Pull Request #40938 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Remove FGAP:v1 from external-internal token exchange #40938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 @@ -323,7 +323,7 @@ protected UserModel importUserFromExternalIdentity(BrokeredIdentityContext conte
}

UserModel user = null;
if (! context.getIdpConfig().isTransientUsers()) {
if (!context.getIdpConfig().isTransientUsers()) {
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(),
context.getUsername(), context.getToken());

Expand Down Expand Up @@ -435,9 +435,9 @@ protected void updateUserSessionFromClientAuth(UserSessionModel userSession) {
}
}

record ExternalExchangeContext (ExchangeExternalToken provider, IdentityProviderModel idpModel) {};
protected record ExternalExchangeContext (ExchangeExternalToken provider, IdentityProviderModel idpModel) {};

private ExternalExchangeContext locateExchangeExternalTokenByAlias(String alias) {
protected ExternalExchangeContext locateExchangeExternalTokenByAlias(String alias) {
try {
IdentityProvider<?> idp = IdentityBrokerService.getIdentityProvider(session, alias);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,27 @@
package org.keycloak.protocol.oidc.tokenexchange;

import jakarta.ws.rs.core.Response;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.TokenExchangeContext;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.managers.UserSessionManager;

import java.util.Arrays;
import java.util.List;

/**
* Provider for external-internal token exchange
*
* TODO Should not extend from V1TokenExchangeProvider, but rather AbstractTokenExchangeProvider or from StandardTokenExchangeProvider (as issuing internal tokens might be done in a same/similar way like for standard V2 provider)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExternalToInternalTokenExchangeProvider extends V1TokenExchangeProvider {
public class ExternalToInternalTokenExchangeProvider extends StandardTokenExchangeProvider {

@Override
public boolean supports(TokenExchangeContext context) {
Expand All @@ -49,4 +60,55 @@ protected Response tokenExchange() {
return exchangeExternalToken(subjectIssuer, subjectToken);
}

@Override
protected List<String> getSupportedOAuthResponseTokenTypes() {
return Arrays.asList(OAuth2Constants.ACCESS_TOKEN_TYPE, OAuth2Constants.ID_TOKEN_TYPE);
}

@Override
protected String getRequestedTokenType() {
String requestedTokenType = params.getRequestedTokenType();
if (requestedTokenType == null) {
requestedTokenType = OAuth2Constants.ACCESS_TOKEN_TYPE;
return requestedTokenType;
}
if (requestedTokenType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)
|| requestedTokenType.equals(OAuth2Constants.ID_TOKEN_TYPE)
|| requestedTokenType.equals(OAuth2Constants.SAML2_TOKEN_TYPE)) {
return requestedTokenType;
}

event.detail(Details.REASON, "requested_token_type unsupported");
event.error(Errors.INVALID_REQUEST);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "requested_token_type unsupported", Response.Status.BAD_REQUEST);
}

protected Response exchangeExternalToken(String subjectIssuer, String subjectToken) {
// try to find the IDP whose alias matches the issuer or the subject issuer in the form params.
ExternalExchangeContext externalExchangeContext = this.locateExchangeExternalTokenByAlias(subjectIssuer);

if (externalExchangeContext == null) {
event.error(Errors.INVALID_ISSUER);
throw new CorsErrorResponseException(cors, Errors.INVALID_ISSUER, "Invalid " + OAuth2Constants.SUBJECT_ISSUER + " parameter", Response.Status.BAD_REQUEST);
}
BrokeredIdentityContext context = externalExchangeContext.provider().exchangeExternal(this, this.context);
if (context == null) {
event.error(Errors.INVALID_ISSUER);
throw new CorsErrorResponseException(cors, Errors.INVALID_ISSUER, "Invalid " + OAuth2Constants.SUBJECT_ISSUER + " parameter", Response.Status.BAD_REQUEST);
}

UserModel user = importUserFromExternalIdentity(context);

UserSessionModel userSession = new UserSessionManager(session).createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteHost(), "external-exchange", false, null, null);
externalExchangeContext.provider().exchangeExternalComplete(userSession, context, formParams);

// this must exist so that we can obtain access token from user session if idp's store tokens is off
userSession.setNote(IdentityProvider.EXTERNAL_IDENTITY_PROVIDER, externalExchangeContext.idpModel().getAlias());
userSession.setNote(IdentityProvider.FEDERATED_ACCESS_TOKEN, subjectToken);

context.addSessionNotesToUserSession(userSession);

return exchangeClientToClient(user, userSession, null, false);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -248,23 +248,26 @@ protected Response exchangeClientToOIDCClient(UserModel targetUser, UserSessionM
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()));

if (subjectToken != null) {
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());
//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
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,7 @@
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.services.resources.admin.fgap.AdminPermissionManagement;
import org.keycloak.services.resources.admin.fgap.AdminPermissions;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeatures;
import org.keycloak.testsuite.broker.AbstractInitializedBaseBrokerTest;
import org.keycloak.testsuite.broker.BrokerConfiguration;
import org.keycloak.testsuite.broker.BrokerTestConstants;
Expand All @@ -84,8 +80,7 @@
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
// TODO: Remove fine grained admin permissions should not be needed. They are neded now for token_exchange_external_internal:v2, but should not be needed in the future
@EnableFeatures({@EnableFeature(Profile.Feature.TOKEN_EXCHANGE_EXTERNAL_INTERNAL_V2), @EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)})
@EnableFeature(Profile.Feature.TOKEN_EXCHANGE_EXTERNAL_INTERNAL_V2)
public class ExternalInternalTokenExchangeV2Test extends AbstractInitializedBaseBrokerTest {

@Override
Expand Down Expand Up @@ -152,17 +147,6 @@ private static void setupRealm(KeycloakSession session) {
client.setFullScopeAllowed(false);
client.setRedirectUris(Set.of(OAuthClient.AUTH_SERVER_ROOT + "/*"));

ClientModel brokerApp = realm.getClientByClientId("broker-app");

AdminPermissionManagement management = AdminPermissions.management(session, realm);
management.idps().setPermissionsEnabled(idp, true);
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
clientRep.setName("toIdp");
clientRep.addClient(client.getId(), brokerApp.getId());
ResourceServer server = management.realmResourceServer();
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(server, clientRep);
management.idps().exchangeToPermission(idp).addAssociatedPolicy(clientPolicy);

realm = session.realms().getRealmByName(BrokerTestConstants.REALM_PROV_NAME);
client = realm.getClientByClientId("brokerapp");
client.addRedirectUri(OAuthClient.APP_ROOT + "/auth");
Expand Down
Loading
0