Extract related methods from IdentityProvider to UserIdentityProvider (#43535)

Closes #43534

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-10-21 16:27:07 +02:00 committed by GitHub
parent 6080f21c64
commit 84a161d4dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 182 additions and 234 deletions

View File

@ -21,6 +21,16 @@ This prevents problems with client IDs or passwords that contain, for example, a
To revert to the old behavior, change the client authentication to the deprecated option *Client secret sent as HTTP Basic authentication without URL encoding* (`client_secret_basic_unencoded`).
=== Identity Provider refactoring
The private SPI for identity providers have been refactored. This is to allow identity providers to support more use
-cases than federated user authentication.
For anyone implementing a custom federated user authentication identity provider and are not extending one provided
by Keycloak or `AbstractIdentityProvider` you need to update your implementation to implement
the new `UserAuthenticationIdentityProvider` interface (all methods remain the same, they have just been moved).
// ------------------------ Notable changes ------------------------ //
== Notable changes

View File

@ -43,7 +43,7 @@ import java.util.UUID;
/**
* @author Pedro Igor
*/
public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> implements IdentityProvider<C> {
public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> implements UserAuthenticationIdentityProvider<C> {
protected static final Logger logger = Logger.getLogger(AbstractIdentityProvider.class);
@ -67,11 +67,6 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
return this.config;
}
@Override
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
return Response.noContent().build();
}
@Override
public void close() {
// no-op

View File

@ -48,7 +48,7 @@ public class BrokeredIdentityContext {
private String brokerUserId;
private String token;
private IdentityProviderModel idpConfig;
private IdentityProvider idp;
private UserAuthenticationIdentityProvider<?> idp;
private Map<String, Object> contextData = new HashMap<>();
private AuthenticationSessionModel authenticationSession;
@ -152,11 +152,11 @@ public class BrokeredIdentityContext {
return idpConfig;
}
public IdentityProvider getIdp() {
public UserAuthenticationIdentityProvider<?> getIdp() {
return idp;
}
public void setIdp(IdentityProvider idp) {
public void setIdp(UserAuthenticationIdentityProvider<?> idp) {
this.idp = idp;
}

View File

@ -1,8 +1,9 @@
package org.keycloak.broker.provider;
import org.keycloak.authentication.ClientAuthenticationFlowContext;
import org.keycloak.models.IdentityProviderModel;
public interface ClientAssertionIdentityProvider {
public interface ClientAssertionIdentityProvider<C extends IdentityProviderModel> extends IdentityProvider<C> {
boolean verifyClientAssertion(ClientAuthenticationFlowContext context) throws Exception;

View File

@ -16,15 +16,9 @@
*/
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.Provider;
import org.keycloak.sessions.AuthenticationSessionModel;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
@ -37,122 +31,24 @@ import java.util.List;
*/
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
String EXTERNAL_IDENTITY_PROVIDER = "EXTERNAL_IDENTITY_PROVIDER";
String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
interface AuthenticationCallback {
/**
* Common method to return current authenticationSession and verify if it is not expired
*
* @param encodedCode
* @return see description
*/
AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode);
/**
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
* with the remote IDP. There is an assumption that authenticationSession is set in the context when this method is called
*
* @param context
* @return see description
*/
Response authenticated(BrokeredIdentityContext context);
/**
* Called when user cancelled authentication on the IDP side - for example user didn't approve consent page on the IDP side.
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
*
* @param idpConfig identity provider config
* @return see description
*/
Response cancelled(IdentityProviderModel idpConfig);
/**
* Indicates that login with the particular IDP should be retried
*
* @param identityProvider provider to retry login
* @param authSession authentication session
* @return see description
*/
Response retryLogin(IdentityProvider<?> identityProvider, AuthenticationSessionModel authSession);
/**
* Called when error happened on the IDP side.
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
*
* @return see description
*/
Response error(IdentityProviderModel idpConfig, String message);
}
C getConfig();
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
/**
* JAXRS callback endpoint for when the remote IDP wants to callback to keycloak.
*
* @return
*/
Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event);
/**
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
* only once during the authentication.</p>
*
* @param request The initial authentication request. Contains all the contextual information in order to build an authentication request to the
* identity provider.
* @return
*/
Response performLogin(AuthenticationRequest request);
/**
* <p>Returns a {@link jakarta.ws.rs.core.Response} containing the token previously stored during the authentication process for a
* specific user.</p>
*
* @param identity
* @return
*/
Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity);
void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
/**
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
* with the IDP
*
* @param userSession
* @param uriInfo
* @param realm
* @return null if this is not supported by this provider
*/
Response keycloakInitiatedBrowserLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
/**
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
*
* @return
*/
Response export(UriInfo uriInfo, RealmModel realm, String format);
/**
* Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
* @return
*/
IdentityProviderDataMarshaller getMarshaller();
default Response export(UriInfo uriInfo, RealmModel realm, String format) {
return Response.noContent().build();
}
/**
* Checks whether a mapper is supported for this Identity Provider.
*/
default boolean isMapperSupported(IdentityProviderMapper mapper) {
List<String> compatibleIdps = Arrays.asList(mapper.getCompatibleProviders());
List<String> compatibleIdps = Arrays.asList(mapper.getCompatibleProviders());
return compatibleIdps.contains(IdentityProviderMapper.ANY_PROVIDER)
|| compatibleIdps.contains(getConfig().getProviderId());
|| compatibleIdps.contains(getConfig().getProviderId());
}
/**
@ -163,11 +59,4 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
default boolean reloadKeys() {
return false;
}
/**
* @return true if identity provider supports long value of "state" parameter (or "RelayState" parameter), which can hold relatively big amount of context data
*/
default boolean supportsLongStateParameter() {
return true;
}
}

View File

@ -0,0 +1,122 @@
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
public interface UserAuthenticationIdentityProvider<C extends IdentityProviderModel> extends IdentityProvider<C> {
String EXTERNAL_IDENTITY_PROVIDER = "EXTERNAL_IDENTITY_PROVIDER";
String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
interface AuthenticationCallback {
/**
* Common method to return current authenticationSession and verify if it is not expired
*
* @param encodedCode
* @return see description
*/
AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode);
/**
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
* with the remote IDP. There is an assumption that authenticationSession is set in the context when this method is called
*
* @param context
* @return see description
*/
Response authenticated(BrokeredIdentityContext context);
/**
* Called when user cancelled authentication on the IDP side - for example user didn't approve consent page on the IDP side.
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
*
* @param idpConfig identity provider config
* @return see description
*/
Response cancelled(IdentityProviderModel idpConfig);
/**
* Indicates that login with the particular IDP should be retried
*
* @param identityProvider provider to retry login
* @param authSession authentication session
* @return see description
*/
Response retryLogin(UserAuthenticationIdentityProvider<?> identityProvider, AuthenticationSessionModel authSession);
/**
* Called when error happened on the IDP side.
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
*
* @return see description
*/
Response error(IdentityProviderModel idpConfig, String message);
}
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
/**
* JAXRS callback endpoint for when the remote IDP wants to callback to keycloak.
*
* @return
*/
Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event);
/**
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
* only once during the authentication.</p>
*
* @param request The initial authentication request. Contains all the contextual information in order to build an authentication request to the
* identity provider.
* @return
*/
Response performLogin(AuthenticationRequest request);
/**
* <p>Returns a {@link jakarta.ws.rs.core.Response} containing the token previously stored during the authentication process for a
* specific user.</p>
*
* @param identity
* @return
*/
Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity);
void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
/**
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
* with the IDP
*
* @param userSession
* @param uriInfo
* @param realm
* @return null if this is not supported by this provider
*/
Response keycloakInitiatedBrowserLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
/**
* Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
* @return
*/
IdentityProviderDataMarshaller getMarshaller();
/**
* @return true if identity provider supports long value of "state" parameter (or "RelayState" parameter), which can hold relatively big amount of context data
*/
default boolean supportsLongStateParameter() {
return true;
}
}

View File

@ -16,11 +16,11 @@
*/
package org.keycloak.broker.social;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.models.IdentityProviderModel;
/**
* @author Pedro Igor
*/
public interface SocialIdentityProvider<C extends IdentityProviderModel> extends IdentityProvider<C> {
public interface SocialIdentityProvider<C extends IdentityProviderModel> extends UserAuthenticationIdentityProvider<C> {
}

View File

@ -20,8 +20,8 @@ package org.keycloak.authentication.authenticators.broker.util;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.models.Constants;
@ -282,7 +282,7 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
ctx.setBrokerUserId(getBrokerUserId());
ctx.setToken(getToken());
IdentityProvider idp = IdentityBrokerService.getIdentityProvider(session, idpConfig.getAlias());
UserAuthenticationIdentityProvider<?> idp = IdentityBrokerService.getIdentityProvider(session, idpConfig.getAlias());
ctx.setIdp(idp);
IdentityProviderDataMarshaller serializer = idp.getMarshaller();

View File

@ -7,7 +7,6 @@ import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.ClientAuthenticationFlowContext;
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
import org.keycloak.broker.provider.ClientAssertionIdentityProvider;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.spiffe.SpiffeConstants;
import org.keycloak.cache.AlternativeLookupProvider;
import org.keycloak.common.Profile;
@ -92,12 +91,7 @@ public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator
if (identityProviderModel == null) {
return null;
}
IdentityProvider<?> identityProvider = IdentityBrokerService.getIdentityProvider(session, identityProviderModel);
if (identityProvider instanceof ClientAssertionIdentityProvider clientAssertionProvider) {
return clientAssertionProvider;
} else {
throw new RuntimeException("Provider does not support client assertions");
}
return IdentityBrokerService.getIdentityProvider(session, identityProviderModel, ClientAssertionIdentityProvider.class);
}
@Override

View File

@ -39,7 +39,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ExchangeExternalToken;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.SecretGenerator;
@ -360,7 +360,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
if (!getConfig().isStoreToken()) {
// if token isn't stored, we need to see if this session has been linked
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
brokerId = brokerId == null ? tokenUserSession.getNote(IdentityProvider.EXTERNAL_IDENTITY_PROVIDER) : brokerId;
brokerId = brokerId == null ? tokenUserSession.getNote(UserAuthenticationIdentityProvider.EXTERNAL_IDENTITY_PROVIDER) : brokerId;
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
event.detail(Details.REASON, "requested_issuer has not linked");
event.error(Errors.INVALID_REQUEST);

View File

@ -94,7 +94,7 @@ import java.util.Optional;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> implements ExchangeExternalToken, ClientAssertionIdentityProvider {
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> implements ExchangeExternalToken, ClientAssertionIdentityProvider<OIDCIdentityProviderConfig> {
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
public static final String SCOPE_OPENID = "openid";

View File

@ -22,7 +22,7 @@ import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
@ -148,7 +148,7 @@ public class SAMLEndpoint {
protected final RealmModel realm;
protected EventBuilder event;
protected final SAMLIdentityProviderConfig config;
protected final IdentityProvider.AuthenticationCallback callback;
protected final UserAuthenticationIdentityProvider.AuthenticationCallback callback;
protected final SAMLIdentityProvider provider;
private final DestinationValidator destinationValidator;
@ -159,7 +159,7 @@ public class SAMLEndpoint {
private final HttpHeaders headers;
public SAMLEndpoint(KeycloakSession session, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback, DestinationValidator destinationValidator) {
public SAMLEndpoint(KeycloakSession session, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, UserAuthenticationIdentityProvider.AuthenticationCallback callback, DestinationValidator destinationValidator) {
this.realm = session.getContext().getRealm();
this.config = config;
this.callback = callback;

View File

@ -1,30 +1,18 @@
package org.keycloak.broker.spiffe;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.keycloak.authentication.ClientAuthenticationFlowContext;
import org.keycloak.authentication.authenticators.client.AbstractJWTClientValidator;
import org.keycloak.authentication.authenticators.client.FederatedJWTClientValidator;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ClientAssertionIdentityProvider;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.keys.PublicKeyStorageUtils;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.nio.charset.StandardCharsets;
@ -40,7 +28,7 @@ import java.nio.charset.StandardCharsets;
* <li>Keys are fetched from a SPIFFE bundle endpoint, where the JWKS has additional SPIFFE specific fields (<code>spiffe_sequence</code> and <code>spiffe_refresh_hint</code>, the JWK does not set the <code>alg></code></li>
* </ul>
*/
public class SpiffeIdentityProvider implements IdentityProvider<SpiffeIdentityProviderConfig>, ClientAssertionIdentityProvider {
public class SpiffeIdentityProvider implements ClientAssertionIdentityProvider<SpiffeIdentityProviderConfig> {
private static final Logger LOGGER = Logger.getLogger(SpiffeIdentityProvider.class);
@ -103,58 +91,4 @@ public class SpiffeIdentityProvider implements IdentityProvider<SpiffeIdentityPr
public void close() {
}
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) {
throw new UnsupportedOperationException();
}
@Override
public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context) {
throw new UnsupportedOperationException();
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
throw new UnsupportedOperationException();
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
throw new UnsupportedOperationException();
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
throw new UnsupportedOperationException();
}
@Override
public Response performLogin(AuthenticationRequest request) {
throw new UnsupportedOperationException();
}
@Override
public Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity) {
throw new UnsupportedOperationException();
}
@Override
public void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
throw new UnsupportedOperationException();
}
@Override
public Response keycloakInitiatedBrowserLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
throw new UnsupportedOperationException();
}
@Override
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
throw new UnsupportedOperationException();
}
@Override
public IdentityProviderDataMarshaller getMarshaller() {
throw new UnsupportedOperationException();
}
}

View File

@ -28,6 +28,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -300,8 +301,8 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv
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);
userSession.setNote(UserAuthenticationIdentityProvider.EXTERNAL_IDENTITY_PROVIDER, externalExchangeContext.idpModel().getAlias());
userSession.setNote(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN, subjectToken);
context.addSessionNotesToUserSession(userSession);

View File

@ -23,7 +23,7 @@ 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.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.UserModel;
@ -101,8 +101,8 @@ public class ExternalToInternalTokenExchangeProvider extends StandardTokenExchan
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);
userSession.setNote(UserAuthenticationIdentityProvider.EXTERNAL_IDENTITY_PROVIDER, externalExchangeContext.idpModel().getAlias());
userSession.setNote(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN, subjectToken);
context.addSessionNotesToUserSession(userSession);

View File

@ -35,7 +35,7 @@ import org.keycloak.authentication.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.VerificationException;
@ -457,7 +457,7 @@ public class AuthenticationManager {
if (logoutBroker) {
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
if (brokerId != null) {
IdentityProvider<?> identityProvider = null;
UserAuthenticationIdentityProvider<?> identityProvider = null;
try {
identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId);
} catch (IdentityBrokerException e) {
@ -700,7 +700,7 @@ public class AuthenticationManager {
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
String initiatingIdp = logoutAuthSession.getAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP);
if (brokerId != null && !brokerId.equals(initiatingIdp)) {
IdentityProvider<?> identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId);
UserAuthenticationIdentityProvider<?> identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId);
Response response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm);
if (response != null) {
return response;

View File

@ -22,6 +22,7 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.authenticators.broker.IdpConfirmOverrideLinkAuthenticator;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdpLinkAction;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.http.HttpRequest;
import org.keycloak.OAuthErrorException;
import org.keycloak.authentication.AuthenticationProcessor;
@ -131,7 +132,7 @@ import static org.keycloak.broker.provider.AbstractIdentityProvider.BROKER_REGIS
*
* @author Pedro Igor
*/
public class IdentityBrokerService implements IdentityProvider.AuthenticationCallback {
public class IdentityBrokerService implements UserAuthenticationIdentityProvider.AuthenticationCallback {
// Authentication session note, which references identity provider that is currently linked
public static final String LINKING_IDENTITY_PROVIDER = "LINKING_IDENTITY_PROVIDER";
@ -340,7 +341,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
public Response performClientInitiatedAccountLogin(String providerAlias, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
try {
IdentityProvider<?> identityProvider = getIdentityProvider(session, providerAlias);
UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, providerAlias);
Response response = identityProvider.performLogin(createAuthenticationRequest(identityProvider, providerAlias, clientSessionCode));
if (response != null) {
@ -402,7 +403,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
clientSessionCode.getClientSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
}
IdentityProvider<?> identityProvider = getIdentityProvider(session, identityProviderModel.getAlias());
UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, identityProviderModel.getAlias());
Response response = identityProvider.performLogin(createAuthenticationRequest(identityProvider, providerAlias, clientSessionCode));
if (response != null) {
@ -423,7 +424,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
@Override
public Response retryLogin(IdentityProvider<?> identityProvider, AuthenticationSessionModel authSession) {
public Response retryLogin(UserAuthenticationIdentityProvider<?> identityProvider, AuthenticationSessionModel authSession) {
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
Response response = identityProvider.performLogin(createAuthenticationRequest(identityProvider, identityProvider.getConfig().getAlias(), clientSessionCode));
@ -443,7 +444,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Path("{provider_alias}/endpoint")
public Object getEndpoint(@PathParam("provider_alias") String providerAlias) {
IdentityProvider identityProvider;
UserAuthenticationIdentityProvider<?> identityProvider;
try {
identityProvider = getIdentityProvider(session, providerAlias);
@ -501,7 +502,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
IdentityProvider identityProvider = getIdentityProvider(session, providerAlias);
UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, providerAlias);
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerAlias);
if (identityProviderConfig.isStoreToken()) {
@ -755,7 +756,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
logger.debugf("Registered new user '%s' after first login with identity provider '%s'. Identity provider username is '%s' . ", federatedUser.getUsername(), providerAlias, context.getUsername());
IdentityProvider idp = context.getIdp();
UserAuthenticationIdentityProvider<?> idp = context.getIdp();
idp.importNewUser(session, realmModel, federatedUser, context);
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
session.identityProviders().getMappersByAliasStream(providerAlias).forEach(mapper -> {
@ -1255,7 +1256,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return null;
}
private AuthenticationRequest createAuthenticationRequest(IdentityProvider<?> identityProvider, String providerAlias, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
private AuthenticationRequest createAuthenticationRequest(UserAuthenticationIdentityProvider<?> identityProvider, String providerAlias, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
AuthenticationSessionModel authSession = null;
IdentityBrokerState encodedState = null;
@ -1342,19 +1343,20 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
throw ErrorResponse.error(message, Response.Status.NOT_FOUND);
}
public static IdentityProvider<?> getIdentityProvider(KeycloakSession session, String alias) {
public static UserAuthenticationIdentityProvider<?> getIdentityProvider(KeycloakSession session, String alias) {
IdentityProviderModel identityProviderModel = session.identityProviders().getByAlias(alias);
IdentityProvider<?> identityProvider = getIdentityProvider(session, identityProviderModel);
UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, identityProviderModel, UserAuthenticationIdentityProvider.class);
if (identityProvider == null) {
throw new IdentityBrokerException("Identity Provider [" + alias + "] not found.");
}
return identityProvider;
}
public static IdentityProvider<?> getIdentityProvider(KeycloakSession session, IdentityProviderModel identityProviderModel) {
public static <T extends IdentityProvider<?>> T getIdentityProvider(KeycloakSession session, IdentityProviderModel identityProviderModel, Class<T> type) {
if (identityProviderModel != null) {
IdentityProviderFactory<?> providerFactory = getIdentityProviderFactory(session, identityProviderModel);
return providerFactory != null ? providerFactory.create(session, identityProviderModel) : null;
IdentityProvider<?> idp = providerFactory.create(session, identityProviderModel);
return type.isInstance(idp) ? type.cast(idp) : null;
}
return null;
}

View File

@ -24,7 +24,7 @@ import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection;
@ -147,7 +147,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
}
protected Response exchangeSessionToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject) {
String accessToken = tokenUserSession.getNote(IdentityProvider.FEDERATED_ACCESS_TOKEN);
String accessToken = tokenUserSession.getNote(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN);
if (accessToken == null) {
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
@ -242,7 +242,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
if (providerConfig.isStoreToken()) {
identity.setToken(token);
}
identity.getContextData().put(IdentityProvider.FEDERATED_ACCESS_TOKEN, token);
identity.getContextData().put(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN, token);
identity.setAuthenticationSession(authSession);
@ -271,7 +271,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
@Override
public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context) {
authSession.setUserSessionNote(IdentityProvider.FEDERATED_ACCESS_TOKEN, (String)context.getContextData().get(IdentityProvider.FEDERATED_ACCESS_TOKEN));
authSession.setUserSessionNote(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN, (String) context.getContextData().get(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN));
}