From 3dd282e11bf9d580e0deeac369bf83472e8ad861 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 10 Jun 2015 11:38:01 -0400 Subject: [PATCH] pluggable required actions backend --- .../authentication/RequiredActionFactory.java | 10 ++ .../RequiredActionProvider.java | 3 +- .../authentication/RequiredActionSpi.java | 32 ++++++ .../actions/UpdatePassword.java | 93 ++++++++++++++++ .../authentication/actions/UpdateProfile.java | 75 +++++++++++++ .../authentication/actions/UpdateTotp.java | 84 ++++++++++++++ .../authentication/actions/VerifyEmail.java | 104 ++++++++++++++++++ .../managers/AuthenticationManager.java | 104 ++++++++++++------ .../resources/LoginActionsService.java | 12 +- ...cloak.authentication.RequiredActionFactory | 4 + .../services/org.keycloak.provider.Spi | 3 +- 11 files changed, 482 insertions(+), 42 deletions(-) create mode 100755 services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java create mode 100755 services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java create mode 100755 services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java create mode 100755 services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java create mode 100755 services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java create mode 100755 services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java b/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java new file mode 100755 index 0000000000..9acfcd12b4 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RequiredActionFactory extends ProviderFactory { +} diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java index b5cc21aedc..e6ff4882b3 100755 --- a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java @@ -9,6 +9,7 @@ import javax.ws.rs.core.Response; * @version $Revision: 1 $ */ public interface RequiredActionProvider extends Provider { + void evaluateTriggers(RequiredActionContext context); Response invokeRequiredAction(RequiredActionContext context); - Object jaxrsService(); + Object jaxrsService(RequiredActionContext context); } diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java b/services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java new file mode 100755 index 0000000000..65370e5bca --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java @@ -0,0 +1,32 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Stian Thorgersen + */ +public class RequiredActionSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "required-action"; + } + + @Override + public Class getProviderClass() { + return RequiredActionProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return RequiredActionFactory.class; + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java new file mode 100755 index 0000000000..23d5a93b1f --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java @@ -0,0 +1,93 @@ +package org.keycloak.authentication.actions; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.util.Time; + +import javax.ws.rs.core.Response; +import java.util.concurrent.TimeUnit; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory { + protected static Logger logger = Logger.getLogger(UpdatePassword.class); + @Override + public void evaluateTriggers(RequiredActionContext context) { + int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword(); + if(daysToExpirePassword != -1) { + for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) { + if (entity.getType().equals(UserCredentialModel.PASSWORD)) { + + if(entity.getCreatedDate() == null) { + context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + logger.debug("User is required to update password"); + } else { + long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate(); + long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword); + + if(timeElapsed > timeToExpire) { + context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + logger.debug("User is required to update password"); + } + } + break; + } + } + } + } + + @Override + public Response invokeRequiredAction(RequiredActionContext context) { + ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); + accessCode.setAction(ClientSessionModel.Action.UPDATE_PASSWORD.name()); + + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + .setUser(context.getUser()); + return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); + } + + @Override + public Object jaxrsService(RequiredActionContext context) { + // this is handled by LoginActionsService at the moment + return null; + } + + + @Override + public void close() { + + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return UserModel.RequiredAction.UPDATE_PASSWORD.name(); + } +} diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java new file mode 100755 index 0000000000..8ee36e94fb --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java @@ -0,0 +1,75 @@ +package org.keycloak.authentication.actions; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.ClientSessionCode; + +import javax.ws.rs.core.Response; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory { + protected static Logger logger = Logger.getLogger(UpdateProfile.class); + @Override + public void evaluateTriggers(RequiredActionContext context) { + if (context.getRealm().isVerifyEmail() && !context.getUser().isEmailVerified()) { + context.getUser().addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL); + logger.debug("User is required to verify email"); + } + } + + @Override + public Response invokeRequiredAction(RequiredActionContext context) { + ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); + accessCode.setAction(ClientSessionModel.Action.UPDATE_PROFILE.name()); + + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + .setUser(context.getUser()); + return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE); + } + + @Override + public Object jaxrsService(RequiredActionContext context) { + // this is handled by LoginActionsService at the moment + // todo should be refactored to contain it here + return null; + } + + + @Override + public void close() { + + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return UserModel.RequiredAction.UPDATE_PROFILE.name(); + } +} diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java new file mode 100755 index 0000000000..7f2228abc0 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java @@ -0,0 +1,84 @@ +package org.keycloak.authentication.actions; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.util.Time; + +import javax.ws.rs.core.Response; +import java.util.concurrent.TimeUnit; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory { + protected static Logger logger = Logger.getLogger(UpdateTotp.class); + @Override + public void evaluateTriggers(RequiredActionContext context) { + // I don't think we need this check here. AuthenticationProcessor should be setting the required action + // if OTP changes from required from optional or disabled + for (RequiredCredentialModel c : context.getRealm().getRequiredCredentials()) { + if (c.getType().equals(CredentialRepresentation.TOTP) && !context.getUser().isTotp()) { + context.getUser().addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP); + logger.debug("User is required to configure totp"); + } + } + } + + @Override + public Response invokeRequiredAction(RequiredActionContext context) { + ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); + accessCode.setAction(ClientSessionModel.Action.CONFIGURE_TOTP.name()); + + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + .setUser(context.getUser()); + return loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP); + } + + @Override + public Object jaxrsService(RequiredActionContext context) { + // this is handled by LoginActionsService at the moment + // todo should be refactored to contain it here + return null; + } + + + @Override + public void close() { + + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return UserModel.RequiredAction.CONFIGURE_TOTP.name(); + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java new file mode 100755 index 0000000000..1be48a02c6 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java @@ -0,0 +1,104 @@ +package org.keycloak.authentication.actions; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.services.resources.LoginActionsService; +import org.keycloak.services.validation.Validation; +import org.keycloak.util.Time; + +import javax.ws.rs.core.Response; +import java.util.concurrent.TimeUnit; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory { + protected static Logger logger = Logger.getLogger(VerifyEmail.class); + @Override + public void evaluateTriggers(RequiredActionContext context) { + int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword(); + if(daysToExpirePassword != -1) { + for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) { + if (entity.getType().equals(UserCredentialModel.PASSWORD)) { + + if(entity.getCreatedDate() == null) { + context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + logger.debug("User is required to update password"); + } else { + long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate(); + long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword); + + if(timeElapsed > timeToExpire) { + context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + logger.debug("User is required to update password"); + } + } + break; + } + } + } + } + + @Override + public Response invokeRequiredAction(RequiredActionContext context) { + if (Validation.isBlank(context.getUser().getEmail())) { + return null; + } + + ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); + accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name()); + context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, context.getUser().getEmail()).success(); + LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId()); + + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + .setUser(context.getUser()); + return loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL); + } + + @Override + public Object jaxrsService(RequiredActionContext context) { + // this is handled by LoginActionsService at the moment + return null; + } + + + @Override + public void close() { + + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return UserModel.RequiredAction.VERIFY_EMAIL.name(); + } + +} diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index f445bfe7b3..8d1995054c 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -6,6 +6,9 @@ import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; import org.keycloak.RSATokenVerifier; import org.keycloak.VerificationException; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; @@ -28,6 +31,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.resources.IdentityBrokerService; @@ -433,15 +437,70 @@ public class AuthenticationManager { } - public static Response actionRequired(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, - ClientConnection clientConnection, - HttpRequest request, UriInfo uriInfo, EventBuilder event) { - RealmModel realm = clientSession.getRealm(); - UserModel user = userSession.getUser(); + public static Response actionRequired(final KeycloakSession session, final UserSessionModel userSession, final ClientSessionModel clientSession, + final ClientConnection clientConnection, + final HttpRequest request, final UriInfo uriInfo, final EventBuilder event) { + final RealmModel realm = clientSession.getRealm(); + final UserModel user = userSession.getUser(); + /* isForcePasswordUpdateRequired(realm, user); isTotpConfigurationRequired(realm, user); isEmailVerificationRequired(realm, user); - ClientModel client = clientSession.getClient(); + */ + final ClientModel client = clientSession.getClient(); + + RequiredActionContext context = new RequiredActionContext() { + @Override + public EventBuilder getEvent() { + return event; + } + + @Override + public UserModel getUser() { + return user; + } + + @Override + public RealmModel getRealm() { + return realm; + } + + @Override + public ClientSessionModel getClientSession() { + return clientSession; + } + + @Override + public UserSessionModel getUserSession() { + return userSession; + } + + @Override + public ClientConnection getConnection() { + return clientConnection; + } + + @Override + public UriInfo getUriInfo() { + return uriInfo; + } + + @Override + public KeycloakSession getSession() { + return session; + } + + @Override + public HttpRequest getHttpRequest() { + return request; + } + }; + + // see if any required actions need triggering, i.e. an expired password + for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) { + RequiredActionProvider provider = ((RequiredActionFactory)factory).create(session); + provider.evaluateTriggers(context); + } ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); @@ -450,38 +509,19 @@ public class AuthenticationManager { event.detail(Details.CODE_ID, clientSession.getId()); Set requiredActions = user.getRequiredActions(); - if (!requiredActions.isEmpty()) { - Iterator i = user.getRequiredActions().iterator(); - String action = i.next(); - - if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isBlank(user.getEmail())) { - if (i.hasNext()) - action = i.next(); - else - action = null; - } + for (String action : requiredActions) { + RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, action); + Response challenge = actionProvider.invokeRequiredAction(context); + if (challenge != null) return challenge; - if (action != null) { - accessCode.setRequiredAction(RequiredAction.valueOf(action)); - - LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) - .setUser(user); - if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name())) { - event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success(); - LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); - } - - return loginFormsProvider.createResponse(RequiredAction.valueOf(action)); - } } - if (client.isConsentRequired()) { accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT.name()); UserConsentModel grantedConsent = user.getConsentByClient(client.getId()); - List realmRoles = new LinkedList(); - MultivaluedMap resourceRoles = new MultivaluedMapImpl(); + List realmRoles = new LinkedList<>(); + MultivaluedMap resourceRoles = new MultivaluedMapImpl<>(); for (RoleModel r : accessCode.getRequestedRoles()) { // Consent already granted by user @@ -496,7 +536,7 @@ public class AuthenticationManager { } } - List protocolMappers = new LinkedList(); + List protocolMappers = new LinkedList<>(); for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) { if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) { if (grantedConsent == null || !grantedConsent.isProtocolMapperGranted(protocolMapper)) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index a81327ee19..08327b96a6 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -773,7 +773,7 @@ public class LoginActionsService { event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success(); } - return redirectOauth(user, accessCode, clientSession, userSession); + return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); } @Path("totp") @@ -818,7 +818,7 @@ public class LoginActionsService { event.clone().event(EventType.UPDATE_TOTP).success(); - return redirectOauth(user, accessCode, clientSession, userSession); + return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); } @Path("password") @@ -880,7 +880,7 @@ public class LoginActionsService { event = event.clone().event(EventType.LOGIN); - return redirectOauth(user, accessCode, clientSession, userSession); + return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); } @@ -913,7 +913,7 @@ public class LoginActionsService { event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN); - return redirectOauth(user, accessCode, clientSession, userSession); + return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); } else { Checks checks = new Checks(); if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL.name())) { @@ -1057,10 +1057,6 @@ public class LoginActionsService { CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true); } - private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) { - return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); - } - private void initEvent(ClientSessionModel clientSession) { event.event(EventType.LOGIN).client(clientSession.getClient()) .user(clientSession.getUserSession().getUser()) diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory new file mode 100755 index 0000000000..fc74174d2c --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory @@ -0,0 +1,4 @@ +org.keycloak.authentication.actions.UpdatePassword +org.keycloak.authentication.actions.UpdateProfile +org.keycloak.authentication.actions.UpdateTotp +org.keycloak.authentication.actions.VerifyEmail \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index d2b5ca78ff..050fef24d6 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -3,4 +3,5 @@ org.keycloak.protocol.ProtocolMapperSpi org.keycloak.exportimport.ClientImportSpi org.keycloak.wellknown.WellKnownSpi org.keycloak.messages.MessagesSpi -org.keycloak.authentication.AuthenticatorSpi \ No newline at end of file +org.keycloak.authentication.AuthenticatorSpi +org.keycloak.authentication.RequiredActionSpi \ No newline at end of file