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 63d8c22098..1927378255 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
@@ -1,124 +1,125 @@
-<#import "template.ftl" as layout>
-<@layout.registrationLayout; section>
- <#if section = "title">
- ${msg("registerWithTitle",(realm.name!''))}
- <#elseif section = "header">
- ${msg("registerWithTitleHtml",(realm.name!''))}
- <#elseif section = "form">
-
- #if>
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("registerWithTitle",(realm.name!''))}
+ <#elseif section = "header">
+ ${msg("registerWithTitleHtml",(realm.name!''))}
+ <#elseif section = "form">
+
+ #if>
@layout.registrationLayout>
\ No newline at end of file
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 de582de4b0..f176555691 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
@@ -71,6 +71,8 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setFormData(MultivaluedMap formData);
+ LoginFormsProvider setAttribute(String name, Object value);
+
public LoginFormsProvider setStatus(Response.Status status);
LoginFormsProvider setActionUri(URI requestUri);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/AuthenticatorConfiguredMethod.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/AuthenticatorConfiguredMethod.java
new file mode 100755
index 0000000000..7327e793e9
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/AuthenticatorConfiguredMethod.java
@@ -0,0 +1,36 @@
+package org.keycloak.login.freemarker;
+
+import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModelException;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorUtil;
+import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.Urls;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ */
+public class AuthenticatorConfiguredMethod implements TemplateMethodModelEx {
+ private final RealmModel realm;
+ private final UserModel user;
+ private final KeycloakSession session;
+
+ public AuthenticatorConfiguredMethod(RealmModel realm, UserModel user, KeycloakSession session) {
+ this.realm = realm;
+ this.user = user;
+ this.session = session;
+ }
+
+ @Override
+ public Object exec(List list) throws TemplateModelException {
+ String providerId = list.get(0).toString();
+ Authenticator authenticator = session.getProvider(Authenticator.class, providerId);
+ return authenticator.configuredFor(session, realm, 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 1f59e843b7..0b36b6810b 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,6 +3,7 @@ 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;
@@ -85,6 +86,7 @@ import java.util.concurrent.TimeUnit;
private UserModel user;
private ClientSessionModel clientSession;
+ private final Map attributes = new HashMap();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
@@ -160,8 +162,6 @@ import java.util.concurrent.TimeUnit;
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
}
- Map attributes = new HashMap();
-
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme;
try {
@@ -207,6 +207,9 @@ import java.util.concurrent.TimeUnit;
}
URI baseUri = uriBuilder.build();
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
+ if (realm != null && user != null && session != null) {
+ attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
+ }
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
@@ -275,7 +278,8 @@ import java.util.concurrent.TimeUnit;
}
@Override
- public Response createForm(String form, Map attributes) {
+ public Response createForm(String form, Map extraAttributes) {
+
RealmModel realm = session.getContext().getRealm();
ClientModel client = session.getContext().getClient();
UriInfo uriInfo = session.getContext().getUri();
@@ -350,6 +354,9 @@ import java.util.concurrent.TimeUnit;
attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
}
}
+ if (realm != null && user != null && session != null) {
+ attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
+ }
try {
String result = freeMarker.processTemplate(attributes, form, theme);
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
@@ -393,6 +400,7 @@ import java.util.concurrent.TimeUnit;
return createResponse(LoginFormsPages.ERROR);
}
+
public Response createOAuthGrant(ClientSessionModel clientSession) {
this.clientSession = clientSession;
return createResponse(LoginFormsPages.OAUTH_GRANT);
@@ -475,6 +483,12 @@ import java.util.concurrent.TimeUnit;
return this;
}
+ @Override
+ public LoginFormsProvider setAttribute(String name, Object value) {
+ this.attributes.put(name, value);
+ return this;
+ }
+
@Override
public LoginFormsProvider setStatus(Response.Status status) {
this.status = status;
diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java
index 63abd766b3..ee9d43500b 100755
--- a/services/src/main/java/org/keycloak/authentication/Authenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java
@@ -15,4 +15,5 @@ public interface Authenticator extends Provider {
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
String getRequiredAction();
+
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
new file mode 100755
index 0000000000..ca7dd4d0e1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
@@ -0,0 +1,48 @@
+package org.keycloak.authentication;
+
+import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticatorUtil {
+
+ public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
+ for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
+ if (model.isAutheticatorFlow()) {
+ AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getAuthenticator(), authProviderId);
+ if (recurse != null) return recurse;
+
+ }
+ AuthenticatorModel authenticator = realm.getAuthenticatorById(model.getAuthenticator());
+ if (authenticator.getProviderId().equals(authProviderId)) {
+ return model;
+ }
+ }
+ return null;
+ }
+
+ public static boolean isEnabled(RealmModel realm, String flowId, String authProviderId) {
+ AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
+ if (execution == null) {
+ return false;
+ }
+ return execution.isEnabled();
+ }
+ public static boolean isRequired(RealmModel realm, String flowId, String authProviderId) {
+ AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
+ if (execution == null) {
+ return false;
+ }
+ return execution.isRequired();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java
index 5daafa807d..3c0769e25b 100755
--- a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java
@@ -95,6 +95,7 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
public Response invokeRequiredAction(RequiredActionContext context) {
return context.getSession().getProvider(LoginFormsProvider.class)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
+ .setUser(context.getUser())
.createForm("terms.ftl", new HashMap());
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
index 17a7360172..44636626a8 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
@@ -33,6 +33,7 @@ public class AbstractFormAuthenticator {
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
URI action = getActionUrl(context, code, LOGIN_FORM_ACTION);
return context.getSession().getProvider(LoginFormsProvider.class)
+ .setUser(context.getUser())
.setActionUri(action)
.setClientSessionCode(code.getCode());
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
index 680be06aa0..fb393e2fe7 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
@@ -27,7 +27,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
@Override
public void authenticate(AuthenticatorContext context) {
- if (!isAction(context, LOGIN_FORM_ACTION)) {
+ if (!isAction(context, LOGIN_FORM_ACTION) && !isAction(context, REGISTRATION_FORM_ACTION)) {
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
return;
}
@@ -35,7 +35,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
}
public void validatePassword(AuthenticatorContext context) {
- MultivaluedMap inputData = context.getHttpRequest().getFormParameters();
+ MultivaluedMap inputData = context.getHttpRequest().getDecodedFormParameters();
List credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
index 33211f3816..62e964318d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
@@ -51,7 +51,6 @@ public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator im
formData.add("rememberMe", "on");
}
}
- if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
Response challengeResponse = challenge(context, formData);
context.challenge(challengeResponse);
return;
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f4867b3043..fc5278e76a 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -25,8 +25,11 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.AuthenticatorUtil;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
+import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.events.Details;
@@ -35,6 +38,7 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
@@ -49,10 +53,12 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.PasswordToken;
@@ -273,6 +279,7 @@ public class LoginActionsService {
return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(clientSessionCode.getCode())
+ .setAttribute("passwordRequired", isPasswordRequired())
.createRegistration();
}
@@ -490,14 +497,13 @@ public class LoginActionsService {
* Registration
*
* @param code
- * @param formData
* @return
*/
@Path("request/registration")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response processRegister(@QueryParam("code") String code,
- final MultivaluedMap formData) {
+ public Response processRegister(@QueryParam("code") String code) {
+ MultivaluedMap formData = request.getDecodedFormParameters();
event.event(EventType.REGISTER);
if (!checkSsl()) {
event.error(Errors.SSL_REQUIRED);
@@ -553,20 +559,23 @@ public class LoginActionsService {
session.getContext().setClient(client);
- List requiredCredentialTypes = new LinkedList();
- for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
- requiredCredentialTypes.add(m.getType());
+ List requiredCredentialTypes = new LinkedList<>();
+ boolean passwordRequired = isPasswordRequired();
+ if (passwordRequired) {
+ requiredCredentialTypes.add(CredentialRepresentation.PASSWORD);
}
// Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
List errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy());
+
if (errors != null && !errors.isEmpty()) {
event.error(Errors.INVALID_REGISTRATION);
return session.getProvider(LoginFormsProvider.class)
.setErrors(errors)
.setFormData(formData)
.setClientSessionCode(clientCode.getCode())
+ .setAttribute("passwordRequired", isPasswordRequired())
.createRegistration();
}
@@ -577,6 +586,7 @@ public class LoginActionsService {
.setError(Messages.USERNAME_EXISTS)
.setFormData(formData)
.setClientSessionCode(clientCode.getCode())
+ .setAttribute("passwordRequired", isPasswordRequired())
.createRegistration();
}
@@ -587,6 +597,7 @@ public class LoginActionsService {
.setError(Messages.EMAIL_EXISTS)
.setFormData(formData)
.setClientSessionCode(clientCode.getCode())
+ .setAttribute("passwordRequired", isPasswordRequired())
.createRegistration();
}
@@ -597,7 +608,7 @@ public class LoginActionsService {
user.setEmail(email);
- if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+ if (passwordRequired) {
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(formData.getFirst("password"));
@@ -626,13 +637,35 @@ public class LoginActionsService {
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
}
}
-
+ clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
AttributeFormDataProcessor.process(formData, realm, user);
event.user(user).success();
event = new EventBuilder(realm, session, clientConnection);
+ clientSession.setAuthenticatedUser(user);
+ AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
+ AuthenticationProcessor processor = new AuthenticationProcessor();
+ processor.setClientSession(clientSession)
+ .setFlowId(flow.getId())
+ .setConnection(clientConnection)
+ .setEventBuilder(event)
+ .setProtector(authManager.getProtector())
+ .setRealm(realm)
+ .setAction(AbstractFormAuthenticator.REGISTRATION_FORM_ACTION)
+ .setSession(session)
+ .setUriInfo(uriInfo)
+ .setRequest(request);
- return processLogin(code, formData);
+ try {
+ return processor.authenticate();
+ } catch (Exception e) {
+ return processor.handleBrowserException(e);
+ }
+ }
+
+ public boolean isPasswordRequired() {
+ AuthenticationFlowModel browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
+ return AuthenticatorUtil.isRequired(realm, browserFlow.getId(), LoginFormPasswordAuthenticatorFactory.PROVIDER_ID);
}
/**
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index d3abc4d830..40e667d6c6 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,134 +1,134 @@
-package org.keycloak.services.validation;
-
-import org.keycloak.models.PasswordPolicy;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.utils.FormMessage;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.messages.Messages;
-
-import javax.ws.rs.core.MultivaluedMap;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public class Validation {
-
- public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
- public static final String FIELD_EMAIL = "email";
- public static final String FIELD_LAST_NAME = "lastName";
- public static final String FIELD_FIRST_NAME = "firstName";
- public static final String FIELD_PASSWORD = "password";
- public static final String FIELD_USERNAME = "username";
-
- // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
- private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
-
- public static List validateRegistrationForm(RealmModel realm, MultivaluedMap formData, List requiredCredentialTypes, PasswordPolicy policy) {
- List errors = new ArrayList<>();
-
- if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) {
- addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
- addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
- addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_EMAIL))) {
- addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
- } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
- addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
- }
-
- if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
- if (isBlank(formData.getFirst(FIELD_PASSWORD))) {
- addError(errors, FIELD_PASSWORD, Messages.MISSING_PASSWORD);
- } else if (!formData.getFirst(FIELD_PASSWORD).equals(formData.getFirst(FIELD_PASSWORD_CONFIRM))) {
- addError(errors, FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM);
- }
- }
-
- if (formData.getFirst(FIELD_PASSWORD) != null) {
- PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
- if (err != null)
- errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
- }
-
- return errors;
- }
-
- private static void addError(List errors, String field, String message){
- errors.add(new FormMessage(field, message));
- }
-
- public static List validateUpdateProfileForm(MultivaluedMap formData) {
- return validateUpdateProfileForm(null, formData);
- }
-
- public static List validateUpdateProfileForm(RealmModel realm, MultivaluedMap formData) {
- List errors = new ArrayList<>();
-
- if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) {
- addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
- addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
- addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
- }
-
- if (isBlank(formData.getFirst(FIELD_EMAIL))) {
- addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
- } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
- addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
- }
-
- return errors;
- }
-
- /**
- * Validate if user object contains all mandatory fields.
- *
- * @param realm user is for
- * @param user to validate
- * @return true if user object contains all mandatory values, false if some mandatory value is missing
- */
- public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){
- return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));
- }
-
- /**
- * Check if string is empty (null or lenght is 0)
- *
- * @param s to check
- * @return true if string is empty
- */
- public static boolean isEmpty(String s) {
- return s == null || s.length() == 0;
- }
-
- /**
- * Check if string is blank (null or lenght is 0 or contains only white characters)
- *
- * @param s to check
- * @return true if string is blank
- */
- public static boolean isBlank(String s) {
- return s == null || s.trim().length() == 0;
- }
-
- public static boolean isEmailValid(String email) {
- return EMAIL_PATTERN.matcher(email).matches();
- }
-
-
-}
+package org.keycloak.services.validation;
+
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.messages.Messages;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class Validation {
+
+ public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
+ public static final String FIELD_EMAIL = "email";
+ public static final String FIELD_LAST_NAME = "lastName";
+ public static final String FIELD_FIRST_NAME = "firstName";
+ public static final String FIELD_PASSWORD = "password";
+ public static final String FIELD_USERNAME = "username";
+
+ // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
+ private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
+
+ public static List validateRegistrationForm(RealmModel realm, MultivaluedMap formData, List requiredCredentialTypes, PasswordPolicy policy) {
+ List errors = new ArrayList<>();
+
+ if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) {
+ addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
+ addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
+ addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_EMAIL))) {
+ addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
+ } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
+ addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
+ }
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+ if (isBlank(formData.getFirst(FIELD_PASSWORD))) {
+ addError(errors, FIELD_PASSWORD, Messages.MISSING_PASSWORD);
+ } else if (!formData.getFirst(FIELD_PASSWORD).equals(formData.getFirst(FIELD_PASSWORD_CONFIRM))) {
+ addError(errors, FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM);
+ }
+ }
+
+ if (formData.getFirst(FIELD_PASSWORD) != null) {
+ PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
+ if (err != null)
+ errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
+ }
+
+ return errors;
+ }
+
+ private static void addError(List errors, String field, String message){
+ errors.add(new FormMessage(field, message));
+ }
+
+ public static List validateUpdateProfileForm(MultivaluedMap formData) {
+ return validateUpdateProfileForm(null, formData);
+ }
+
+ public static List validateUpdateProfileForm(RealmModel realm, MultivaluedMap formData) {
+ List errors = new ArrayList<>();
+
+ if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) {
+ addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
+ addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
+ addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
+ }
+
+ if (isBlank(formData.getFirst(FIELD_EMAIL))) {
+ addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
+ } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
+ addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
+ }
+
+ return errors;
+ }
+
+ /**
+ * Validate if user object contains all mandatory fields.
+ *
+ * @param realm user is for
+ * @param user to validate
+ * @return true if user object contains all mandatory values, false if some mandatory value is missing
+ */
+ public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){
+ return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));
+ }
+
+ /**
+ * Check if string is empty (null or lenght is 0)
+ *
+ * @param s to check
+ * @return true if string is empty
+ */
+ public static boolean isEmpty(String s) {
+ return s == null || s.length() == 0;
+ }
+
+ /**
+ * Check if string is blank (null or lenght is 0 or contains only white characters)
+ *
+ * @param s to check
+ * @return true if string is blank
+ */
+ public static boolean isBlank(String s) {
+ return s == null || s.trim().length() == 0;
+ }
+
+ public static boolean isEmailValid(String email) {
+ return EMAIL_PATTERN.matcher(email).matches();
+ }
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index bb8d1e36c4..18b4ca2558 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -160,7 +160,7 @@ public class RequiredActionEmailVerificationTest {
MimeMessage message = greenMail.getReceivedMessages()[0];
- Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail("username", "verifyEmail").detail("email", "email@mail.com").assertEvent();
+ Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail("username", "verifyemail").detail("email", "email@mail.com").assertEvent();
String sessionId = sendEvent.getSessionId();
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
@@ -171,9 +171,9 @@ public class RequiredActionEmailVerificationTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectRequiredAction(EventType.VERIFY_EMAIL).user(userId).session(sessionId).detail("username", "verifyEmail").detail("email", "email@mail.com").detail(Details.CODE_ID, mailCodeId).assertEvent();
+ events.expectRequiredAction(EventType.VERIFY_EMAIL).user(userId).session(sessionId).detail("username", "verifyemail").detail("email", "email@mail.com").detail(Details.CODE_ID, mailCodeId).assertEvent();
- events.expectLogin().user(userId).session(sessionId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
+ events.expectLogin().user(userId).session(sessionId).detail("username", "verifyemail").detail(Details.CODE_ID, mailCodeId).assertEvent();
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index b759b3e843..3891749729 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -109,11 +109,11 @@ public class RequiredActionTotpSetupTest {
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp").assertEvent().getSessionId();
+ String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp").assertEvent();
+ events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
}
@Test
@@ -165,9 +165,9 @@ public class RequiredActionTotpSetupTest {
// After totp config, user should be on the app page
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+ events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
- Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+ Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
// Logout
oauth.openLogout();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
old mode 100644
new mode 100755
index 13c628a2ba..cf2fd77d1a
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -137,7 +137,7 @@ public class RegisterTest {
String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").assertEvent().getUserId();
- events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent();
+ events.expectLogin().user(userId).detail(Details.USERNAME, "registerpasswordpolicy").assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@@ -190,7 +190,7 @@ public class RegisterTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
- events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
+ events.expectLogin().detail("username", "registerusersuccess").user(userId).assertEvent();
}
@Test
@@ -250,7 +250,7 @@ public class RegisterTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
- events.expectLogin().detail("username", "registerUserSuccessE@email").user(userId).assertEvent();
+ events.expectLogin().detail("username", "registerusersuccesse@email").user(userId).assertEvent();
} finally {
configureRelamRegistrationEmailAsUsername(false);
}