diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml index f5f01954d9..80b246071a 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml @@ -22,6 +22,9 @@ + + + diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties index 6cbb05d6ca..4fe6ab3fcf 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties @@ -13,6 +13,8 @@ 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=Invalid Recaptcha +recaptchaNotConfigured=Recaptcha is required, but not configured registerWithTitle=Registrierung bei {0} registerWithTitleHtml=Registrierung bei {0} diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties index 7a5237dc48..3fdb91c48f 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -30,6 +30,8 @@ codeSuccessTitle=Success code codeErrorTitle=Error code\: {0} termsTitle=Terms and Conditions termsTitleHtml=Terms and Conditions +recaptchaFailed=Invalid Recaptcha +recaptchaNotConfigured=Recaptcha is required, but not configured noAccount=New user? username=Username diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties index 5049b68f74..b239ecc31a 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties @@ -13,6 +13,8 @@ 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=Invalid Recaptcha +recaptchaNotConfigured=Recaptcha is required, but not configured registerWithTitle=Registrati come {0} registerWithTitleHtml=Registrati come {0} diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties index 4959003a7f..85b28fd553 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties @@ -13,6 +13,8 @@ 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=Invalid Recaptcha +recaptchaNotConfigured=Recaptcha is required, but not configured registerWithTitle=Registre-se com {0} registerWithTitleHtml=Registre-se com {0} diff --git a/forms/common-themes/src/main/resources/theme/base/login/register.ftl b/forms/common-themes/src/main/resources/theme/base/login/register.ftl index 1927378255..75a48fc738 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/register.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/register.ftl @@ -107,7 +107,13 @@ - + <#if recaptchaRequired??> +
+
+
+
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/login/template.ftl b/forms/common-themes/src/main/resources/theme/base/login/template.ftl old mode 100644 new mode 100755 index 34b2d4a5db..aca358fd4c --- a/forms/common-themes/src/main/resources/theme/base/login/template.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/template.ftl @@ -21,6 +21,11 @@ + <#if scripts??> + <#list scripts as script> + + + diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java index f176555691..dd5bc60594 100755 --- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java +++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java @@ -63,6 +63,8 @@ public interface LoginFormsProvider extends Provider { */ public LoginFormsProvider setErrors(List messages); + LoginFormsProvider addError(FormMessage errorMessage); + public LoginFormsProvider setSuccess(String message, Object ... parameters); public LoginFormsProvider setUser(UserModel user); diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java index 413c701b2b..2f74db9570 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -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; @@ -61,7 +61,7 @@ import java.util.concurrent.TimeUnit; /** * @author Stian Thorgersen */ - public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { +public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class); @@ -445,13 +445,29 @@ import java.util.concurrent.TimeUnit; @Override public LoginFormsProvider setErrors(List messages) { + if (messages == null) return this; this.messageType = MessageType.ERROR; this.messages = new ArrayList<>(messages); return this; } @Override - public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) { + 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); return this; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java index 123e6258a5..d4b21ee4ae 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java @@ -10,6 +10,7 @@ import org.keycloak.models.AuthenticationExecutionModel; public class AuthenticationExecutionEntity { protected String id; protected String authenticator; + private String authenticatorConfig; protected String flowId; protected AuthenticationExecutionModel.Requirement requirement; protected int priority; @@ -80,4 +81,12 @@ public class AuthenticationExecutionEntity { public void setFlowId(String flowId) { this.flowId = flowId; } + + public String getAuthenticatorConfig() { + return authenticatorConfig; + } + + public void setAuthenticatorConfig(String authenticatorConfig) { + this.authenticatorConfig = authenticatorConfig; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 3f2a1034d9..dc7a44ae85 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -2,8 +2,12 @@ package org.keycloak.models.utils; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.RealmModel; +import java.util.HashMap; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -34,7 +38,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 +53,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 +62,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 +71,30 @@ 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); + //AuthenticatorConfigModel captchaConfig = new AuthenticatorConfigModel(); + //captchaConfig.setAlias("Recaptcha Config"); + //Map config = new HashMap<>(); + //config.put("site.key", "6LcFEAkTAAAAAOaY-5RJk3zIYw4AalNtqfac27Bn"); + //config.put("secret", "6LcFEAkTAAAAAM0SErEs9NlfhYpOTRj_vOVJSAMI"); + //captchaConfig.setConfig(config); + //captchaConfig = realm.addAuthenticatorConfig(captchaConfig); + 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); + //execution.setAuthenticatorConfig(captchaConfig.getId()); + realm.addAuthenticatorExecution(execution); + + } diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 7df3f6ff13..45f3c095f0 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -1308,6 +1308,7 @@ public class RealmAdapter implements RealmModel { model.setParentFlow(entity.getParentFlow()); model.setFlowId(entity.getFlowId()); model.setAutheticatorFlow(entity.isAuthenticatorFlow()); + model.setAuthenticatorConfig(entity.getAuthenticatorConfig()); return model; } @@ -1339,6 +1340,7 @@ public class RealmAdapter implements RealmModel { entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setAuthenticatorFlow(model.isAutheticatorFlow()); entity.setFlowId(model.getFlowId()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); AuthenticationFlowEntity flow = getFlowEntity(model.getId()); flow.getExecutions().add(entity); model.setId(entity.getId()); @@ -1362,6 +1364,7 @@ public class RealmAdapter implements RealmModel { entity.setRequirement(model.getRequirement()); entity.setFlowId(model.getFlowId()); entity.setUserSetupAllowed(model.isUserSetupAllowed()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index eec43ba82a..5ccf6c1f0f 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1613,6 +1613,7 @@ public class RealmAdapter implements RealmModel { model.setFlowId(entity.getFlowId()); model.setParentFlow(entity.getParentFlow().getId()); model.setAutheticatorFlow(entity.isAutheticatorFlow()); + model.setAuthenticatorConfig(entity.getAuthenticatorConfig()); return model; } @@ -1631,6 +1632,7 @@ public class RealmAdapter implements RealmModel { entity.setPriority(model.getPriority()); entity.setFlowId(model.getFlowId()); entity.setRequirement(model.getRequirement()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); AuthenticationFlowEntity flow = em.find(AuthenticationFlowEntity.class, model.getParentFlow()); entity.setParentFlow(flow); flow.getExecutions().add(entity); @@ -1653,6 +1655,7 @@ public class RealmAdapter implements RealmModel { entity.setPriority(model.getPriority()); entity.setRequirement(model.getRequirement()); entity.setUserSetupAllowed(model.isUserSetupAllowed()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); entity.setFlowId(model.getFlowId()); em.flush(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java index b27659f445..c5e9019960 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java @@ -39,6 +39,9 @@ public class AuthenticationExecutionEntity { @Column(name="AUTHENTICATOR") protected String authenticator; + @Column(name="AUTH_CONFIG") + protected String authenticatorConfig; + @Column(name="AUTH_FLOW_ID") protected String flowId; @@ -125,4 +128,12 @@ public class AuthenticationExecutionEntity { public void setFlowId(String flowId) { this.flowId = flowId; } + + public String getAuthenticatorConfig() { + return authenticatorConfig; + } + + public void setAuthenticatorConfig(String authenticatorConfig) { + this.authenticatorConfig = authenticatorConfig; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 935746efe5..cb5cab9c68 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1383,6 +1383,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme model.setFlowId(entity.getFlowId()); model.setParentFlow(entity.getParentFlow()); model.setAutheticatorFlow(entity.isAuthenticatorFlow()); + model.setAuthenticatorConfig(entity.getAuthenticatorConfig()); return model; } @@ -1415,6 +1416,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setAuthenticatorFlow(model.isAutheticatorFlow()); entity.setFlowId(model.getFlowId()); entity.setParentFlow(model.getParentFlow()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow()); flow.getExecutions().add(entity); updateMongoEntity(); @@ -1439,6 +1441,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setRequirement(model.getRequirement()); entity.setFlowId(model.getFlowId()); entity.setUserSetupAllowed(model.isUserSetupAllowed()); + entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); updateMongoEntity(); } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java index af9f4367d5..66b75929cb 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java @@ -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(); } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java index 4c340c5d3f..e76811ea98 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java @@ -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()); } } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java index 7fcd35f04b..458278a4fa 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java @@ -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()); } } diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java index 2120044eba..55013ed16e 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java @@ -210,7 +210,8 @@ public class ClientSessionAdapter extends AbstractMongoAdapterBill Burke - * @version $Revision: 1 $ - */ -public interface FormActionContext extends AuthenticatorContext { - FormAuthenticator getFormAuthenticator(); - AuthenticationExecutionModel getFormExecution(); -} diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java index ab4071708a..8cd6751989 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java @@ -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(); + return processor.newEvent(); } - @Override - public FormAuthenticator getFormAuthenticator() { - return formAuthenticator; - } - - @Override - public AuthenticationExecutionModel getFormExecution() { - return formExecution; - } - - @Override + @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(); } - @Override - public BruteForceProtector getProtector() { - return delegate.getProtector(); + } + + private class ValidationContextImpl extends FormContextImpl implements ValidationContext { + FormAction action; + + private ValidationContextImpl(AuthenticationExecutionModel executionModel, FormAction action) { + super(executionModel); + this.action = action; } + boolean success; + List errors = null; + MultivaluedMap formData = null; @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 formData, List 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 executionStatus = new HashMap<>(); List requiredActions = new LinkedList<>(); + List 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; - executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); + 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 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 executionStatus = new HashMap<>(); - Response response = processResult(executionStatus, result, formExecution); - for (Map.Entry entry : executionStatus.entrySet()) { - processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue()); + return renderForm(null, null); + } + + public Response renderForm(MultivaluedMap formData, List 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); } - return response; + FormContext context = new FormContextImpl(formExecution); + return formAuthenticator.render(context, form); } - - - public Response processResult(Map executionStatus, AuthenticatorContext result, AuthenticationExecutionModel execution) { - AuthenticationProcessor.Status status = result.getStatus(); - if (status == AuthenticationProcessor.Status.SUCCESS) { - executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); - return null; - } else if (status == AuthenticationProcessor.Status.FAILED) { - AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator()); - processor.logFailure(); - executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED); - if (result.getChallenge() != null) { - return sendChallenge(result); - } - 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); - } - 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(); - } - - } diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java index f33843ea6b..02efebea70 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java @@ -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 formData, List errorMessages); + Response render(FormContext context, LoginFormsProvider form); } diff --git a/services/src/main/java/org/keycloak/authentication/FormContext.java b/services/src/main/java/org/keycloak/authentication/FormContext.java new file mode 100755 index 0000000000..f135945cf8 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormContext.java @@ -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 Bill Burke +* @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(); + +} diff --git a/services/src/main/java/org/keycloak/authentication/ValidationContext.java b/services/src/main/java/org/keycloak/authentication/ValidationContext.java new file mode 100755 index 0000000000..7a4eb44465 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/ValidationContext.java @@ -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 Bill Burke +* @version $Revision: 1 $ +*/ +public interface ValidationContext extends FormContext { + void validationError(MultivaluedMap formData, List errors); + void success(); +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java index 302fcf8153..19f0f3e5cf 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java @@ -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 formData = new MultivaluedMapImpl<>(); String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java index 0fa38c566c..c527ea4687 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java @@ -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 Bill Burke @@ -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 formData, List errorMessages) { - LoginFormsProvider registrationPage = createForm(context, context.getFormExecution().getId()); - if (formData != null) registrationPage.setFormData(formData); - if (errorMessages != null) { - registrationPage.setErrors(errorMessages); - } - return registrationPage.createRegistration(); - } - - public LoginFormsProvider createForm(AuthenticatorContext context, String executionId) { - AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD); - boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED; - String code = context.generateAccessCode(); - URI actionUrl = getActionUrl(context, executionId, code); - return context.getSession().getProvider(LoginFormsProvider.class) - .setAttribute("passwordRequired", passwordRequired) - .setActionUri(actionUrl) - .setClientSessionCode(code); + public Response render(FormContext context, LoginFormsProvider form) { + return form.createRegistration(); } @Override diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java similarity index 70% rename from services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java rename to services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java index 565d7d3a86..ffbf214f28 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPasswordValidation.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java @@ -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 Bill Burke * @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 formData = context.getHttpRequest().getDecodedFormParameters(); List 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 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; diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfile.java similarity index 76% rename from services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java rename to services/src/main/java/org/keycloak/authentication/forms/RegistrationProfile.java index 75d9cf36b9..af779b9c5c 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfileValidation.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationProfile.java @@ -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 Bill Burke * @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 formData = context.getHttpRequest().getDecodedFormParameters(); List 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 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; diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java new file mode 100755 index 0000000000..95e75e9112 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java @@ -0,0 +1,177 @@ +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.AuthenticatorConfigModel; +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 Bill Burke + * @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) { + AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig(); + if (captchaConfig == null || captchaConfig.getConfig() == null + || captchaConfig.getConfig().get("site.key") == null + || captchaConfig.getConfig().get("secret") == null + ) { + form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED)); + return; + } + String siteKey = captchaConfig.getConfig().get("site.key"); + form.setAttribute("recaptchaRequired", true); + form.setAttribute("recaptchaSiteKey", siteKey); + List scripts = new LinkedList<>(); + scripts.add("https://www.google.com/recaptcha/api.js"); + form.setAttribute("scripts", scripts); + } + + @Override + public void validate(ValidationContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + List errors = new ArrayList<>(); + boolean success = false; + context.getEvent().detail(Details.REGISTER_METHOD, "form"); + + String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE); + if (!Validation.isBlank(captcha)) { + AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig(); + String secret = captchaConfig.getConfig().get("secret"); + + HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient(); + HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify"); + List formparams = new LinkedList<>(); + formparams.add(new BasicNameValuePair("secret", secret)); + 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 { + errors.add(new FormMessage(null, 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; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java index e218c54e15..c8716f70d4 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java @@ -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 Bill Burke @@ -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 formData = context.getHttpRequest().getDecodedFormParameters(); + List errors = new ArrayList<>(); + context.getEvent().detail(Details.REGISTER_METHOD, "form"); + + String email = formData.getFirst(Validation.FIELD_EMAIL); + String username = formData.getFirst(RegistrationPage.FIELD_USERNAME); + context.getEvent().detail(Details.USERNAME, username); + context.getEvent().detail(Details.EMAIL, email); + + String usernameField = RegistrationPage.FIELD_USERNAME; + if (context.getRealm().isRegistrationEmailAsUsername()) { + username = email; + context.getEvent().detail(Details.USERNAME, username); + usernameField = RegistrationPage.FIELD_EMAIL; + if (Validation.isBlank(email)) { + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL)); + } else if (!Validation.isEmailValid(email)) { + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); + formData.remove(Validation.FIELD_EMAIL); + } + if (errors.size() > 0) { + context.getEvent().error(Errors.INVALID_REGISTRATION); + 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 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()) diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java deleted file mode 100755 index be406e3187..0000000000 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUsernameValidation.java +++ /dev/null @@ -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 Bill Burke - * @version $Revision: 1 $ - */ -public class RegistrationUsernameValidation implements FormAction, FormActionFactory { - - - public static final String PROVIDER_ID = "username-validation-action"; - - @Override - public void authenticate(FormActionContext context) { - MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); - List errors = new ArrayList<>(); - context.getEvent().detail(Details.REGISTER_METHOD, "form"); - - String email = formData.getFirst(Validation.FIELD_EMAIL); - String username = formData.getFirst(RegistrationPage.FIELD_USERNAME); - context.getEvent().detail(Details.USERNAME, username); - context.getEvent().detail(Details.EMAIL, email); - - String usernameField = RegistrationPage.FIELD_USERNAME; - if (context.getRealm().isRegistrationEmailAsUsername()) { - username = email; - context.getEvent().detail(Details.USERNAME, username); - usernameField = RegistrationPage.FIELD_EMAIL; - if (Validation.isBlank(email)) { - errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL)); - } else if (!Validation.isEmailValid(email)) { - errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); - formData.remove(Validation.FIELD_EMAIL); - } - if (errors.size() > 0) { - context.getEvent().error(Errors.INVALID_REGISTRATION); - Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); - context.challenge(challenge); - return; - } - if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) { - context.getEvent().error(Errors.USERNAME_IN_USE); - formData.remove(Validation.FIELD_EMAIL); - errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.USERNAME_EXISTS)); - Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); - context.challenge(challenge); - return; - } - } else { - if (Validation.isBlank(username)) { - context.getEvent().error(Errors.INVALID_REGISTRATION); - errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME)); - Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); - context.challenge(challenge); - return; - } - - } - if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) { - context.getEvent().error(Errors.USERNAME_IN_USE); - errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS)); - formData.remove(Validation.FIELD_USERNAME); - formData.remove(Validation.FIELD_EMAIL); - Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors); - context.challenge(challenge); - return; - - } - context.success(); - } - - @Override - public boolean requiresUser() { - return false; - } - - @Override - public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { - return true; - } - - @Override - public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { - - } - - @Override - public void close() { - - } - - @Override - public String getDisplayType() { - return "Username Validation"; - } - - @Override - public String getReferenceCategory() { - return null; - } - - @Override - public boolean isConfigurable() { - return false; - } - - @Override - public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { - return new AuthenticationExecutionModel.Requirement[0]; - } - - @Override - public FormAction create(KeycloakSession session) { - return this; - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public String getId() { - return PROVIDER_ID; - } -} diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index 9f9070ad58..e9b7fa0304 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -57,6 +57,8 @@ 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 RECAPTCHA_NOT_CONFIGURED = "recaptchaNotConfigured"; public static final String EMAIL_EXISTS = "emailExistsMessage"; diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory index 47a023c5f1..b0240f38d6 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory @@ -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 \ No newline at end of file +org.keycloak.authentication.forms.RegistrationRecaptcha \ No newline at end of file