From 392fa21f1ea80ddba2997fe24a17677097f8577b Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 11 Jun 2015 21:15:53 -0400 Subject: [PATCH] finish reg --- .../resources/theme/base/login/register.ftl | 247 ++++++++-------- .../keycloak/login/LoginFormsProvider.java | 2 + .../AuthenticatorConfiguredMethod.java | 36 +++ .../FreeMarkerLoginFormsProvider.java | 20 +- .../authentication/Authenticator.java | 1 + .../authentication/AuthenticatorUtil.java | 48 ++++ .../actions/TermsAndConditions.java | 1 + .../AbstractFormAuthenticator.java | 1 + .../LoginFormPasswordAuthenticator.java | 4 +- .../LoginFormUsernameAuthenticator.java | 1 - .../resources/LoginActionsService.java | 51 +++- .../services/validation/Validation.java | 268 +++++++++--------- .../RequiredActionEmailVerificationTest.java | 6 +- .../actions/RequiredActionTotpSetupTest.java | 8 +- .../testsuite/forms/RegisterTest.java | 6 +- 15 files changed, 418 insertions(+), 282 deletions(-) create mode 100755 forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/AuthenticatorConfiguredMethod.java create mode 100755 services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java mode change 100644 => 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java 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 63d8c2209857..1927378255e1 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 !realm.registrationEmailAsUsername> -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
- - -
- - -
- -
-
-
- +<#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 !realm.registrationEmailAsUsername> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + <#if passwordRequired> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ + +
+ + +
+ +
+
+
+ \ 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 de582de4b072..f1765556917c 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 000000000000..7327e793e9e4 --- /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 1f59e843b79b..0b36b6810b67 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 @@ 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 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { 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 @@ private Response createResponse(LoginFormsPages page) { uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode); } - Map attributes = new HashMap(); - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme; try { @@ -207,6 +207,9 @@ private Response createResponse(LoginFormsPages page) { } 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 @@ private Response createResponse(LoginFormsPages page) { } @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 @@ public Response createForm(String form, Map attributes) { 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 @@ public Response createErrorPage() { return createResponse(LoginFormsPages.ERROR); } + public Response createOAuthGrant(ClientSessionModel clientSession) { this.clientSession = clientSession; return createResponse(LoginFormsPages.OAUTH_GRANT); @@ -475,6 +483,12 @@ public LoginFormsProvider setAccessRequest(String accessRequestMessage) { 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 63abd766b326..ee9d43500b34 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 000000000000..ca7dd4d0e184 --- /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 5daafa807d51..3c0769e25ba7 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 void evaluateTriggers(RequiredActionContext context) { 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 17a7360172c6..44636626a877 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 @@ protected LoginFormsProvider loginForm(AuthenticatorContext context) { 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 680be06aa03a..fb393e2fe7ae 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 LoginFormPasswordAuthenticator(AuthenticatorModel model) { @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 void authenticate(AuthenticatorContext context) { } 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 33211f38167a..62e964318dd1 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 void authenticate(AuthenticatorContext context) { 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 f4867b3043f5..fc5278e76a51 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.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.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.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 Response registerPage(@QueryParam("code") String code) { return session.getProvider(LoginFormsProvider.class) .setClientSessionCode(clientSessionCode.getCode()) + .setAttribute("passwordRequired", isPasswordRequired()) .createRegistration(); } @@ -490,14 +497,13 @@ public Response processLogin(@QueryParam("code") String code, * 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 Response processRegister(@QueryParam("code") String code, 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 Response processRegister(@QueryParam("code") String code, .setError(Messages.USERNAME_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) + .setAttribute("passwordRequired", isPasswordRequired()) .createRegistration(); } @@ -587,6 +597,7 @@ public Response processRegister(@QueryParam("code") String code, .setError(Messages.EMAIL_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) + .setAttribute("passwordRequired", isPasswordRequired()) .createRegistration(); } @@ -597,7 +608,7 @@ public Response processRegister(@QueryParam("code") String code, 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 Response processRegister(@QueryParam("code") String code, .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); } } - + clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username); AttributeFormDataProcessor.process(formData, realm, user); event.user(user).success(); event = new EventBuilder(realm, session, clientConnection); + clientSession.setAuthenticatedUser(user); + AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + AuthenticationProcessor processor = new AuthenticationProcessor(); + processor.setClientSession(clientSession) + .setFlowId(flow.getId()) + .setConnection(clientConnection) + .setEventBuilder(event) + .setProtector(authManager.getProtector()) + .setRealm(realm) + .setAction(AbstractFormAuthenticator.REGISTRATION_FORM_ACTION) + .setSession(session) + .setUriInfo(uriInfo) + .setRequest(request); + + try { + return processor.authenticate(); + } catch (Exception e) { + return processor.handleBrowserException(e); + } + } - return processLogin(code, formData); + 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 d3abc4d830af..40e667d6c690 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 bb8d1e36c47b..18b4ca25583a 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 void verifyEmailRegister() throws IOException, MessagingException { 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 void verifyEmailRegister() throws IOException, MessagingException { 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 b759b3e843bd..38917497295f 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 void setupTotpRegister() { 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 void setupTotpRegisteredAfterTotpRemoval() { // 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 13c628a2baa3..cf2fd77d1afd --- 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 void config(RealmManager manager, RealmModel adminstrationRealm, RealmMod 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 void registerUserSuccess() { 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 void registerUserSuccess_emailAsUsername() { 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); }