form action refactor

This commit is contained in:
Bill Burke 2015-07-01 14:07:02 -04:00
parent a1c612f833
commit 39aa09ca36
30 changed files with 554 additions and 504 deletions

View file

@ -13,6 +13,7 @@ kerberosNotConfigured=Kerberos Not Configured
kerberosNotConfiguredTitle=Kerberos Not Configured
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
kerberosNotSetUp=Kerberos is not set up. You cannot login.
recaptchaFailed=Recaptcha Failed
registerWithTitle=Registrierung bei {0}
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>

View file

@ -30,6 +30,7 @@ codeSuccessTitle=Success code
codeErrorTitle=Error code\: {0}
termsTitle=Terms and Conditions
termsTitleHtml=Terms and Conditions
recaptchaFailed=Recaptcha Failed
noAccount=New user?
username=Username

View file

@ -13,6 +13,7 @@ bypassKerberos=Your browser is not set up for Kerberos login. Please click cont
kerberosNotSetUp=Kerberos is not set up. You cannot login.
kerberosNotConfigured=Kerberos Not Configured
kerberosNotConfiguredTitle=Kerberos Not Configured
recaptchaFailed=Recaptcha Failed
registerWithTitle=Registrati come {0}
registerWithTitleHtml=Registrati come <strong>{0}</strong>

View file

@ -13,6 +13,7 @@ bypassKerberos=Your browser is not set up for Kerberos login. Please click cont
kerberosNotSetUp=Kerberos is not set up. You cannot login.
kerberosNotConfigured=Kerberos Not Configured
kerberosNotConfiguredTitle=Kerberos Not Configured
recaptchaFailed=Recaptcha Failed
registerWithTitle=Registre-se com {0}
registerWithTitleHtml=Registre-se com <strong>{0}</strong>

View file

@ -107,7 +107,13 @@
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
</div>
</div>
<#if recaptchaRequired??>
<div class="form-group">
<div class="${properties.kcInputWrapperClass!}">
<div class="g-recaptcha" data-sitekey="${recaptchaSiteKey}"></div>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">

View file

@ -21,6 +21,11 @@
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
</#list>
</#if>
<#if scripts??>
<#list scripts as script>
<script src="${script}" type="text/javascript"></script>
</#list>
</#if>
</head>
<body class="${properties.kcBodyClass!}">

View file

@ -63,6 +63,8 @@ public interface LoginFormsProvider extends Provider {
*/
public LoginFormsProvider setErrors(List<FormMessage> messages);
LoginFormsProvider addError(FormMessage errorMessage);
public LoginFormsProvider setSuccess(String message, Object ... parameters);
public LoginFormsProvider setUser(UserModel user);

View file

@ -3,7 +3,6 @@ package org.keycloak.login.freemarker;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@ -39,8 +38,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
@ -52,6 +51,7 @@ import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -445,11 +445,27 @@ import java.util.concurrent.TimeUnit;
@Override
public LoginFormsProvider setErrors(List<FormMessage> messages) {
if (messages == null) return this;
this.messageType = MessageType.ERROR;
this.messages = new ArrayList<>(messages);
return this;
}
@Override
public LoginFormsProvider addError(FormMessage errorMessage) {
if (this.messageType != MessageType.ERROR) {
this.messageType = null;
this.messages = null;
}
if (messages == null) {
this.messageType = MessageType.ERROR;
this.messages = new LinkedList<>();
}
this.messages.add(errorMessage);
return this;
}
@Override
public FreeMarkerLoginFormsProvider setSuccess(String message, Object... parameters) {
setMessage(MessageType.SUCCESS, message, parameters);

View file

@ -34,7 +34,9 @@ public class DefaultAuthenticationFlows {
registrationFormFlow.setProviderId("form-flow");
registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
AuthenticationExecutionModel execution;
execution = new AuthenticationExecutionModel();
execution.setParentFlow(registrationFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("registration-page-form");
@ -47,7 +49,7 @@ public class DefaultAuthenticationFlows {
execution = new AuthenticationExecutionModel();
execution.setParentFlow(registrationFormFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("username-validation-action");
execution.setAuthenticator("registration-user-creation");
execution.setPriority(20);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(false);
@ -56,16 +58,7 @@ public class DefaultAuthenticationFlows {
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.setAuthenticator("registration-profile-action");
execution.setPriority(40);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(false);
@ -74,12 +67,22 @@ public class DefaultAuthenticationFlows {
execution = new AuthenticationExecutionModel();
execution.setParentFlow(registrationFormFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("registration-user-creation");
execution.setAuthenticator("registration-password-action");
execution.setPriority(50);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(registrationFormFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
execution.setAuthenticator("registration-recaptcha-action");
execution.setPriority(60);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}

View file

@ -227,7 +227,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public void setAuthenticatedUser(UserModel user) {
entity.setAuthUserId(user.getId());
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
update();
}

View file

@ -306,6 +306,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public void setAuthenticatedUser(UserModel user) {
entity.setUserId(user.getId());
if (user == null) entity.setUserId(null);
else entity.setUserId(user.getId());
}
}

View file

@ -193,7 +193,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public void setAuthenticatedUser(UserModel user) {
entity.setAuthUserId(user.getId());
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
}
}

View file

@ -210,7 +210,8 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
@Override
public void setAuthenticatedUser(UserModel user) {
entity.setAuthUserId(user.getId());
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
updateMongoEntity();
}

View file

@ -45,7 +45,6 @@ public class AuthenticationProcessor {
protected EventBuilder event;
protected HttpRequest request;
protected String flowId;
protected String action;
/**
* This could be an error message forwarded from brokering when the broker failed authentication
* and we want to continue authentication locally. forwardedErrorMessage can then be displayed by
@ -151,16 +150,39 @@ public class AuthenticationProcessor {
return this;
}
public AuthenticationProcessor setAction(String action) {
this.action = action;
return this;
}
public AuthenticationProcessor setForwardedErrorMessage(String forwardedErrorMessage) {
this.forwardedErrorMessage = forwardedErrorMessage;
return this;
}
public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode();
}
public EventBuilder newEvent() {
this.event = new EventBuilder(realm, session, connection);
return this.event;
}
public EventBuilder getEvent() {
return event;
}
public HttpRequest getRequest() {
return request;
}
public void setAutheticatedUser(UserModel user) {
UserModel previousUser = clientSession.getAuthenticatedUser();
if (previousUser != null && !user.getId().equals(previousUser.getId()))
throw new AuthException(Error.USER_CONFLICT);
validateUser(user);
getClientSession().setAuthenticatedUser(user);
}
private class Result implements AuthenticatorContext {
AuthenticatorConfigModel authenticatorConfig;
AuthenticationExecutionModel execution;
@ -178,8 +200,7 @@ public class AuthenticationProcessor {
@Override
public EventBuilder newEvent() {
AuthenticationProcessor.this.event = new EventBuilder(realm, session, connection);
return AuthenticationProcessor.this.event;
return AuthenticationProcessor.this.newEvent();
}
@Override
@ -213,11 +234,6 @@ public class AuthenticationProcessor {
return authenticatorConfig;
}
@Override
public String getAction() {
return AuthenticationProcessor.this.action;
}
@Override
public Authenticator getAuthenticator() {
return authenticator;
@ -288,11 +304,7 @@ public class AuthenticationProcessor {
@Override
public void setUser(UserModel user) {
UserModel previousUser = getUser();
if (previousUser != null && !user.getId().equals(previousUser.getId()))
throw new AuthException(Error.USER_CONFLICT);
validateUser(user);
getClientSession().setAuthenticatedUser(user);
setAutheticatedUser(user);
}
@Override
@ -347,11 +359,10 @@ public class AuthenticationProcessor {
@Override
public String generateAccessCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode();
return generateCode();
}
@Override
public Response getChallenge() {
return challenge;

View file

@ -27,16 +27,6 @@ public interface AuthenticatorContext {
void setExecution(AuthenticationExecutionModel execution);
AuthenticatorConfigModel getAuthenticatorConfig();
String getAction();
Authenticator getAuthenticator();
void setAuthenticator(Authenticator authenticator);
AuthenticationProcessor.Status getStatus();
UserModel getUser();
void setUser(UserModel user);
@ -55,17 +45,6 @@ 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);
void challenge(Response challenge);
void forceChallenge(Response challenge);
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
void attempted();
/**
* This could be an error message forwarded from brokering when the broker failed authentication
@ -81,6 +60,27 @@ public interface AuthenticatorContext {
*/
String generateAccessCode();
AuthenticatorConfigModel getAuthenticatorConfig();
Authenticator getAuthenticator();
void setAuthenticator(Authenticator authenticator);
AuthenticationProcessor.Status getStatus();
AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory);
void success();
void failure(AuthenticationProcessor.Error error);
void failure(AuthenticationProcessor.Error error, Response response);
void challenge(Response challenge);
void forceChallenge(Response challenge);
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
void attempted();
Response getChallenge();
AuthenticationProcessor.Error getError();

View file

@ -1,5 +1,6 @@
package org.keycloak.authentication;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -10,7 +11,8 @@ import org.keycloak.provider.Provider;
* @version $Revision: 1 $
*/
public interface FormAction extends Provider {
void authenticate(FormActionContext context);
void validate(ValidationContext context);
void success(FormContext context);
boolean requiresUser();
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
@ -21,4 +23,6 @@ public interface FormAction extends Provider {
*/
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
void buildPage(FormContext context, LoginFormsProvider form);
}

View file

@ -1,12 +0,0 @@
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();
}

View file

@ -2,7 +2,9 @@ package org.keycloak.authentication;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.EventBuilder;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientSessionModel;
@ -10,10 +12,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.resources.LoginActionsService;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -37,183 +43,99 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
formAuthenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
}
private class FormContext implements FormActionContext {
protected AuthenticatorContext delegate;
private class FormContextImpl implements FormContext {
AuthenticationExecutionModel executionModel;
AuthenticatorConfigModel authenticatorConfig;
private FormContext(AuthenticatorContext delegate) {
this.delegate = delegate;
private FormContextImpl(AuthenticationExecutionModel executionModel) {
this.executionModel = executionModel;
}
@Override
public EventBuilder newEvent() {
return delegate.newEvent();
}
@Override
public FormAuthenticator getFormAuthenticator() {
return formAuthenticator;
}
@Override
public AuthenticationExecutionModel getFormExecution() {
return formExecution;
return processor.newEvent();
}
@Override
public EventBuilder getEvent() {
return delegate.getEvent();
return processor.getEvent();
}
@Override
public AuthenticationExecutionModel getExecution() {
return delegate.getExecution();
}
@Override
public void setExecution(AuthenticationExecutionModel execution) {
delegate.setExecution(execution);
return executionModel;
}
@Override
public AuthenticatorConfigModel getAuthenticatorConfig() {
return delegate.getAuthenticatorConfig();
}
@Override
public String getAction() {
return delegate.getAction();
}
@Override
public Authenticator getAuthenticator() {
return delegate.getAuthenticator();
}
@Override
public void setAuthenticator(Authenticator authenticator) {
delegate.setAuthenticator(authenticator);
}
@Override
public AuthenticationProcessor.Status getStatus() {
return delegate.getStatus();
if (executionModel.getAuthenticatorConfig() == null) return null;
if (authenticatorConfig != null) return authenticatorConfig;
authenticatorConfig = getRealm().getAuthenticatorConfigById(executionModel.getAuthenticatorConfig());
return authenticatorConfig;
}
@Override
public UserModel getUser() {
return delegate.getUser();
return getClientSession().getAuthenticatedUser();
}
@Override
public void setUser(UserModel user) {
delegate.setUser(user);
processor.setAutheticatedUser(user);
}
@Override
public RealmModel getRealm() {
return delegate.getRealm();
return processor.getRealm();
}
@Override
public ClientSessionModel getClientSession() {
return delegate.getClientSession();
}
@Override
public void attachUserSession(UserSessionModel userSession) {
delegate.attachUserSession(userSession);
return processor.getClientSession();
}
@Override
public ClientConnection getConnection() {
return delegate.getConnection();
return processor.getConnection();
}
@Override
public UriInfo getUriInfo() {
return delegate.getUriInfo();
return processor.getUriInfo();
}
@Override
public KeycloakSession getSession() {
return delegate.getSession();
return processor.getSession();
}
@Override
public HttpRequest getHttpRequest() {
return delegate.getHttpRequest();
return processor.getRequest();
}
}
private class ValidationContextImpl extends FormContextImpl implements ValidationContext {
FormAction action;
private ValidationContextImpl(AuthenticationExecutionModel executionModel, FormAction action) {
super(executionModel);
this.action = action;
}
boolean success;
List<FormMessage> errors = null;
MultivaluedMap<String, String> formData = null;
@Override
public BruteForceProtector getProtector() {
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;
public void validationError(MultivaluedMap<String, String> formData, List<FormMessage> errors) {
this.errors = errors;
this.formData = formData;
}
@Override
public void success() {
delegate.success();
}
@Override
public void failure(AuthenticationProcessor.Error error) {
delegate.failure(error);
}
@Override
public void failure(AuthenticationProcessor.Error error, Response response) {
delegate.failure(error, response);
}
@Override
public void challenge(Response challenge) {
delegate.challenge(challenge);
}
@Override
public void forceChallenge(Response challenge) {
delegate.forceChallenge(challenge);
}
@Override
public void failureChallenge(AuthenticationProcessor.Error error, Response challenge) {
delegate.failureChallenge(error, challenge);
}
@Override
public void attempted() {
delegate.attempted();
}
@Override
public String getForwardedErrorMessage() {
return delegate.getForwardedErrorMessage();
}
@Override
public String generateAccessCode() {
return delegate.generateAccessCode();
}
@Override
public Response getChallenge() {
return delegate.getChallenge();
}
@Override
public AuthenticationProcessor.Error getError() {
return delegate.getError();
success = true;
}
}
@ -224,7 +146,12 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
}
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
List<FormAction> requiredActions = new LinkedList<>();
List<ValidationContextImpl> successes = new LinkedList<>();
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
if (!formActionExecution.isEnabled()) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
@ -251,12 +178,20 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
}
}
AuthenticatorContext delegate = processor.createAuthenticatorContext(formActionExecution, null, formActionExecutions);
FormActionContext result = new FormContext(delegate);
action.authenticate(result);
Response challenge = processResult(executionStatus, result, formActionExecution);
if (challenge != null) return challenge;
ValidationContextImpl result = new ValidationContextImpl(formActionExecution, action);
action.validate(result);
if (result.success) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
successes.add(result);
} else {
processor.logFailure();
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return renderForm(result.formData, result.errors);
}
}
for (ValidationContextImpl context : successes) {
context.action.success(context);
}
// set status and required actions only if form is fully successful
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
@ -270,63 +205,36 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
}
public URI getActionUrl(String executionId, String code) {
return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
.queryParam(OAuth2Constants.CODE, code)
.queryParam("execution", executionId)
.build(processor.getRealm().getName());
}
@Override
public Response processFlow() {
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;
return renderForm(null, null);
}
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();
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result);
public Response renderForm(MultivaluedMap<String, String> formData, List<FormMessage> errors) {
String executionId = formExecution.getId();
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, executionId);
String code = processor.generateCode();
URI actionUrl = getActionUrl(executionId, code);
LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
.setActionUri(actionUrl)
.setClientSessionCode(code)
.setFormData(formData)
.setErrors(errors);
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
if (!formActionExecution.isEnabled()) continue;
FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
FormContext result = new FormContextImpl(formActionExecution);
action.buildPage(result, form);
}
throw new AuthenticationProcessor.AuthException(result.getError());
} else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) {
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);
} else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) {
AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
processor.logFailure();
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);
FormContext context = new FormContextImpl(formExecution);
return formAuthenticator.render(context, form);
}
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
return null;
} else {
AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
AuthenticationProcessor.logger.error("Unknown result status");
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INTERNAL_ERROR);
}
}
public Response sendChallenge(AuthenticatorContext result) {
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, formExecution.getId());
return result.getChallenge();
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.authentication;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.provider.Provider;
@ -12,6 +13,5 @@ import java.util.List;
* @version $Revision: 1 $
*/
public interface FormAuthenticator extends Provider {
void authenticate(AuthenticatorContext context);
Response createChallenge(FormActionContext context, MultivaluedMap<String, String> formData, List<FormMessage> errorMessages);
Response render(FormContext context, LoginFormsProvider form);
}

View file

@ -0,0 +1,33 @@
package org.keycloak.authentication;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import javax.ws.rs.core.UriInfo;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormContext {
EventBuilder getEvent();
EventBuilder newEvent();
AuthenticationExecutionModel getExecution();
UserModel getUser();
void setUser(UserModel user);
RealmModel getRealm();
ClientSessionModel getClientSession();
ClientConnection getConnection();
UriInfo getUriInfo();
KeycloakSession getSession();
HttpRequest getHttpRequest();
AuthenticatorConfigModel getAuthenticatorConfig();
}

View file

@ -0,0 +1,15 @@
package org.keycloak.authentication;
import org.keycloak.models.utils.FormMessage;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ValidationContext extends FormContext {
void validationError(MultivaluedMap<String, String> formData, List<FormMessage> errors);
void success();
}

View file

@ -48,10 +48,6 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
@Override
public void authenticate(AuthenticatorContext context) {
if (REGISTRATION_FORM_ACTION.equals(context.getAction()) && context.getUser() != null) {
context.success();
return;
}
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);

View file

@ -1,23 +1,15 @@
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.authentication.FormContext;
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>
@ -35,38 +27,8 @@ public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFac
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);
public Response render(FormContext context, LoginFormsProvider form) {
return form.createRegistration();
}
@Override

View file

@ -1,13 +1,13 @@
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.authentication.FormContext;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -16,11 +16,11 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.representations.idm.CredentialRepresentation;
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;
@ -28,11 +28,11 @@ 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";
public class RegistrationPassword implements FormAction, FormActionFactory {
public static final String PROVIDER_ID = "registration-password-action";
@Override
public void authenticate(FormActionContext context) {
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
List<FormMessage> errors = new ArrayList<>();
context.getEvent().detail(Details.REGISTER_METHOD, "form");
@ -51,14 +51,34 @@ public class RegistrationPasswordValidation implements FormAction, FormActionFac
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);
context.validationError(formData, errors);
return;
} else {
context.success();
}
}
@Override
public void success(FormContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String password = formData.getFirst(RegistrationPage.FIELD_PASSWORD);
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(password);
UserModel user = context.getUser();
try {
context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
} catch (Exception me) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
form.setAttribute("passwordRequired", true);
}
@Override
public boolean requiresUser() {
return false;

View file

@ -1,17 +1,16 @@
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.authentication.FormContext;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.login.LoginFormsProvider;
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;
@ -19,7 +18,6 @@ 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;
@ -27,12 +25,12 @@ 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";
public class RegistrationProfile implements FormAction, FormActionFactory {
public static final String PROVIDER_ID = "registration-profile-action";
@Override
public void authenticate(FormActionContext context) {
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
List<FormMessage> errors = new ArrayList<>();
@ -65,8 +63,7 @@ public class RegistrationProfileValidation implements FormAction, FormActionFact
if (errors.size() > 0) {
context.getEvent().error(eventError);
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
context.challenge(challenge);
context.validationError(formData, errors);
return;
} else {
@ -74,6 +71,20 @@ public class RegistrationProfileValidation implements FormAction, FormActionFact
}
}
@Override
public void success(FormContext context) {
UserModel user = context.getUser();
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
user.setFirstName(formData.getFirst(RegistrationPage.FIELD_FIRST_NAME));
user.setLastName(formData.getFirst(RegistrationPage.FIELD_LAST_NAME));
user.setEmail(formData.getFirst(RegistrationPage.FIELD_EMAIL));
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
// complete
}
@Override
public boolean requiresUser() {
return false;

View file

@ -0,0 +1,169 @@
package org.keycloak.authentication.forms;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.FormAction;
import org.keycloak.authentication.FormActionFactory;
import org.keycloak.authentication.FormContext;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.login.LoginFormsProvider;
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 org.keycloak.util.JsonSerialization;
import javax.ws.rs.core.MultivaluedMap;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RegistrationRecaptcha implements FormAction, FormActionFactory {
public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";
public static final String RECAPTCHA_REFERENCE_CATEGORY = "recaptcha";
protected static Logger logger = Logger.getLogger(RegistrationRecaptcha.class);
public static final String PROVIDER_ID = "registration-recaptcha-action";
@Override
public String getDisplayType() {
return "Recaptcha";
}
@Override
public String getReferenceCategory() {
return RECAPTCHA_REFERENCE_CATEGORY;
}
@Override
public boolean isConfigurable() {
return true;
}
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
form.setAttribute("recaptchaRequired", true);
form.setAttribute("recaptchaSiteKey", "6LcFEAkTAAAAAOaY-5RJk3zIYw4AalNtqfac27Bn");
List<String> scripts = new LinkedList<>();
scripts.add("https://www.google.com/recaptcha/api.js");
form.setAttribute("scripts", scripts);
}
@Override
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
List<FormMessage> errors = new ArrayList<>();
boolean success = false;
context.getEvent().detail(Details.REGISTER_METHOD, "form");
String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
if (Validation.isBlank(captcha)) {
HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("secret", "6LcFEAkTAAAAAM0SErEs9NlfhYpOTRj_vOVJSAMI"));
formparams.add(new BasicNameValuePair("response", captcha));
formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
try {
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = httpClient.execute(post);
InputStream content = response.getEntity().getContent();
try {
Map json = JsonSerialization.readValue(content, Map.class);
Object val = json.get("success");
success = Boolean.TRUE.equals(val);
} finally {
content.close();
}
} catch (Exception e) {
logger.error("Recaptcha failed", e);
}
}
if (success) {
context.success();
} else {
String usernameField = RegistrationPage.FIELD_USERNAME;
if (context.getRealm().isRegistrationEmailAsUsername()) {
usernameField = RegistrationPage.FIELD_EMAIL;
}
errors.add(new FormMessage(usernameField, Messages.RECAPTCHA_FAILED));
formData.remove(G_RECAPTCHA_RESPONSE);
context.getEvent().error(Errors.INVALID_REGISTRATION);
context.validationError(formData, errors);
return;
}
}
@Override
public void success(FormContext context) {
}
@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 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;
}
}

View file

@ -1,27 +1,28 @@
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.authentication.FormContext;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
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.models.utils.FormMessage;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.AttributeFormDataProcessor;
import org.keycloak.services.validation.Validation;
import javax.ws.rs.core.MultivaluedMap;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -32,7 +33,67 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
public static final String PROVIDER_ID = "registration-user-creation";
@Override
public void authenticate(FormActionContext context) {
public void validate(ValidationContext 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);
context.validationError(formData, errors);
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));
context.validationError(formData, errors);
return;
}
} else {
if (Validation.isBlank(username)) {
context.getEvent().error(Errors.INVALID_REGISTRATION);
errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME));
context.validationError(formData, errors);
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);
context.validationError(formData, errors);
return;
}
context.success();
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
}
@Override
public void success(FormContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String email = formData.getFirst(Validation.FIELD_EMAIL);
String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
@ -45,29 +106,12 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
;
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())

View file

@ -1,152 +0,0 @@
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;
}
}

View file

@ -57,6 +57,7 @@ public class Messages {
public static final String INVALID_TOTP = "invalidTotpMessage";
public static final String USERNAME_EXISTS = "usernameExistsMessage";
public static final String RECAPTCHA_FAILED = "recaptchaFailed";
public static final String EMAIL_EXISTS = "emailExistsMessage";

View file

@ -1,4 +1,4 @@
org.keycloak.authentication.forms.RegistrationPasswordValidation
org.keycloak.authentication.forms.RegistrationProfileValidation
org.keycloak.authentication.forms.RegistrationPassword
org.keycloak.authentication.forms.RegistrationProfile
org.keycloak.authentication.forms.RegistrationUserCreation
org.keycloak.authentication.forms.RegistrationUsernameValidation
org.keycloak.authentication.forms.RegistrationRecaptcha