form action refactor
This commit is contained in:
parent
a1c612f833
commit
39aa09ca36
30 changed files with 554 additions and 504 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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!}">
|
||||
|
|
5
forms/common-themes/src/main/resources/theme/base/login/template.ftl
Normal file → Executable file
5
forms/common-themes/src/main/resources/theme/base/login/template.ftl
Normal file → Executable 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!}">
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
33
services/src/main/java/org/keycloak/authentication/FormContext.java
Executable file
33
services/src/main/java/org/keycloak/authentication/FormContext.java
Executable 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();
|
||||
|
||||
}
|
15
services/src/main/java/org/keycloak/authentication/ValidationContext.java
Executable file
15
services/src/main/java/org/keycloak/authentication/ValidationContext.java
Executable 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();
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue