From 84a161d4dda24d49c806cf91ea0f961af24ea891 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 21 Oct 2025 16:27:07 +0200 Subject: [PATCH] Extract related methods from IdentityProvider to UserIdentityProvider (#43535) Closes #43534 Signed-off-by: stianst --- .../topics/changes/changes-26_5_0.adoc | 10 ++ .../provider/AbstractIdentityProvider.java | 7 +- .../provider/BrokeredIdentityContext.java | 6 +- .../ClientAssertionIdentityProvider.java | 3 +- .../broker/provider/IdentityProvider.java | 121 +---------------- .../UserAuthenticationIdentityProvider.java | 122 ++++++++++++++++++ .../broker/social/SocialIdentityProvider.java | 4 +- .../SerializedBrokeredIdentityContext.java | 4 +- .../FederatedJWTClientAuthenticator.java | 8 +- .../oidc/AbstractOAuth2IdentityProvider.java | 4 +- .../broker/oidc/OIDCIdentityProvider.java | 2 +- .../keycloak/broker/saml/SAMLEndpoint.java | 6 +- .../broker/spiffe/SpiffeIdentityProvider.java | 68 +--------- .../AbstractTokenExchangeProvider.java | 5 +- ...ternalToInternalTokenExchangeProvider.java | 6 +- .../managers/AuthenticationManager.java | 6 +- .../resources/IdentityBrokerService.java | 26 ++-- .../twitter/TwitterIdentityProvider.java | 8 +- 18 files changed, 182 insertions(+), 234 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/broker/provider/UserAuthenticationIdentityProvider.java diff --git a/docs/documentation/upgrading/topics/changes/changes-26_5_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_5_0.adoc index 2f8da5087bd..e9fd0cbd234 100644 --- a/docs/documentation/upgrading/topics/changes/changes-26_5_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-26_5_0.adoc @@ -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 diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java index 6035a0ea44f..ad16071ac2d 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java @@ -43,7 +43,7 @@ import java.util.UUID; /** * @author Pedro Igor */ -public abstract class AbstractIdentityProvider implements IdentityProvider { +public abstract class AbstractIdentityProvider implements UserAuthenticationIdentityProvider { protected static final Logger logger = Logger.getLogger(AbstractIdentityProvider.class); @@ -67,11 +67,6 @@ public abstract class AbstractIdentityProvider return this.config; } - @Override - public Response export(UriInfo uriInfo, RealmModel realm, String format) { - return Response.noContent().build(); - } - @Override public void close() { // no-op diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java index 2e8c5c3371e..3b304066742 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java @@ -48,7 +48,7 @@ public class BrokeredIdentityContext { private String brokerUserId; private String token; private IdentityProviderModel idpConfig; - private IdentityProvider idp; + private UserAuthenticationIdentityProvider idp; private Map 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; } diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/ClientAssertionIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/ClientAssertionIdentityProvider.java index 5d47683e6a0..b6b710645ce 100644 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/ClientAssertionIdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/ClientAssertionIdentityProvider.java @@ -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 extends IdentityProvider { boolean verifyClientAssertion(ClientAuthenticationFlowContext context) throws Exception; diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java index 4069add6b99..375d3831a7e 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java @@ -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 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); - - /** - *

Initiates the authentication process by sending an authentication request to an identity provider. This method is called - * only once during the authentication.

- * - * @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); - - /** - *

Returns a {@link jakarta.ws.rs.core.Response} containing the token previously stored during the authentication process for a - * specific user.

- * - * @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 compatibleIdps = Arrays.asList(mapper.getCompatibleProviders()); + List 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 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; - } } diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/UserAuthenticationIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/UserAuthenticationIdentityProvider.java new file mode 100644 index 00000000000..c208a5073a6 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/UserAuthenticationIdentityProvider.java @@ -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 extends IdentityProvider { + + 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); + + /** + *

Initiates the authentication process by sending an authentication request to an identity provider. This method is called + * only once during the authentication.

+ * + * @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); + + /** + *

Returns a {@link jakarta.ws.rs.core.Response} containing the token previously stored during the authentication process for a + * specific user.

+ * + * @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; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java index f77e3130726..dfd548fe964 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java @@ -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 extends IdentityProvider { +public interface SocialIdentityProvider extends UserAuthenticationIdentityProvider { } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java index 14e1a7c0929..d6e88154698 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java @@ -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(); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/FederatedJWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/FederatedJWTClientAuthenticator.java index 6e0ad9ef12f..4dc912cb563 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/FederatedJWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/FederatedJWTClientAuthenticator.java @@ -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 diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 88744d389a5..92f76b0d50e 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -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 implements ExchangeExternalToken, ClientAssertionIdentityProvider { +public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider implements ExchangeExternalToken, ClientAssertionIdentityProvider { protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class); public static final String SCOPE_OPENID = "openid"; diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java index b351e412658..6f79b663a4a 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -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; diff --git a/services/src/main/java/org/keycloak/broker/spiffe/SpiffeIdentityProvider.java b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeIdentityProvider.java index c695223a968..ba705985be3 100644 --- a/services/src/main/java/org/keycloak/broker/spiffe/SpiffeIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeIdentityProvider.java @@ -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; *
  • Keys are fetched from a SPIFFE bundle endpoint, where the JWKS has additional SPIFFE specific fields (spiffe_sequence and spiffe_refresh_hint, the JWK does not set the alg>
  • * */ -public class SpiffeIdentityProvider implements IdentityProvider, ClientAssertionIdentityProvider { +public class SpiffeIdentityProvider implements ClientAssertionIdentityProvider { private static final Logger LOGGER = Logger.getLogger(SpiffeIdentityProvider.class); @@ -103,58 +91,4 @@ public class SpiffeIdentityProvider implements 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; diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 0c05eefe607..f4c1c6f805f 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -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 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 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 clientSessionCode) { + private AuthenticationRequest createAuthenticationRequest(UserAuthenticationIdentityProvider identityProvider, String providerAlias, ClientSessionCode 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 getIdentityProvider(KeycloakSession session, IdentityProviderModel identityProviderModel, Class 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; } diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java index 7f42c4a035b..743a7e3d07b 100755 --- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java @@ -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