diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java index 25a28c9969..011e7f3c3b 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java @@ -56,6 +56,9 @@ public class UrlBean { } public String getRegistrationAction() { + if (this.actionuri != null) { + return this.actionuri.toString(); + } return Urls.realmRegisterAction(baseURI, realm).toString(); } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 751242295b..3f2a1034d9 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -2,7 +2,6 @@ package org.keycloak.models.utils; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.RealmModel; /** @@ -11,10 +10,80 @@ import org.keycloak.models.RealmModel; */ public class DefaultAuthenticationFlows { + public static final String REGISTRATION_FLOW = "registration"; + public static final String REGISTRATION_FORM_FLOW = "registration form"; public static final String BROWSER_FLOW = "browser"; - public static final String FORMS_FLOW = "forms"; + public static final String LOGIN_FORMS_FLOW = "forms"; public static void addFlows(RealmModel realm) { + browserFlow(realm); + registrationFlow(realm); + + } + + public static void registrationFlow(RealmModel realm) { + AuthenticationFlowModel registrationFlow = new AuthenticationFlowModel(); + registrationFlow.setAlias(REGISTRATION_FLOW); + registrationFlow.setDescription("registration flow"); + registrationFlow.setProviderId("basic-flow"); + registrationFlow = realm.addAuthenticationFlow(registrationFlow); + + AuthenticationFlowModel registrationFormFlow = new AuthenticationFlowModel(); + registrationFormFlow.setAlias(REGISTRATION_FORM_FLOW); + registrationFormFlow.setDescription("registration form"); + registrationFormFlow.setProviderId("form-flow"); + registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow); + + AuthenticationExecutionModel execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); + execution.setAuthenticator("registration-page-form"); + execution.setPriority(10); + execution.setUserSetupAllowed(false); + execution.setAutheticatorFlow(true); + execution.setFlowId(registrationFormFlow.getId()); + realm.addAuthenticatorExecution(execution); + + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFormFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); + execution.setAuthenticator("username-validation-action"); + execution.setPriority(20); + execution.setUserSetupAllowed(false); + execution.setAutheticatorFlow(false); + realm.addAuthenticatorExecution(execution); + + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFormFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); + execution.setAuthenticator("profile-validation-action"); + execution.setPriority(30); + execution.setUserSetupAllowed(false); + execution.setAutheticatorFlow(false); + realm.addAuthenticatorExecution(execution); + + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFormFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); + execution.setAuthenticator("password-validation-action"); + execution.setPriority(40); + execution.setUserSetupAllowed(false); + execution.setAutheticatorFlow(false); + realm.addAuthenticatorExecution(execution); + + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFormFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); + execution.setAuthenticator("registration-user-creation"); + execution.setPriority(50); + execution.setUserSetupAllowed(false); + execution.setAutheticatorFlow(false); + realm.addAuthenticatorExecution(execution); + + + } + + public static void browserFlow(RealmModel realm) { AuthenticationFlowModel browser = new AuthenticationFlowModel(); browser.setAlias(BROWSER_FLOW); browser.setDescription("browser based authentication"); @@ -39,7 +108,7 @@ public class DefaultAuthenticationFlows { AuthenticationFlowModel forms = new AuthenticationFlowModel(); - forms.setAlias(FORMS_FLOW); + forms.setAlias(LOGIN_FORMS_FLOW); forms.setDescription("Username, password, otp and other auth forms."); forms.setProviderId("basic-flow"); forms = realm.addAuthenticationFlow(forms); @@ -72,8 +141,5 @@ public class DefaultAuthenticationFlows { execution.setUserSetupAllowed(true); execution.setAutheticatorFlow(false); realm.addAuthenticatorExecution(execution); - - // - } } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index f5ff56e9b0..472f62f8c9 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -26,6 +26,7 @@ import org.keycloak.util.Time; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import java.util.List; /** * @author Bill Burke @@ -167,10 +168,31 @@ public class AuthenticationProcessor { Status status; Response challenge; Error error; + List currentExecutions; - private Result(AuthenticationExecutionModel execution, Authenticator authenticator) { + private Result(AuthenticationExecutionModel execution, Authenticator authenticator, List currentExecutions) { this.execution = execution; this.authenticator = authenticator; + this.currentExecutions = currentExecutions; + } + + @Override + public EventBuilder newEvent() { + AuthenticationProcessor.this.event = new EventBuilder(realm, session, connection); + return AuthenticationProcessor.this.event; + } + + @Override + public AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory) { + List executions = realm.getAuthenticationExecutions(execution.getParentFlow()); + for (AuthenticationExecutionModel exe : executions) { + AuthenticatorFactory factory = (AuthenticatorFactory) getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, exe.getAuthenticator()); + if (factory != null && factory.getReferenceCategory().equals(authenticatorCategory)) { + return exe.getRequirement(); + } + + } + return null; } @Override @@ -434,8 +456,7 @@ public class AuthenticationProcessor { throw new AuthException(Error.INTERNAL_ERROR); } if (flow.getProviderId() == null || flow.getProviderId().equals("basic-flow")) { - DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this); - flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator(); + DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this, flow); return flowExecution; } else if (flow.getProviderId().equals("form-flow")) { @@ -448,7 +469,6 @@ public class AuthenticationProcessor { public Response authenticate() throws AuthException { checkClientSession(); logger.debug("AUTHENTICATE"); - event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .detail(Details.AUTH_METHOD, clientSession.getAuthMethod()); @@ -490,7 +510,6 @@ public class AuthenticationProcessor { resetFlow(clientSession); return authenticate(); } - event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .detail(Details.AUTH_METHOD, clientSession.getAuthMethod()); @@ -521,7 +540,6 @@ public class AuthenticationProcessor { public Response authenticateOnly() throws AuthException { checkClientSession(); - event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .detail(Details.AUTH_METHOD, clientSession.getAuthMethod()); @@ -587,8 +605,8 @@ public class AuthenticationProcessor { } - public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator) { - return new Result(model, authenticator); + public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator, List executions) { + return new Result(model, authenticator, executions); } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java index 07a3fd1cb4..80654c797e 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java @@ -21,6 +21,7 @@ import javax.ws.rs.core.UriInfo; */ public interface AuthenticatorContext { EventBuilder getEvent(); + EventBuilder newEvent(); AuthenticationExecutionModel getExecution(); @@ -54,6 +55,8 @@ public interface AuthenticatorContext { HttpRequest getHttpRequest(); BruteForceProtector getProtector(); + AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory); + void success(); void failure(AuthenticationProcessor.Error error); void failure(AuthenticationProcessor.Error error, Response response); diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java index 2e6178c034..f928bea1fd 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java @@ -9,24 +9,7 @@ import org.keycloak.provider.ProviderFactory; * @author Bill Burke * @version $Revision: 1 $ */ -public interface AuthenticatorFactory extends ProviderFactory, ConfiguredProvider { +public interface AuthenticatorFactory extends ProviderFactory, ConfiguredProvider, ConfigurableAuthenticatorFactory { Authenticator create(); - String getDisplayType(); - - /** - * General authenticator type, i.e. totp, password, cert. - * - * @return null if not a referencable type - */ - String getReferenceType(); - - boolean isConfigurable(); - - /** - * What requirement settings are allowed. - * - * @return - */ - AuthenticationExecutionModel.Requirement[] getRequirementChoices(); } diff --git a/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java new file mode 100755 index 0000000000..cca47c6d84 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java @@ -0,0 +1,27 @@ +package org.keycloak.authentication; + +import org.keycloak.models.AuthenticationExecutionModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface ConfigurableAuthenticatorFactory { + String getDisplayType(); + + /** + * General authenticator type, i.e. totp, password, cert. + * + * @return null if not a referencable category + */ + String getReferenceCategory(); + + boolean isConfigurable(); + + /** + * What requirement settings are allowed. + * + * @return + */ + AuthenticationExecutionModel.Requirement[] getRequirementChoices(); +} diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java index 8aaaa643e3..54da8add29 100755 --- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java @@ -1,11 +1,13 @@ package org.keycloak.authentication; import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.UserModel; import javax.ws.rs.core.Response; import java.util.Iterator; +import java.util.List; /** * @author Bill Burke @@ -15,11 +17,16 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow { Response alternativeChallenge = null; AuthenticationExecutionModel challengedAlternativeExecution = null; boolean alternativeSuccessful = false; - Iterator executions; + List executions; + Iterator executionIterator; AuthenticationProcessor processor; + AuthenticationFlowModel flow; - public DefaultAuthenticationFlow(AuthenticationProcessor processor) { + public DefaultAuthenticationFlow(AuthenticationProcessor processor, AuthenticationFlowModel flow) { this.processor = processor; + this.flow = flow; + this.executions = processor.getRealm().getAuthenticationExecutions(flow.getId()); + this.executionIterator = executions.iterator(); } protected boolean isProcessed(AuthenticationExecutionModel model) { @@ -34,25 +41,21 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow { @Override public Response processAction(String actionExecution) { - while (executions.hasNext()) { - AuthenticationExecutionModel model = executions.next(); + while (executionIterator.hasNext()) { + AuthenticationExecutionModel model = executionIterator.next(); if (isProcessed(model)) { AuthenticationProcessor.logger.debug("execution is processed"); if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model)) alternativeSuccessful = true; continue; } - if (!model.getId().equals(actionExecution)) { - if (model.isAutheticatorFlow()) { - AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model); - return authenticationFlow.processAction(actionExecution); - } else { - throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR); - } - } else { // we found the action + if (model.isAutheticatorFlow()) { + AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model); + return authenticationFlow.processAction(actionExecution); + } else if (model.getId().equals(actionExecution)) { AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator()); Authenticator authenticator = factory.create(); - AuthenticatorContext result = processor.createAuthenticatorContext(model, authenticator); + AuthenticatorContext result = processor.createAuthenticatorContext(model, authenticator, executions); authenticator.action(result); Response response = processResult(result); if (response == null) return processFlow(); @@ -64,8 +67,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow { @Override public Response processFlow() { - while (executions.hasNext()) { - AuthenticationExecutionModel model = executions.next(); + while (executionIterator.hasNext()) { + AuthenticationExecutionModel model = executionIterator.next(); if (isProcessed(model)) { AuthenticationProcessor.logger.debug("execution is processed"); if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model)) @@ -132,7 +135,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow { } } } - AuthenticatorContext context = processor.createAuthenticatorContext(model, authenticator); + AuthenticatorContext context = processor.createAuthenticatorContext(model, authenticator, executions); authenticator.authenticate(context); Response response = processResult(context); if (response != null) return response; diff --git a/services/src/main/java/org/keycloak/authentication/FormAction.java b/services/src/main/java/org/keycloak/authentication/FormAction.java index 90bd084187..b231829d7e 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAction.java +++ b/services/src/main/java/org/keycloak/authentication/FormAction.java @@ -10,7 +10,7 @@ import org.keycloak.provider.Provider; * @version $Revision: 1 $ */ public interface FormAction extends Provider { - void authenticate(FormContext context); + void authenticate(FormActionContext context); boolean requiresUser(); boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user); diff --git a/services/src/main/java/org/keycloak/authentication/FormActionContext.java b/services/src/main/java/org/keycloak/authentication/FormActionContext.java new file mode 100755 index 0000000000..a519009da9 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormActionContext.java @@ -0,0 +1,12 @@ +package org.keycloak.authentication; + +import org.keycloak.models.AuthenticationExecutionModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface FormActionContext extends AuthenticatorContext { + FormAuthenticator getFormAuthenticator(); + AuthenticationExecutionModel getFormExecution(); +} diff --git a/services/src/main/java/org/keycloak/authentication/FormActionFactory.java b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java index cbac58b611..866cdfaf98 100755 --- a/services/src/main/java/org/keycloak/authentication/FormActionFactory.java +++ b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java @@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory; * @author Bill Burke * @version $Revision: 1 $ */ -public interface FormActionFactory extends ProviderFactory { +public interface FormActionFactory extends ProviderFactory, ConfigurableAuthenticatorFactory { } diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java index b8bb691348..ab4071708a 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java @@ -14,8 +14,10 @@ import org.keycloak.services.managers.BruteForceProtector; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import java.util.Iterator; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Bill Burke @@ -23,26 +25,38 @@ import java.util.List; */ public class FormAuthenticationFlow implements AuthenticationFlow { AuthenticationProcessor processor; - AuthenticationExecutionModel execution; + AuthenticationExecutionModel formExecution; + private final List formActionExecutions; + private final FormAuthenticator formAuthenticator; public FormAuthenticationFlow(AuthenticationProcessor processor, AuthenticationExecutionModel execution) { this.processor = processor; - this.execution = execution; + this.formExecution = execution; + formActionExecutions = processor.getRealm().getAuthenticationExecutions(execution.getFlowId()); + formAuthenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator()); } - private static class FormActionResult implements FormContext { - AuthenticatorContext delegate; - FormAuthenticator authenticator; + private class FormContext implements FormActionContext { + protected AuthenticatorContext delegate; - FormActionResult(AuthenticatorContext delegate, FormAuthenticator authenticator) { + private FormContext(AuthenticatorContext delegate) { this.delegate = delegate; - this.authenticator = authenticator; + } + + @Override + public EventBuilder newEvent() { + return delegate.newEvent(); } @Override public FormAuthenticator getFormAuthenticator() { - return authenticator; + return formAuthenticator; + } + + @Override + public AuthenticationExecutionModel getFormExecution() { + return formExecution; } @Override @@ -135,6 +149,18 @@ public class FormAuthenticationFlow implements AuthenticationFlow { return delegate.getProtector(); } + @Override + public AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory) { + for (AuthenticationExecutionModel formActionExecution : formActionExecutions) { + FormActionFactory factory = (FormActionFactory) getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator()); + if (factory != null && authenticatorCategory.equals(factory.getReferenceCategory())) { + return formActionExecution.getRequirement(); + } + + } + return null; + } + @Override public void success() { delegate.success(); @@ -191,19 +217,19 @@ public class FormAuthenticationFlow implements AuthenticationFlow { } } - @Override public Response processAction(String actionExecution) { - if (!actionExecution.equals(execution.getId())) { + if (!actionExecution.equals(formExecution.getId())) { throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR); } - FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator()); - for (AuthenticationExecutionModel formActionExecution : processor.getRealm().getAuthenticationExecutions(execution.getFlowId())) { - FormAction action = processor.getSession().getProvider(FormAction.class, execution.getAuthenticator()); + Map executionStatus = new HashMap<>(); + List requiredActions = new LinkedList<>(); + for (AuthenticationExecutionModel formActionExecution : formActionExecutions) { + FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator()); UserModel authUser = processor.getClientSession().getAuthenticatedUser(); if (action.requiresUser() && authUser == null) { - throw new AuthenticationProcessor.AuthException("form action: " + execution.getAuthenticator() + " requires user", AuthenticationProcessor.Error.UNKNOWN_USER); + throw new AuthenticationProcessor.AuthException("form action: " + formExecution.getAuthenticator() + " requires user", AuthenticationProcessor.Error.UNKNOWN_USER); } boolean configuredFor = false; if (action.requiresUser() && authUser != null) { @@ -211,23 +237,33 @@ public class FormAuthenticationFlow implements AuthenticationFlow { if (!configuredFor) { if (formActionExecution.isRequired()) { if (formActionExecution.isUserSetupAllowed()) { - AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", execution.getAuthenticator()); - processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED); - action.setRequiredActions(processor.getSession(), processor.getRealm(), authUser); + AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", formExecution.getAuthenticator()); + executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED); + requiredActions.add(action); continue; } else { throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.CREDENTIAL_SETUP_REQUIRED); } } else if (formActionExecution.isOptional()) { - processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED); + executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED); continue; } } } - FormActionResult result = new FormActionResult(processor.createAuthenticatorContext(formActionExecution, null), authenticator); + AuthenticatorContext delegate = processor.createAuthenticatorContext(formActionExecution, null, formActionExecutions); + FormActionContext result = new FormContext(delegate); action.authenticate(result); - return processResult(result, formActionExecution); + Response challenge = processResult(executionStatus, result, formActionExecution); + if (challenge != null) return challenge; + executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); + } + // set status and required actions only if form is fully successful + for (Map.Entry entry : executionStatus.entrySet()) { + processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue()); + } + for (FormAction action : requiredActions) { + action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getClientSession().getAuthenticatedUser()); } return null; @@ -236,42 +272,48 @@ public class FormAuthenticationFlow implements AuthenticationFlow { @Override public Response processFlow() { - FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator()); - AuthenticatorContext context = processor.createAuthenticatorContext(execution, null); - authenticator.authenticate(context); - return processResult(context, execution); + AuthenticatorContext delegate = processor.createAuthenticatorContext(formExecution, null, formActionExecutions); + FormActionContext result = new FormContext(delegate); + formAuthenticator.authenticate(result); + Map executionStatus = new HashMap<>(); + Response response = processResult(executionStatus, result, formExecution); + for (Map.Entry entry : executionStatus.entrySet()) { + processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue()); + } + return response; } - public Response processResult(AuthenticatorContext result, AuthenticationExecutionModel execution) { + public Response processResult(Map executionStatus, AuthenticatorContext result, AuthenticationExecutionModel execution) { AuthenticationProcessor.Status status = result.getStatus(); if (status == AuthenticationProcessor.Status.SUCCESS) { + executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); return null; } else if (status == AuthenticationProcessor.Status.FAILED) { AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator()); processor.logFailure(); - processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED); + executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED); if (result.getChallenge() != null) { - return sendChallenge(result, execution); + return sendChallenge(result); } throw new AuthenticationProcessor.AuthException(result.getError()); } else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) { - processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); - return sendChallenge(result, execution); + executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); + return sendChallenge(result); } else if (status == AuthenticationProcessor.Status.CHALLENGE) { processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); - return sendChallenge(result, execution); + return sendChallenge(result); } else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) { AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator()); processor.logFailure(); - processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); - return sendChallenge(result, execution); + executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); + return sendChallenge(result); } else if (status == AuthenticationProcessor.Status.ATTEMPTED) { AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator()); if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INVALID_CREDENTIALS); } - processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED); + executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED); return null; } else { AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator()); @@ -281,8 +323,8 @@ public class FormAuthenticationFlow implements AuthenticationFlow { } - public Response sendChallenge(AuthenticatorContext result, AuthenticationExecutionModel execution) { - processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId()); + public Response sendChallenge(AuthenticatorContext result) { + processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, formExecution.getId()); return result.getChallenge(); } diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java index 4804e92635..f33843ea6b 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java @@ -1,8 +1,11 @@ package org.keycloak.authentication; +import org.keycloak.models.utils.FormMessage; import org.keycloak.provider.Provider; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import java.util.List; /** * @author Bill Burke @@ -10,5 +13,5 @@ import javax.ws.rs.core.Response; */ public interface FormAuthenticator extends Provider { void authenticate(AuthenticatorContext context); - Response createChallenge(FormContext context, String... errorMessages); + Response createChallenge(FormActionContext context, MultivaluedMap formData, List errorMessages); } diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java index 5b99f1a764..a388f43e3c 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java @@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory; * @author Bill Burke * @version $Revision: 1 $ */ -public interface FormAuthenticatorFactory extends ProviderFactory { +public interface FormAuthenticatorFactory extends ProviderFactory, ConfigurableAuthenticatorFactory { } diff --git a/services/src/main/java/org/keycloak/authentication/FormContext.java b/services/src/main/java/org/keycloak/authentication/FormContext.java deleted file mode 100755 index 24c46a71a7..0000000000 --- a/services/src/main/java/org/keycloak/authentication/FormContext.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.authentication; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface FormContext extends AuthenticatorContext { - FormAuthenticator getFormAuthenticator(); -} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java index d45d2aeb9a..5889b8c2d1 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java @@ -49,7 +49,7 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory { } @Override - public String getReferenceType() { + public String getReferenceCategory() { return "cookie"; } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java index 6e21a52c06..dd222fe954 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java @@ -51,7 +51,7 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory { } @Override - public String getReferenceType() { + public String getReferenceCategory() { return UserCredentialModel.TOTP; } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java index 8310d64f8b..cd23b1ef63 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java @@ -46,7 +46,7 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory { } @Override - public String getReferenceType() { + public String getReferenceCategory() { return UserCredentialModel.KERBEROS; } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java index 36b3d217a8..123c4b405f 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordFormFactory.java @@ -51,7 +51,7 @@ public class UsernamePasswordFormFactory implements AuthenticatorFactory { } @Override - public String getReferenceType() { + public String getReferenceCategory() { return UserCredentialModel.PASSWORD; } diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java new file mode 100755 index 0000000000..0fa38c566c --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java @@ -0,0 +1,116 @@ +package org.keycloak.authentication.forms; + +import org.keycloak.Config; +import org.keycloak.OAuth2Constants; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.authentication.FormActionContext; +import org.keycloak.authentication.FormAuthenticator; +import org.keycloak.authentication.FormAuthenticatorFactory; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.services.resources.LoginActionsService; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFactory { + + public static final String EXECUTION = "execution"; + public static final String FIELD_PASSWORD_CONFIRM = "password-confirm"; + public static final String FIELD_PASSWORD = "password"; + public static final String FIELD_EMAIL = "email"; + public static final String FIELD_USERNAME = "username"; + public static final String FIELD_LAST_NAME = "lastName"; + public static final String FIELD_FIRST_NAME = "firstName"; + public static final String PROVIDER_ID = "registration-page-form"; + + @Override + public void authenticate(AuthenticatorContext context) { + LoginFormsProvider registrationPage = createForm(context, context.getExecution().getId()); + context.challenge(registrationPage.createRegistration()); + + } + public URI getActionUrl(AuthenticatorContext context, String executionId, String code) { + return LoginActionsService.registrationFormProcessor(context.getUriInfo()) + .queryParam(OAuth2Constants.CODE, code) + .queryParam(EXECUTION, executionId) + .build(context.getRealm().getName()); + } + + + @Override + public Response createChallenge(FormActionContext context, MultivaluedMap formData, List errorMessages) { + LoginFormsProvider registrationPage = createForm(context, context.getFormExecution().getId()); + if (formData != null) registrationPage.setFormData(formData); + if (errorMessages != null) { + registrationPage.setErrors(errorMessages); + } + return registrationPage.createRegistration(); + } + + public LoginFormsProvider createForm(AuthenticatorContext context, String executionId) { + AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD); + boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED; + String code = context.generateAccessCode(); + URI actionUrl = getActionUrl(context, executionId, code); + return context.getSession().getProvider(LoginFormsProvider.class) + .setAttribute("passwordRequired", passwordRequired) + .setActionUri(actionUrl) + .setClientSessionCode(code); + } + + @Override + public void close() { + + } + + @Override + public String getDisplayType() { + return "Registration Page"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[0]; + } + + @Override + public FormAuthenticator create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java new file mode 100755 index 0000000000..565d7d3a86 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java @@ -0,0 +1,121 @@ +package org.keycloak.authentication.forms; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.authentication.FormAction; +import org.keycloak.authentication.FormActionContext; +import org.keycloak.authentication.FormActionFactory; +import org.keycloak.authentication.FormAuthenticator; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.services.messages.Messages; +import org.keycloak.services.validation.Validation; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RegistrationPasswordValidation implements FormAction, FormActionFactory { + public static final String PROVIDER_ID = "password-validation-action"; + + @Override + public void authenticate(FormActionContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + List errors = new ArrayList<>(); + context.getEvent().detail(Details.REGISTER_METHOD, "form"); + if (Validation.isBlank(formData.getFirst(RegistrationPage.FIELD_PASSWORD))) { + errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, Messages.MISSING_PASSWORD)); + } else if (!formData.getFirst(RegistrationPage.FIELD_PASSWORD).equals(formData.getFirst(RegistrationPage.FIELD_PASSWORD_CONFIRM))) { + errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM)); + } + if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) { + PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD)); + if (err != null) + errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters())); + } + + if (errors.size() > 0) { + context.getEvent().error(Errors.INVALID_REGISTRATION); + formData.remove(RegistrationPage.FIELD_PASSWORD); + formData.remove(RegistrationPage.FIELD_PASSWORD_CONFIRM); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + } else { + context.success(); + } + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void close() { + + } + + @Override + public String getDisplayType() { + return "Password Validation"; + } + + @Override + public String getReferenceCategory() { + return UserCredentialModel.PASSWORD; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[0]; + } + + @Override + public FormAction create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java new file mode 100755 index 0000000000..75d9cf36b9 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java @@ -0,0 +1,136 @@ +package org.keycloak.authentication.forms; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.authentication.FormAction; +import org.keycloak.authentication.FormActionContext; +import org.keycloak.authentication.FormActionFactory; +import org.keycloak.authentication.FormAuthenticator; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.services.messages.Messages; +import org.keycloak.services.validation.Validation; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RegistrationProfileValidation implements FormAction, FormActionFactory { + public static final String PROVIDER_ID = "profile-validation-action"; + + + @Override + public void authenticate(FormActionContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + List errors = new ArrayList<>(); + + context.getEvent().detail(Details.REGISTER_METHOD, "form"); + String eventError = Errors.INVALID_REGISTRATION; + + if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_FIRST_NAME)))) { + errors.add(new FormMessage(RegistrationPage.FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME)); + } + + if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_LAST_NAME)))) { + errors.add(new FormMessage(RegistrationPage.FIELD_LAST_NAME, Messages.MISSING_LAST_NAME)); + } + + String email = formData.getFirst(Validation.FIELD_EMAIL); + if (Validation.isBlank(email)) { + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL)); + } else if (!Validation.isEmailValid(email)) { + formData.remove(Validation.FIELD_EMAIL); + context.getEvent().detail(Details.EMAIL, email); + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); + } + + if (context.getSession().users().getUserByEmail(email, context.getRealm()) != null) { + eventError = Errors.EMAIL_IN_USE; + formData.remove(Validation.FIELD_EMAIL); + context.getEvent().detail(Details.EMAIL, email); + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.EMAIL_EXISTS)); + } + + if (errors.size() > 0) { + context.getEvent().error(eventError); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + + } else { + context.success(); + } + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void close() { + + } + + @Override + public String getDisplayType() { + return "Profile Validation"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[0]; + } + + @Override + public FormAction create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java new file mode 100755 index 0000000000..e218c54e15 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java @@ -0,0 +1,141 @@ +package org.keycloak.authentication.forms; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.authentication.FormAction; +import org.keycloak.authentication.FormActionContext; +import org.keycloak.authentication.FormActionFactory; +import org.keycloak.authentication.FormAuthenticator; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.resources.AttributeFormDataProcessor; +import org.keycloak.services.validation.Validation; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RegistrationUserCreation implements FormAction, FormActionFactory { + + public static final String PROVIDER_ID = "registration-user-creation"; + + @Override + public void authenticate(FormActionContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + String email = formData.getFirst(Validation.FIELD_EMAIL); + String username = formData.getFirst(RegistrationPage.FIELD_USERNAME); + if (context.getRealm().isRegistrationEmailAsUsername()) { + username = formData.getFirst(RegistrationPage.FIELD_EMAIL); + } + context.getEvent().detail(Details.USERNAME, username) + .detail(Details.REGISTER_METHOD, "form") + .detail(Details.EMAIL, email) + ; + UserModel user = context.getSession().users().addUser(context.getRealm(), username); + user.setEnabled(true); + user.setFirstName(formData.getFirst("firstName")); + user.setLastName(formData.getFirst("lastName")); + + user.setEmail(email); + context.getClientSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username); + AttributeFormDataProcessor.process(formData, context.getRealm(), user); + context.setUser(user); + AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD); + boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED; + if (passwordRequired) { + String password = formData.getFirst(RegistrationPage.FIELD_PASSWORD); + UserCredentialModel credentials = new UserCredentialModel(); + credentials.setType(CredentialRepresentation.PASSWORD); + credentials.setValue(password); + + try { + context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password"))); + } catch (Exception me) { + user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + } + } + context.getEvent().user(user); + context.success(); + context.getEvent().success(); + context.newEvent().event(EventType.LOGIN); + context.getEvent().client(context.getClientSession().getClient().getClientId()) + .detail(Details.REDIRECT_URI, context.getClientSession().getRedirectUri()) + .detail(Details.AUTH_METHOD, context.getClientSession().getAuthMethod()); + String authType = context.getClientSession().getNote(Details.AUTH_TYPE); + if (authType != null) { + context.getEvent().detail(Details.AUTH_TYPE, authType); + } + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void close() { + + } + + @Override + public String getDisplayType() { + return "Registration User Creation"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[0]; + } + + @Override + public FormAction create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java new file mode 100755 index 0000000000..be406e3187 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java @@ -0,0 +1,152 @@ +package org.keycloak.authentication.forms; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.authentication.FormAction; +import org.keycloak.authentication.FormActionContext; +import org.keycloak.authentication.FormActionFactory; +import org.keycloak.authentication.FormAuthenticator; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.services.messages.Messages; +import org.keycloak.services.validation.Validation; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RegistrationUsernameValidation implements FormAction, FormActionFactory { + + + public static final String PROVIDER_ID = "username-validation-action"; + + @Override + public void authenticate(FormActionContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + List errors = new ArrayList<>(); + context.getEvent().detail(Details.REGISTER_METHOD, "form"); + + String email = formData.getFirst(Validation.FIELD_EMAIL); + String username = formData.getFirst(RegistrationPage.FIELD_USERNAME); + context.getEvent().detail(Details.USERNAME, username); + context.getEvent().detail(Details.EMAIL, email); + + String usernameField = RegistrationPage.FIELD_USERNAME; + if (context.getRealm().isRegistrationEmailAsUsername()) { + username = email; + context.getEvent().detail(Details.USERNAME, username); + usernameField = RegistrationPage.FIELD_EMAIL; + if (Validation.isBlank(email)) { + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL)); + } else if (!Validation.isEmailValid(email)) { + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); + formData.remove(Validation.FIELD_EMAIL); + } + if (errors.size() > 0) { + context.getEvent().error(Errors.INVALID_REGISTRATION); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + } + if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) { + context.getEvent().error(Errors.USERNAME_IN_USE); + formData.remove(Validation.FIELD_EMAIL); + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.USERNAME_EXISTS)); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + } + } else { + if (Validation.isBlank(username)) { + context.getEvent().error(Errors.INVALID_REGISTRATION); + errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME)); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + } + + } + if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) { + context.getEvent().error(Errors.USERNAME_IN_USE); + errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS)); + formData.remove(Validation.FIELD_USERNAME); + formData.remove(Validation.FIELD_EMAIL); + Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); + context.challenge(challenge); + return; + + } + context.success(); + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void close() { + + } + + @Override + public String getDisplayType() { + return "Username Validation"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[0]; + } + + @Override + public FormAction create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index e938732b97..c2a42eb89a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -249,6 +249,7 @@ public class AuthorizationEndpoint { } protected Response browserAuthentication(String accessCode) { + this.event.event(EventType.LOGIN); List identityProviders = realm.getIdentityProviders(); for (IdentityProviderModel identityProvider : identityProviders) { if (identityProvider.isAuthenticateByDefault()) { diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 190750f2f9..c672235f8c 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -29,7 +29,7 @@ import java.util.Set; public class DefaultKeycloakSession implements KeycloakSession { private final DefaultKeycloakSessionFactory factory; - private final Map providers = new HashMap(); + private final Map providers = new HashMap<>(); private final List closable = new LinkedList(); private final DefaultKeycloakTransactionManager transactionManager; private RealmProvider model; 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 57b16a7785..9f223d2f7b 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -466,6 +466,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } protected Response browserAuthentication(ClientSessionModel clientSession, String errorMessage) { + this.event.event(EventType.LOGIN); AuthenticationFlowModel flow = realmModel.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); String flowId = flow.getId(); AuthenticationProcessor processor = new AuthenticationProcessor(); 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 44910cb73b..79d20d5011 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -130,6 +130,10 @@ public class LoginActionsService { return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "authenticateForm"); } + public static UriBuilder registrationFormProcessor(UriInfo uriInfo) { + return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "processRegister"); + } + public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) { return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService"); } @@ -269,6 +273,10 @@ public class LoginActionsService { protected Response processAuthentication(String execution, ClientSessionModel clientSession) { String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW; + return processFlow(execution, clientSession, flowAlias); + } + + protected Response processFlow(String execution, ClientSessionModel clientSession, String flowAlias) { AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); AuthenticationProcessor processor = new AuthenticationProcessor(); processor.setClientSession(clientSession) @@ -313,6 +321,12 @@ public class LoginActionsService { return processAuthentication(execution, clientSession); } + protected Response processRegistration(String execution, ClientSessionModel clientSession) { + String flowAlias = DefaultAuthenticationFlows.REGISTRATION_FLOW; + return processFlow(execution, clientSession, flowAlias); + } + + /** * protocol independent registration page entry point @@ -322,7 +336,8 @@ public class LoginActionsService { */ @Path("registration") @GET - public Response registerPage(@QueryParam("code") String code) { + public Response registerPage(@QueryParam("code") String code, + @QueryParam("execution") String execution) { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); @@ -330,7 +345,7 @@ public class LoginActionsService { } Checks checks = new Checks(); - if (!checks.check(code)) { + if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) { return checks.response; } event.detail(Details.CODE_ID, code); @@ -340,10 +355,7 @@ public class LoginActionsService { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return session.getProvider(LoginFormsProvider.class) - .setClientSessionCode(clientSessionCode.getCode()) - .setAttribute("passwordRequired", isPasswordRequired()) - .createRegistration(); + return processRegistration(execution, clientSession); } @@ -353,143 +365,24 @@ public class LoginActionsService { * @param code * @return */ - @Path("request/registration") + @Path("registration") @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response processRegister(@QueryParam("code") String code) { + public Response processRegister(@QueryParam("code") String code, + @QueryParam("execution") String execution) { event.event(EventType.REGISTER); Checks checks = new Checks(); if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) { return checks.response; } - if (!realm.isRegistrationAllowed()) { + if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED); } - MultivaluedMap formData = request.getDecodedFormParameters(); - String username = formData.getFirst(Validation.FIELD_USERNAME); - String email = formData.getFirst(Validation.FIELD_EMAIL); - if (realm.isRegistrationEmailAsUsername()) { - username = email; - formData.putSingle(AuthenticationManager.FORM_USERNAME, username); - } ClientSessionCode clientCode = checks.clientCode; ClientSessionModel clientSession = clientCode.getClientSession(); - event.client(clientSession.getClient()) - .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) - .detail(Details.RESPONSE_TYPE, "code") - .detail(Details.USERNAME, username) - .detail(Details.EMAIL, email) - .detail(Details.REGISTER_METHOD, "form"); - List requiredCredentialTypes = new LinkedList<>(); - boolean passwordRequired = isPasswordRequired(); - if (passwordRequired) { - requiredCredentialTypes.add(CredentialRepresentation.PASSWORD); - } - - // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm - List errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy()); - - - if (errors != null && !errors.isEmpty()) { - event.error(Errors.INVALID_REGISTRATION); - return session.getProvider(LoginFormsProvider.class) - .setErrors(errors) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .setAttribute("passwordRequired", isPasswordRequired()) - .createRegistration(); - } - - // Validate that user with this username doesn't exist in realm or any federation provider - if (session.users().getUserByUsername(username, realm) != null) { - event.error(Errors.USERNAME_IN_USE); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.USERNAME_EXISTS) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .setAttribute("passwordRequired", isPasswordRequired()) - .createRegistration(); - } - - // Validate that user with this email doesn't exist in realm or any federation provider - if (email != null && session.users().getUserByEmail(email, realm) != null) { - event.error(Errors.EMAIL_IN_USE); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.EMAIL_EXISTS) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .setAttribute("passwordRequired", isPasswordRequired()) - .createRegistration(); - } - - UserModel user = session.users().addUser(realm, username); - user.setEnabled(true); - user.setFirstName(formData.getFirst("firstName")); - user.setLastName(formData.getFirst("lastName")); - - user.setEmail(email); - - if (passwordRequired) { - UserCredentialModel credentials = new UserCredentialModel(); - credentials.setType(CredentialRepresentation.PASSWORD); - credentials.setValue(formData.getFirst("password")); - - boolean passwordUpdateSuccessful; - String passwordUpdateError = null; - Object[] passwordUpdateErrorParameters = null; - try { - session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password"))); - passwordUpdateSuccessful = true; - } catch (ModelException me) { - passwordUpdateSuccessful = false; - passwordUpdateError = me.getMessage(); - passwordUpdateErrorParameters = me.getParameters(); - } catch (Exception ape) { - passwordUpdateSuccessful = false; - passwordUpdateError = ape.getMessage(); - } - - // User already registered, but force him to update password - if (!passwordUpdateSuccessful) { - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - return session.getProvider(LoginFormsProvider.class) - .setError(passwordUpdateError, passwordUpdateErrorParameters) - .setClientSessionCode(clientCode.getCode()) - .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); - } - } - clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username); - AttributeFormDataProcessor.process(formData, realm, user); - - event.user(user).success(); - event = new EventBuilder(realm, session, clientConnection); - clientSession.setAuthenticatedUser(user); - AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); - AuthenticationProcessor processor = new AuthenticationProcessor(); - processor.setClientSession(clientSession) - .setFlowId(flow.getId()) - .setConnection(clientConnection) - .setEventBuilder(event) - .setProtector(authManager.getProtector()) - .setRealm(realm) - .setAction(AbstractFormAuthenticator.REGISTRATION_FORM_ACTION) - .setSession(session) - .setUriInfo(uriInfo) - .setRequest(request); - - try { - return processor.authenticate(); - } catch (Exception e) { - return processor.handleBrowserException(e); - } - } - - public boolean isPasswordRequired() { - AuthenticationFlowModel browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); - return AuthenticatorUtil.isRequired(realm, browserFlow.getId(), UsernamePasswordFormFactory.PROVIDER_ID); + return processRegistration(execution, clientSession); } /** diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 7f1edbd61e..a78baaa657 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -8,7 +8,6 @@ import org.keycloak.authentication.AuthenticatorFactory; import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; @@ -139,8 +138,8 @@ public class AuthenticationManagementResource { rep.setSubFlow(true); } AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, execution.getAuthenticator()); - if (factory.getReferenceType() == null) continue; - rep.setReferenceType(factory.getReferenceType()); + if (factory.getReferenceCategory() == null) continue; + rep.setReferenceType(factory.getReferenceCategory()); rep.setConfigurable(factory.isConfigurable()); for (AuthenticationExecutionModel.Requirement choice : factory.getRequirementChoices()) { rep.getRequirementChoices().add(choice.name()); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory new file mode 100755 index 0000000000..47a023c5f1 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory @@ -0,0 +1,4 @@ +org.keycloak.authentication.forms.RegistrationPasswordValidation +org.keycloak.authentication.forms.RegistrationProfileValidation +org.keycloak.authentication.forms.RegistrationUserCreation +org.keycloak.authentication.forms.RegistrationUsernameValidation \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory new file mode 100755 index 0000000000..4be1af4c28 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory @@ -0,0 +1 @@ +org.keycloak.authentication.forms.RegistrationPage \ No newline at end of file diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java index e6851b5324..3d70977937 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -167,7 +167,6 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory { .user(user != null ? user.getId() : null) .detail(Details.USERNAME, username) .detail(Details.EMAIL, email) - .detail(Details.RESPONSE_TYPE, "code") .detail(Details.REGISTER_METHOD, "form") .detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index cf2fd77d1a..7c3143aeb9 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -80,7 +80,9 @@ public class RegisterTest { registerPage.assertCurrent(); Assert.assertEquals("Username already exists.", registerPage.getError()); - events.expectRegister("test-user@localhost", "registerExistingUser@email").user((String) null).error("username_in_use").assertEvent(); + events.expectRegister("test-user@localhost", "registerExistingUser@email") + .removeDetail(Details.EMAIL) + .user((String) null).error("username_in_use").assertEvent(); } @Test @@ -94,7 +96,10 @@ public class RegisterTest { registerPage.assertCurrent(); Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError()); - events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email").user((String) null).error("invalid_registration").assertEvent(); + events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .user((String) null).error("invalid_registration").assertEvent(); } @Test @@ -108,7 +113,10 @@ public class RegisterTest { registerPage.assertCurrent(); Assert.assertEquals("Please specify password.", registerPage.getError()); - events.expectRegister("registerUserMissingPassword", "registerUserMissingPassword@email").user((String) null).error("invalid_registration").assertEvent(); + events.expectRegister("registerUserMissingPassword", "registerUserMissingPassword@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .user((String) null).error("invalid_registration").assertEvent(); } @Test @@ -130,7 +138,10 @@ public class RegisterTest { registerPage.assertCurrent(); Assert.assertEquals("Invalid password: minimum length 8.", registerPage.getError()); - events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").user((String) null).error("invalid_registration").assertEvent(); + events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .user((String) null).error("invalid_registration").assertEvent(); registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "password", "password"); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); @@ -159,7 +170,10 @@ public class RegisterTest { registerPage.assertCurrent(); Assert.assertEquals("Please specify username.", registerPage.getError()); - events.expectRegister(null, "registerUserMissingUsername@email").removeDetail("username").error("invalid_registration").assertEvent(); + events.expectRegister(null, "registerUserMissingUsername@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .error("invalid_registration").assertEvent(); } @Test @@ -171,12 +185,15 @@ public class RegisterTest { registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password"); registerPage.assertCurrent(); Assert.assertEquals("Please specify email.", registerPage.getError()); - events.expectRegister("registerUserMissingEmail", null).removeDetail("email").error("invalid_registration").assertEvent(); + events.expectRegister("registerUserMissingEmail", null) + .removeDetail("email") + .error("invalid_registration").assertEvent(); registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password"); registerPage.assertCurrent(); Assert.assertEquals("Invalid email address.", registerPage.getError()); - events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent(); + events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail") + .error("invalid_registration").assertEvent(); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java index 557fa7ef99..6a03f3e540 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java @@ -5,7 +5,6 @@ import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory; import org.keycloak.authentication.authenticators.UsernamePasswordFormFactory; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.representations.idm.CredentialRepresentation; @@ -29,7 +28,7 @@ public class CredentialHelper { public static void setCredentialRequirement(String type, RealmModel realm, AuthenticationExecutionModel.Requirement requirement) { if (type.equals(CredentialRepresentation.TOTP)) { String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID; - String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW; + String flowAlias = DefaultAuthenticationFlows.LOGIN_FORMS_FLOW; authenticationRequirement(realm, providerId, flowAlias, requirement); } else if (type.equals(CredentialRepresentation.KERBEROS)) { String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID; @@ -37,7 +36,7 @@ public class CredentialHelper { authenticationRequirement(realm, providerId, flowAlias, requirement); } else if (type.equals(CredentialRepresentation.PASSWORD)) { String providerId = UsernamePasswordFormFactory.PROVIDER_ID; - String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW; + String flowAlias = DefaultAuthenticationFlows.LOGIN_FORMS_FLOW; authenticationRequirement(realm, providerId, flowAlias, requirement); } }