commit
45c5a2ae78
33 changed files with 977 additions and 246 deletions
|
@ -56,6 +56,9 @@ public class UrlBean {
|
|||
}
|
||||
|
||||
public String getRegistrationAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.toString();
|
||||
}
|
||||
return Urls.realmRegisterAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
//
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -167,10 +168,31 @@ public class AuthenticationProcessor {
|
|||
Status status;
|
||||
Response challenge;
|
||||
Error error;
|
||||
List<AuthenticationExecutionModel> currentExecutions;
|
||||
|
||||
private Result(AuthenticationExecutionModel execution, Authenticator authenticator) {
|
||||
private Result(AuthenticationExecutionModel execution, Authenticator authenticator, List<AuthenticationExecutionModel> 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<AuthenticationExecutionModel> 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<AuthenticationExecutionModel> executions) {
|
||||
return new Result(model, authenticator, executions);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -9,24 +9,7 @@ import org.keycloak.provider.ProviderFactory;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfiguredProvider {
|
||||
public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, 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();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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();
|
||||
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -15,11 +17,16 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
|||
Response alternativeChallenge = null;
|
||||
AuthenticationExecutionModel challengedAlternativeExecution = null;
|
||||
boolean alternativeSuccessful = false;
|
||||
Iterator<AuthenticationExecutionModel> executions;
|
||||
List<AuthenticationExecutionModel> executions;
|
||||
Iterator<AuthenticationExecutionModel> 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
|
||||
} 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
12
services/src/main/java/org/keycloak/authentication/FormActionContext.java
Executable file
12
services/src/main/java/org/keycloak/authentication/FormActionContext.java
Executable file
|
@ -0,0 +1,12 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface FormActionContext extends AuthenticatorContext {
|
||||
FormAuthenticator getFormAuthenticator();
|
||||
AuthenticationExecutionModel getFormExecution();
|
||||
}
|
|
@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface FormActionFactory extends ProviderFactory<FormAction> {
|
||||
public interface FormActionFactory extends ProviderFactory<FormAction>, ConfigurableAuthenticatorFactory {
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -23,26 +25,38 @@ import java.util.List;
|
|||
*/
|
||||
public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||
AuthenticationProcessor processor;
|
||||
AuthenticationExecutionModel execution;
|
||||
AuthenticationExecutionModel formExecution;
|
||||
private final List<AuthenticationExecutionModel> 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<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
||||
List<FormAction> 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<String, ClientSessionModel.ExecutionStatus> 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<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
||||
Response response = processResult(executionStatus, result, formExecution);
|
||||
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
|
||||
processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
public Response processResult(AuthenticatorContext result, AuthenticationExecutionModel execution) {
|
||||
public Response processResult(Map<String, ClientSessionModel.ExecutionStatus> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -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<String, String> formData, List<FormMessage> errorMessages);
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@ import org.keycloak.provider.ProviderFactory;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface FormAuthenticatorFactory extends ProviderFactory<FormAuthenticator> {
|
||||
public interface FormAuthenticatorFactory extends ProviderFactory<FormAuthenticator>, ConfigurableAuthenticatorFactory {
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface FormContext extends AuthenticatorContext {
|
||||
FormAuthenticator getFormAuthenticator();
|
||||
}
|
|
@ -49,7 +49,7 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
public String getReferenceCategory() {
|
||||
return "cookie";
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.TOTP;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.KERBEROS;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class UsernamePasswordFormFactory implements AuthenticatorFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.PASSWORD;
|
||||
}
|
||||
|
||||
|
|
116
services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
Executable file
116
services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
Executable file
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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<String, String> formData, List<FormMessage> 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;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<FormMessage> 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;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<FormMessage> 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;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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<String, String> 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;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<FormMessage> 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;
|
||||
}
|
||||
}
|
|
@ -249,6 +249,7 @@ public class AuthorizationEndpoint {
|
|||
}
|
||||
|
||||
protected Response browserAuthentication(String accessCode) {
|
||||
this.event.event(EventType.LOGIN);
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isAuthenticateByDefault()) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.util.Set;
|
|||
public class DefaultKeycloakSession implements KeycloakSession {
|
||||
|
||||
private final DefaultKeycloakSessionFactory factory;
|
||||
private final Map<Integer, Provider> providers = new HashMap<Integer, Provider>();
|
||||
private final Map<Integer, Provider> providers = new HashMap<>();
|
||||
private final List<Provider> closable = new LinkedList<Provider>();
|
||||
private final DefaultKeycloakTransactionManager transactionManager;
|
||||
private RealmProvider model;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,10 +365,10 @@ 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())) {
|
||||
|
@ -367,129 +379,10 @@ public class LoginActionsService {
|
|||
return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
MultivaluedMap<String, String> 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<String> 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<FormMessage> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
org.keycloak.authentication.forms.RegistrationPasswordValidation
|
||||
org.keycloak.authentication.forms.RegistrationProfileValidation
|
||||
org.keycloak.authentication.forms.RegistrationUserCreation
|
||||
org.keycloak.authentication.forms.RegistrationUsernameValidation
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.authentication.forms.RegistrationPage
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue