mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-17 04:24:48 -06:00
Remove FGAP:v1 from external-internal token exchange (#40938)
Closes #40855 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
committed by
GitHub
parent
cf0b3c542a
commit
2f36276ff0
@@ -323,7 +323,7 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv
|
||||
}
|
||||
|
||||
UserModel user = null;
|
||||
if (! context.getIdpConfig().isTransientUsers()) {
|
||||
if (!context.getIdpConfig().isTransientUsers()) {
|
||||
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(),
|
||||
context.getUsername(), context.getToken());
|
||||
|
||||
@@ -435,9 +435,9 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -49,4 +60,53 @@ public class ExternalToInternalTokenExchangeProvider extends V1TokenExchangeProv
|
||||
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 (getSupportedOAuthResponseTokenTypes().contains(requestedTokenType)) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -248,23 +248,26 @@ public class StandardTokenExchangeProvider extends AbstractTokenExchangeProvider
|
||||
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();
|
||||
|
||||
@@ -54,11 +54,7 @@ import org.keycloak.representations.AccessTokenResponse;
|
||||
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;
|
||||
@@ -84,8 +80,7 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
|
||||
*
|
||||
* @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
|
||||
@@ -152,17 +147,6 @@ public class ExternalInternalTokenExchangeV2Test extends AbstractInitializedBase
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user