keycloak-aplcache

Merge branch 'KEYCLOAK-1113' of https://github.com/velias/keycloak

4/1/2015 4:45:13 AM

Details

diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
index bc6920b..f5bcbb2 100755
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
@@ -1,52 +1,55 @@
 package org.keycloak.account;
 
-import org.apache.http.client.methods.HttpHead;
-import org.keycloak.events.Event;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.provider.Provider;
+import java.util.List;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.util.List;
+
+import org.keycloak.events.Event;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.provider.Provider;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public interface AccountProvider extends Provider {
 
-    AccountProvider setUriInfo(UriInfo uriInfo);
+	AccountProvider setUriInfo(UriInfo uriInfo);
+
+	AccountProvider setHttpHeaders(HttpHeaders httpHeaders);
 
-    AccountProvider setHttpHeaders(HttpHeaders httpHeaders);
+	Response createResponse(AccountPages page);
 
-    Response createResponse(AccountPages page);
+	AccountProvider setError(String message, Object... parameters);
 
-    AccountProvider setError(String message, Object ... parameters);
+	AccountProvider setErrors(List<FormMessage> messages);
 
-    AccountProvider setSuccess(String message, Object ... parameters);
+	AccountProvider setSuccess(String message, Object... parameters);
 
-    AccountProvider setWarning(String message, Object ... parameters);
+	AccountProvider setWarning(String message, Object... parameters);
 
-    AccountProvider setUser(UserModel user);
+	AccountProvider setUser(UserModel user);
 
-    AccountProvider setProfileFormData(MultivaluedMap<String, String> formData);
+	AccountProvider setProfileFormData(MultivaluedMap<String, String> formData);
 
-    AccountProvider setStatus(Response.Status status);
+	AccountProvider setStatus(Response.Status status);
 
-    AccountProvider setRealm(RealmModel realm);
+	AccountProvider setRealm(RealmModel realm);
 
-    AccountProvider setReferrer(String[] referrer);
+	AccountProvider setReferrer(String[] referrer);
 
-    AccountProvider setEvents(List<Event> events);
+	AccountProvider setEvents(List<Event> events);
 
-    AccountProvider setSessions(List<UserSessionModel> sessions);
+	AccountProvider setSessions(List<UserSessionModel> sessions);
 
-    AccountProvider setPasswordSet(boolean passwordSet);
+	AccountProvider setPasswordSet(boolean passwordSet);
 
-    AccountProvider setStateChecker(String stateChecker);
+	AccountProvider setStateChecker(String stateChecker);
 
-    AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported);
+	AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported);
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 4e0c007..2828531 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -1,25 +1,54 @@
 package org.keycloak.account.freemarker;
 
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import org.jboss.logging.Logger;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.AccountProvider;
-import org.keycloak.account.freemarker.model.*;
+import org.keycloak.account.freemarker.model.AccountBean;
+import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
+import org.keycloak.account.freemarker.model.FeaturesBean;
+import org.keycloak.account.freemarker.model.LogBean;
+import org.keycloak.account.freemarker.model.PasswordBean;
+import org.keycloak.account.freemarker.model.RealmBean;
+import org.keycloak.account.freemarker.model.ReferrerBean;
+import org.keycloak.account.freemarker.model.SessionsBean;
+import org.keycloak.account.freemarker.model.TotpBean;
+import org.keycloak.account.freemarker.model.UrlBean;
 import org.keycloak.events.Event;
-import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.LocaleHelper;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.freemarker.beans.MessageBean;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.freemarker.beans.MessageType;
+import org.keycloak.freemarker.beans.MessagesPerFieldBean;
 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.resources.flows.Urls;
 
-import javax.ws.rs.core.*;
-import java.io.IOException;
-import java.net.URI;
-import java.text.MessageFormat;
-import java.util.*;
-import org.keycloak.freemarker.beans.LocaleBean;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -43,13 +72,10 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     private FreeMarkerUtil freeMarker;
     private HttpHeaders headers;
 
-    public static enum MessageType {SUCCESS, WARNING, ERROR}
-
     private UriInfo uriInfo;
 
-    private String message;
-    private Object[] parameters;
-    private MessageType messageType;
+    private List<FormMessage> messages = null;
+    private MessageType messageType = MessageType.ERROR;
 
     public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
@@ -87,13 +113,13 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         }
 
         Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
-        Properties messages;
+        Properties messagesBundle;
         try {
-            messages = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messages));
+        	messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
-            messages = new Properties();
+            messagesBundle = new Properties();
         }
 
         URI baseUri = uriInfo.getBaseUri();
@@ -107,15 +133,19 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             attributes.put("stateChecker", stateChecker);
         }
 
-        if (message != null) {
-            String formattedMessage;
-            if(messages.containsKey(message)){
-                formattedMessage = new MessageFormat(messages.getProperty(message),locale).format(parameters);
-            }else{
-                formattedMessage = message;
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
             }
-            attributes.put("message", new MessageBean(formattedMessage, messageType));
+            attributes.put("message", wholeMessage);
         }
+        attributes.put("messagesPerField", messagesPerField);
 
         if (referrer != null) {
             attributes.put("referrer", new ReferrerBean(referrer));
@@ -134,7 +164,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
                     b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath());
                     break;
             }
-            attributes.put("locale", new LocaleBean(realm, locale, b, messages));
+            attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
         }
 
         attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported));
@@ -173,28 +203,47 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         this.passwordSet = passwordSet;
         return this;
     }
+    
+	protected void setMessage(MessageType type, String message, Object... parameters) {
+		messageType = type;
+		messages = new ArrayList<>();
+		messages.add(new FormMessage(null, message, parameters));
+	}
+
+	protected String formatMessage(FormMessage message, Properties messagesBundle, Locale locale) {
+		if (message == null)
+			return null;
+		if (messagesBundle.containsKey(message.getMessage())) {
+			return new MessageFormat(messagesBundle.getProperty(message.getMessage()), locale)
+					.format(message.getParameters());
+		} else {
+			return message.getMessage();
+		}
+	}
+  
+  @Override
+	public AccountProvider setErrors(List<FormMessage> messages) {
+		this.messageType = MessageType.ERROR;
+		this.messages = new ArrayList<>(messages);
+		return this;
+	}
+
 
     @Override
     public AccountProvider setError(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
-        this.messageType = MessageType.ERROR;
+    	  setMessage(MessageType.ERROR, message, parameters);
         return this;
     }
 
     @Override
     public AccountProvider setSuccess(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
-        this.messageType = MessageType.SUCCESS;
+    	  setMessage(MessageType.SUCCESS, message, parameters);
         return this;
     }
 
     @Override
     public AccountProvider setWarning(String message, Object ... parameters) {
-        this.message = message;
-        this.parameters = parameters;
-        this.messageType = MessageType.WARNING;
+    	  setMessage(MessageType.WARNING, message, parameters);
         return this;
     }
 
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java
new file mode 100644
index 0000000..5858b07
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.freemarker.beans;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Bean used to hold form messages per field. Stored under <code>messagesPerField</code> key in Freemarker context.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class MessagesPerFieldBean {
+
+    private Map<String, MessageBean> messagesPerField = new HashMap<String, MessageBean>();
+
+    public void addMessage(String field, String messageText, MessageType messageType) {
+        if (messageText == null || messageText.trim().isEmpty())
+            return;
+        if (field == null)
+            field = "global";
+
+        MessageBean fm = messagesPerField.get(field);
+        if (fm == null) {
+            messagesPerField.put(field, new MessageBean(messageText, messageType));
+        } else {
+            fm.appendSummaryLine(messageText);
+        }
+    }
+
+    /**
+     * Check if message for given field exists
+     * 
+     * @param field
+     * @return
+     */
+    public boolean exists(String field) {
+        return messagesPerField.containsKey(field);
+    }
+
+    /**
+     * Get message for given field.
+     * 
+     * @param fieldName
+     * @return message text or empty string
+     */
+    public String get(String fieldName) {
+        MessageBean mb = messagesPerField.get(fieldName);
+        if (mb != null) {
+            return mb.getSummary();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Print text if message for given field exists. Useful eg. to add css styles for fields with message.
+     * 
+     * @param fieldName to check for
+     * @param text to print
+     * @return text if message exists for given field, else empty string
+     */
+    public String printIfExists(String fieldName, String text) {
+        if (exists(fieldName))
+            return text;
+        else
+            return "";
+    }
+
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java
new file mode 100644
index 0000000..a404c7e
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessageType.java
@@ -0,0 +1,17 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.freemarker.beans;
+
+/**
+ * Enum with types of messages.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public enum MessageType {
+	
+	SUCCESS, WARNING, ERROR
+
+}
diff --git a/forms/common-themes/src/main/resources/theme/base/account/account.ftl b/forms/common-themes/src/main/resources/theme/base/account/account.ftl
index a41d769..7c349df 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/account.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/account/account.ftl
@@ -14,7 +14,7 @@
 
         <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="username" class="control-label">${msg("username")}</label>
             </div>
@@ -24,7 +24,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
             <div class="col-sm-2 col-md-2">
             <label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
             </div>
@@ -34,7 +34,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
             </div>
@@ -44,7 +44,7 @@
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
             <div class="col-sm-2 col-md-2">
                 <label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
index 56b5cbe..a534073 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
@@ -6,7 +6,7 @@
         ${msg("loginProfileTitle")}
     <#elseif section = "form">
         <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginUpdateProfileUrl}" method="post">
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
                 </div>
@@ -15,7 +15,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
                 </div>
@@ -24,7 +24,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
                 </div>
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 930a07d..e560cf1 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
@@ -7,7 +7,7 @@
     <#elseif section = "form">
         <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
           <#if !realm.registrationEmailAsUsername>
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
                 </div>
@@ -16,7 +16,7 @@
                 </div>
             </div>
           </#if>
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
                 </div>
@@ -25,7 +25,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
                 </div>
@@ -34,7 +34,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
                 </div>
@@ -43,7 +43,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
                 </div>
@@ -52,7 +52,7 @@
                 </div>
             </div>
 
-            <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties b/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
index 25427a7..7c83966 100644
--- a/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
+++ b/forms/common-themes/src/main/resources/theme/patternfly/login/theme.properties
@@ -18,6 +18,7 @@ kcFormAreaClass=col-xs-12 col-sm-8 col-md-8 col-lg-6 login
 
 kcFormClass=form-horizontal
 kcFormGroupClass=form-group
+kcFormGroupErrorClass=has-error
 kcLabelClass=control-label
 kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-4 col-lg-3
 kcInputClass=form-control
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 36a5252..c3a71d9 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
@@ -1,19 +1,21 @@
 package org.keycloak.login;
 
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.provider.Provider;
 
-import javax.ws.rs.core.HttpHeaders;
-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.List;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -48,7 +50,20 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
     public LoginFormsProvider setAccessRequest(String message);
 
+    /**
+     * Set one global error message.
+     * 
+     * @param message key of message
+     * @param parameters to be formatted into message
+     */
     public LoginFormsProvider setError(String message, Object ... parameters);
+    
+    /**
+     * Set multiple error messages.
+     * 
+     * @param messages to be set
+     */
+    public LoginFormsProvider setErrors(List<FormMessage> messages);
 
     public LoginFormsProvider setSuccess(String message, Object ... parameters);
 
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 9aca77f..0dd6342 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
@@ -1,25 +1,50 @@
 package org.keycloak.login.freemarker;
 
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
-import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.LocaleHelper;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
 import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
+import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.freemarker.beans.MessageBean;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.freemarker.beans.MessageType;
+import org.keycloak.freemarker.beans.MessagesPerFieldBean;
 import org.keycloak.login.LoginFormsPages;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.login.freemarker.model.ClientBean;
 import org.keycloak.login.freemarker.model.CodeBean;
-import org.keycloak.freemarker.beans.LocaleBean;
+import org.keycloak.login.freemarker.model.IdentityProviderBean;
 import org.keycloak.login.freemarker.model.LoginBean;
-import org.keycloak.login.freemarker.model.MessageBean;
 import org.keycloak.login.freemarker.model.OAuthGrantBean;
 import org.keycloak.login.freemarker.model.ProfileBean;
 import org.keycloak.login.freemarker.model.RealmBean;
 import org.keycloak.login.freemarker.model.RegisterBean;
-import org.keycloak.login.freemarker.model.IdentityProviderBean;
 import org.keycloak.login.freemarker.model.TotpBean;
 import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
@@ -28,16 +53,10 @@ import org.keycloak.models.KeycloakSession;
 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.resources.flows.Urls;
 
-import javax.ws.rs.core.*;
-import java.io.IOException;
-import java.net.URI;
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -45,8 +64,6 @@ import java.util.concurrent.TimeUnit;
 
     private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
 
-    public static enum MessageType {SUCCESS, WARNING, ERROR}
-
     private String accessCode;
     private Response.Status status;
     private List<RoleModel> realmRolesRequested;
@@ -55,9 +72,8 @@ import java.util.concurrent.TimeUnit;
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
-    private Object[] parameters;
 
-    private String message;
+    private List<FormMessage> messages = null;
     private MessageType messageType = MessageType.ERROR;
 
     private MultivaluedMap<String, String> formData;
@@ -134,7 +150,7 @@ import java.util.concurrent.TimeUnit;
                 return Response.serverError().build();
         }
 
-        if (message == null) {
+        if (messages == null) {
             setWarning(actionMessage);
         }
 
@@ -175,25 +191,30 @@ import java.util.concurrent.TimeUnit;
             logger.warn("Failed to load properties", e);
         }
 
-        Properties messages;
+        Properties messagesBundle;
         Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders);
         try {
-            messages = theme.getMessages(locale);
-            attributes.put("msg", new MessageFormatterMethod(locale, messages));
+            messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
-            messages = new Properties();
+            messagesBundle = new Properties();
         }
 
-        if (message != null) {
-            String formattedMessage;
-            if(messages.containsKey(message)){
-                formattedMessage = new MessageFormat(messages.getProperty(message),locale).format(parameters);
-            }else{
-                formattedMessage = message;
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
             }
-            attributes.put("message", new MessageBean(formattedMessage, messageType));
+            attributes.put("message", wholeMessage);
         }
+        attributes.put("messagesPerField", messagesPerField);
+
         if (page == LoginFormsPages.OAUTH_GRANT) {
             // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
             uriBuilder.replaceQuery(null);
@@ -218,7 +239,7 @@ import java.util.concurrent.TimeUnit;
                         b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
                         break;
                 }
-                attributes.put("locale", new LocaleBean(realm, locale, b, messages));
+                attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
             }
         }
 
@@ -240,10 +261,10 @@ import java.util.concurrent.TimeUnit;
                 break;
             case OAUTH_GRANT:
                 attributes.put("oauth", new OAuthGrantBean(accessCode, clientSession, client, realmRolesRequested, resourceRolesRequested, this.accessRequestMessage));
-                attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messages));
+                attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
                 break;
             case CODE:
-                attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
+                attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? getFirstMessageUnformatted() : null));
                 break;
         }
 
@@ -303,24 +324,51 @@ import java.util.concurrent.TimeUnit;
         return createResponse(LoginFormsPages.CODE);
     }
 
-    public FreeMarkerLoginFormsProvider setError(String message, Object ... parameters) {
-        this.message = message;
+    protected void setMessage(MessageType type, String message, Object... parameters) {
+        messageType = type;
+        messages = new ArrayList<>();
+        messages.add(new FormMessage(null, message, parameters));
+    }
+
+    protected String getFirstMessageUnformatted() {
+        if (messages != null && !messages.isEmpty()) {
+            return messages.get(0).getMessage();
+        }
+        return null;
+    }
+
+    protected String formatMessage(FormMessage message, Properties messagesBundle, Locale locale) {
+        if (message == null)
+            return null;
+        if (messagesBundle.containsKey(message.getMessage())) {
+            return new MessageFormat(messagesBundle.getProperty(message.getMessage()), locale).format(message.getParameters());
+        } else {
+            return message.getMessage();
+        }
+    }
+
+    @Override
+    public FreeMarkerLoginFormsProvider setError(String message, Object... parameters) {
+        setMessage(MessageType.ERROR, message, parameters);
+        return this;
+    }
+
+    @Override
+    public LoginFormsProvider setErrors(List<FormMessage> messages) {
         this.messageType = MessageType.ERROR;
-        this.parameters = parameters;
+        this.messages = new ArrayList<>(messages);
         return this;
     }
 
+    @Override
     public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) {
-        this.message = message;
-        this.messageType = MessageType.SUCCESS;
-        this.parameters = parameters;
+        setMessage(MessageType.SUCCESS, message, parameters);
         return this;
     }
 
+    @Override
     public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) {
-        this.message = message;
-        this.messageType = MessageType.WARNING;
-        this.parameters = parameters;
+        setMessage(MessageType.WARNING, message, parameters);
         return this;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
new file mode 100644
index 0000000..6a93c51
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
@@ -0,0 +1,69 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.models.utils;
+
+import java.util.Arrays;
+
+/**
+ * Message (eg. error) to be shown in form.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class FormMessage {
+
+	/**
+	 * Value used for {@link #field} if message is global (not tied to any specific form field)
+	 */
+	public static final String GLOBAL = "global";
+
+	private String field;
+	private String message;
+	private Object[] parameters;
+
+	/**
+	 * Create message.
+	 * 
+	 * @param field this message is for. {@link #GLOBAL} is used if null
+	 * @param message key for the message
+	 * @param parameters to be formatted into message
+	 */
+	public FormMessage(String field, String message, Object... parameters) {
+		this(field, message);
+		this.parameters = parameters;
+	}
+	
+	/**
+     * Create message without parameters.
+     * 
+     * @param field this message is for. {@link #GLOBAL} is used if null
+     * @param message key for the message
+     */
+    public FormMessage(String field, String message) {
+        super();
+        if (field == null)
+            field = GLOBAL;
+        this.field = field;
+        this.message = message;
+    }
+
+	public String getField() {
+		return field;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public Object[] getParameters() {
+		return parameters;
+	}
+
+	@Override
+	public String toString() {
+		return "FormMessage [field=" + field + ", message=" + message + ", parameters=" + Arrays.toString(parameters) + "]";
+	}
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 6a0e0b5..906e3e2 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -33,6 +33,7 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventStoreProvider;
 import org.keycloak.events.EventType;
 import org.keycloak.models.*;
+import org.keycloak.models.utils.FormMessage;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -69,6 +70,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.core.Variant;
+
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.HashSet;
@@ -403,10 +405,10 @@ public class AccountService {
 
         UserModel user = auth.getUser();
 
-        String error = Validation.validateUpdateProfileForm(formData);
-        if (error != null) {
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(formData);
+        if (errors != null && !errors.isEmpty()) {
             setReferrerOnPage();
-            return account.setError(error).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
+            return account.setErrors(errors).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
         }
 
         try {
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 e2ad7f0..078e408 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -1,959 +1,954 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, Red Hat, Inc., and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.keycloak.services.resources;
-
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.ClientConnection;
-import org.keycloak.email.EmailException;
-import org.keycloak.email.EmailProvider;
-import org.keycloak.events.Details;
-import org.keycloak.events.Errors;
-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.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelException;
-import org.keycloak.models.PasswordPolicy;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RequiredCredentialModel;
-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.KeycloakModelUtils;
-import org.keycloak.models.utils.TimeBasedOTP;
-import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.PasswordToken;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.messages.Messages;
-import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.Urls;
-import org.keycloak.services.util.CookieHelper;
-import org.keycloak.services.validation.Validation;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
-import javax.ws.rs.ext.Providers;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class LoginActionsService {
-
-    protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
-
-    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
-
-    private RealmModel realm;
-
-    @Context
-    private HttpRequest request;
-
-    @Context
-    protected HttpHeaders headers;
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private ClientConnection clientConnection;
-
-    @Context
-    protected Providers providers;
-
-    @Context
-    protected KeycloakSession session;
-
-    private AuthenticationManager authManager;
-
-    private EventBuilder event;
-
-    public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return loginActionsBaseUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
-        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
-    }
-
-    public static UriBuilder processLoginUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return processLoginUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) {
-        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
-        return uriBuilder.path(OIDCLoginProtocolService.class, "processLogin");
-    }
-
-    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return processOAuthUrl(baseUriBuilder);
-    }
-
-    public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) {
-        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
-        return uriBuilder.path(OIDCLoginProtocolService.class, "processOAuth");
-    }
-
-    public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) {
-        this.realm = realm;
-        this.authManager = authManager;
-        this.event = event;
-    }
-
-    private boolean checkSsl() {
-        if (uriInfo.getBaseUri().getScheme().equals("https")) {
-            return true;
-        } else {
-            return !realm.getSslRequired().isRequired(clientConnection);
-        }
-    }
-
-
-    private class Checks {
-        ClientSessionCode clientCode;
-        Response response;
-
-        boolean check(String code, ClientSessionModel.Action requiredAction) {
-            if (!check(code)) {
-                return false;
-            } else if (!clientCode.isValid(requiredAction)) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
-            if (!check(code)) {
-                return false;
-            } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE);
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        public boolean check(String code) {
-            if (!checkSsl()) {
-                event.error(Errors.SSL_REQUIRED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-                return false;
-            }
-            if (!realm.isEnabled()) {
-                event.error(Errors.REALM_DISABLED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-                return false;
-            }
-            clientCode = ClientSessionCode.parse(code, session, realm);
-            if (clientCode == null) {
-                event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * protocol independent login page entry point
-     *
-     *
-     * @param code
-     * @return
-     */
-    @Path("login")
-    @GET
-    public Response loginPage(@QueryParam("code") String code) {
-        event.event(EventType.LOGIN);
-        Checks checks = new Checks();
-        if (!checks.check(code)) {
-            return checks.response;
-        }
-        event.detail(Details.CODE_ID, code);
-        ClientSessionCode clientSessionCode = checks.clientCode;
-        ClientSessionModel clientSession = clientSessionCode.getClientSession();
-
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
-            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
-        }
-
-        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
-                .setClientSessionCode(clientSessionCode.getCode());
-
-        return forms.createLogin();
-    }
-
-    /**
-     * protocol independent registration page entry point
-     *
-     * @param code
-     * @return
-     */
-    @Path("registration")
-    @GET
-    public Response registerPage(@QueryParam("code") String code) {
-        event.event(EventType.REGISTER);
-        if (!realm.isRegistrationAllowed()) {
-            event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
-        }
-
-        Checks checks = new Checks();
-        if (!checks.check(code)) {
-            return checks.response;
-        }
-        event.detail(Details.CODE_ID, code);
-        ClientSessionCode clientSessionCode = checks.clientCode;
-        ClientSessionModel clientSession = clientSessionCode.getClientSession();
-
-
-        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
-
-        return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
-                .setClientSessionCode(clientSessionCode.getCode())
-                .createRegistration();
-    }
-
-    /**
-     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
-     *
-     * @param code
-     * @param formData
-     * @return
-     */
-    @Path("request/login")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processLogin(@QueryParam("code") String code,
-                                 final MultivaluedMap<String, String> formData) {
-        event.event(EventType.LOGIN);
-        if (!checkSsl()) {
-            event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
-        if (clientCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-
-        ClientSessionModel clientSession = clientCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
-
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
-            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
-            event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
-            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createLogin();
-        }
-
-        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
-
-        String rememberMe = formData.getFirst("rememberMe");
-        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
-
-        event.client(clientSession.getClient().getClientId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.AUTH_METHOD, "form")
-                .detail(Details.USERNAME, username);
-
-        if (remember) {
-            event.detail(Details.REMEMBER_ME, "true");
-        }
-
-
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-        if (!client.isEnabled()) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-        if (formData.containsKey("cancel")) {
-            event.error(Errors.REJECTED_BY_USER);
-            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
-            protocol.setRealm(realm)
-                    .setHttpHeaders(headers)
-                    .setUriInfo(uriInfo);
-            return protocol.cancelLogin(clientSession);
-        }
-
-        AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
-
-        if (remember) {
-            authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
-        } else {
-            authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
-        }
-
-        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
-        if (user != null) {
-            event.user(user);
-        }
-
-        switch (status) {
-            case SUCCESS:
-            case ACTIONS_REQUIRED:
-                UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null);
-                TokenManager.attachClientSession(userSession, clientSession);
-                event.session(userSession);
-                return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
-            case ACCOUNT_TEMPORARILY_DISABLED:
-                event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-            case ACCOUNT_DISABLED:
-                event.error(Errors.USER_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setError(Messages.ACCOUNT_DISABLED)
-                        .setClientSessionCode(clientCode.getCode())
-                        .setFormData(formData).createLogin();
-            case MISSING_TOTP:
-                formData.remove(CredentialRepresentation.PASSWORD);
-
-                String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
-                formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
-
-                return Flows.forms(this.session, realm, client, uriInfo, headers)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLoginTotp();
-            case INVALID_USER:
-                event.error(Errors.USER_NOT_FOUND);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-            default:
-                event.error(Errors.INVALID_USER_CREDENTIALS);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
-                        .setFormData(formData)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createLogin();
-        }
-    }
-
-    /**
-     * Registration
-     *
-     * @param code
-     * @param formData
-     * @return
-     */
-    @Path("request/registration")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processRegister(@QueryParam("code") String code,
-                                    final MultivaluedMap<String, String> formData) {
-        event.event(EventType.REGISTER);
-        if (!checkSsl()) {
-            event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        if (!realm.isRegistrationAllowed()) {
-            event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
-        }
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
-        if (clientCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
-        }
-
-        String username = formData.getFirst("username");
-        String email = formData.getFirst("email");
-        if (realm.isRegistrationEmailAsUsername()) {
-            username = email;
-            formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
-        }
-        ClientSessionModel clientSession = clientCode.getClientSession();
-        event.client(clientSession.getClient())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.USERNAME, username)
-                .detail(Details.EMAIL, email)
-                .detail(Details.REGISTER_METHOD, "form");
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-
-        if (!client.isEnabled()) {
-            event.error(Errors.CLIENT_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-
-        List<String> requiredCredentialTypes = new LinkedList<String>();
-        for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
-            requiredCredentialTypes.add(m.getType());
-        }
-
-        // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
-        String errorMessage = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes);
-        Object[] parameters = new Object[0];
-        if (errorMessage == null) {
-            PasswordPolicy.Error error = Validation.validatePassword(formData, realm.getPasswordPolicy());
-            if(error != null){
-                errorMessage = error.getMessage();
-                parameters = error.getParameters();
-            }
-        }
-
-        if (errorMessage != null) {
-            event.error(Errors.INVALID_REGISTRATION);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(errorMessage, parameters)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        // Validate that user with this username doesn't exist in realm or any federation provider
-        if (session.users().getUserByUsername(username, realm) != null) {
-            event.error(Errors.USERNAME_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(Messages.USERNAME_EXISTS)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        // Validate that user with this email doesn't exist in realm or any federation provider
-        if (session.users().getUserByEmail(email, realm) != null) {
-            event.error(Errors.EMAIL_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo, headers)
-                    .setError(Messages.EMAIL_EXISTS)
-                    .setFormData(formData)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createRegistration();
-        }
-
-        UserModel user = session.users().addUser(realm, username);
-        user.setEnabled(true);
-        user.setFirstName(formData.getFirst("firstName"));
-        user.setLastName(formData.getFirst("lastName"));
-
-        user.setEmail(email);
-
-        if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
-            UserCredentialModel credentials = new UserCredentialModel();
-            credentials.setType(CredentialRepresentation.PASSWORD);
-            credentials.setValue(formData.getFirst("password"));
-
-            boolean passwordUpdateSuccessful;
-            String passwordUpdateError = null;
-            Object[] passwordUpdateErrorParameters = null;
-            try {
-                session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password")));
-                passwordUpdateSuccessful = true;
-            } catch (ModelException me) {
-                passwordUpdateSuccessful = false;
-                passwordUpdateError = me.getMessage();
-                passwordUpdateErrorParameters = me.getParameters();
-            } catch (Exception ape) {
-                passwordUpdateSuccessful = false;
-                passwordUpdateError = ape.getMessage();
-            }
-
-            // User already registered, but force him to update password
-            if (!passwordUpdateSuccessful) {
-                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-                return Flows.forms(session, realm, client, uriInfo, headers)
-                        .setError(passwordUpdateError, passwordUpdateErrorParameters)
-                        .setClientSessionCode(clientCode.getCode())
-                        .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
-            }
-        }
-
-        AttributeFormDataProcessor.process(formData, realm, user);
-
-        event.user(user).success();
-        event = new EventBuilder(realm, session, clientConnection);
-
-        return processLogin(code, formData);
-    }
-
-    /**
-     * OAuth grant page.  You should not invoked this directly!
-     *
-     * @param formData
-     * @return
-     */
-    @Path("consent")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response processConsent(final MultivaluedMap<String, String> formData) {
-        event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
-
-
-        if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-
-        String code = formData.getFirst("code");
-
-        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE);
-        }
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
-
-        String redirect = clientSession.getRedirectUri();
-
-        event.client(clientSession.getClient())
-                .user(clientSession.getUserSession().getUser())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.REDIRECT_URI, redirect);
-
-        UserSessionModel userSession = clientSession.getUserSession();
-        if (userSession != null) {
-            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-            event.detail(Details.USERNAME, userSession.getLoginUsername());
-            if (userSession.isRememberMe()) {
-                event.detail(Details.REMEMBER_ME, "true");
-            }
-        }
-
-        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
-        }
-        event.session(userSession);
-
-        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
-        protocol.setRealm(realm)
-                .setHttpHeaders(headers)
-                .setUriInfo(uriInfo);
-        if (formData.containsKey("cancel")) {
-            event.error(Errors.REJECTED_BY_USER);
-            return protocol.consentDenied(clientSession);
-        }
-
-        event.success();
-
-        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
-    }
-
-    @Path("profile")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updateProfile(@QueryParam("code") String code,
-                                  final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_PROFILE);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String error = Validation.validateUpdateProfileForm(formData);
-        if (error != null) {
-            return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PROFILE);
-        }
-
-        user.setFirstName(formData.getFirst("firstName"));
-        user.setLastName(formData.getFirst("lastName"));
-
-        String email = formData.getFirst("email");
-
-        String oldEmail = user.getEmail();
-        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
-
-        if (emailChanged) {
-            UserModel userByEmail = session.users().getUserByEmail(email, realm);
-
-            // check for duplicated email
-            if (userByEmail != null && !userByEmail.getId().equals(user.getId())) {
-                return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS)
-                        .setClientSessionCode(accessCode.getCode())
-                        .createResponse(RequiredAction.UPDATE_PROFILE);
-            }
-
-            user.setEmail(email);
-            user.setEmailVerified(false);
-        }
-
-        user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
-        event.clone().event(EventType.UPDATE_PROFILE).success();
-
-        if (emailChanged) {
-            event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
-        }
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-    @Path("totp")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updateTotp(@QueryParam("code") String code,
-                               final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_TOTP);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String totp = formData.getFirst("totp");
-        String totpSecret = formData.getFirst("totpSecret");
-
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
-        if (Validation.isEmpty(totp)) {
-            return loginForms.setError(Messages.MISSING_TOTP)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.CONFIGURE_TOTP);
-        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-            return loginForms.setError(Messages.INVALID_TOTP)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.CONFIGURE_TOTP);
-        }
-
-        UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.TOTP);
-        credentials.setValue(totpSecret);
-        session.users().updateCredential(realm, user, credentials);
-
-        user.setTotp(true);
-
-        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
-
-        event.clone().event(EventType.UPDATE_TOTP).success();
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-    @Path("password")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updatePassword(@QueryParam("code") String code,
-                                   final MultivaluedMap<String, String> formData) {
-        event.event(EventType.UPDATE_PASSWORD);
-        Checks checks = new Checks();
-        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            return checks.response;
-        }
-        ClientSessionCode accessCode = checks.clientCode;
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        UserSessionModel userSession = clientSession.getUserSession();
-        UserModel user = userSession.getUser();
-
-        initEvent(clientSession);
-
-        String passwordNew = formData.getFirst("password-new");
-        String passwordConfirm = formData.getFirst("password-confirm");
-
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
-        if (Validation.isEmpty(passwordNew)) {
-            return loginForms.setError(Messages.MISSING_PASSWORD)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } else if (!passwordNew.equals(passwordConfirm)) {
-            return loginForms.setError(Messages.NOTMATCH_PASSWORD)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        }
-
-        try {
-            session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
-        } catch (ModelException me) {
-            return loginForms.setError(me.getMessage(), me.getParameters())
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } catch (Exception ape) {
-            return loginForms.setError(ape.getMessage())
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        }
-
-        user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
-
-        event.event(EventType.UPDATE_PASSWORD).success();
-
-        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
-            String actionCookieValue = getActionCookie();
-            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage();
-            }
-        }
-
-        event = event.clone().event(EventType.LOGIN);
-
-        return redirectOauth(user, accessCode, clientSession, userSession);
-    }
-
-
-    @Path("email-verification")
-    @GET
-    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
-        event.event(EventType.VERIFY_EMAIL);
-        if (key != null) {
-            Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            ClientSessionModel clientSession = accessCode.getClientSession();
-            UserSessionModel userSession = clientSession.getUserSession();
-            UserModel user = userSession.getUser();
-            initEvent(clientSession);
-            user.setEmailVerified(true);
-
-            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
-
-            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
-
-            String actionCookieValue = getActionCookie();
-            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage();
-            }
-
-            event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
-
-            return redirectOauth(user, accessCode, clientSession, userSession);
-        } else {
-            Checks checks = new Checks();
-            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            ClientSessionModel clientSession = accessCode.getClientSession();
-            UserSessionModel userSession = clientSession.getUserSession();
-            initEvent(clientSession);
-
-            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
-
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(accessCode.getCode())
-                    .setUser(userSession.getUser())
-                    .createResponse(RequiredAction.VERIFY_EMAIL);
-        }
-    }
-
-    @Path("password-reset")
-    @GET
-    public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
-        event.event(EventType.RESET_PASSWORD);
-        if (key != null) {
-            Checks checks = new Checks();
-            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
-                return checks.response;
-            }
-            ClientSessionCode accessCode = checks.clientCode;
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createResponse(RequiredAction.UPDATE_PASSWORD);
-        } else {
-            return Flows.forms(session, realm, null, uriInfo, headers)
-                    .setClientSessionCode(code)
-                    .createPasswordReset();
-        }
-    }
-
-    @Path("password-reset")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response sendPasswordReset(@QueryParam("code") String code,
-                                      final MultivaluedMap<String, String> formData) {
-        event.event(EventType.SEND_RESET_PASSWORD);
-        if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
-        }
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
-        }
-        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null) {
-            event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
-        }
-        ClientSessionModel clientSession = accessCode.getClientSession();
-
-        String username = formData.getFirst("username");
-
-        ClientModel client = clientSession.getClient();
-        if (client == null) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
-        }
-        if (!client.isEnabled()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
-        }
-
-        event.client(client.getClientId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.AUTH_METHOD, "form")
-                .detail(Details.USERNAME, username);
-
-        UserModel user = session.users().getUserByUsername(username, realm);
-        if (user == null && username.contains("@")) {
-            user = session.users().getUserByEmail(username, realm);
-        }
-
-        if (user == null) {
-            event.error(Errors.USER_NOT_FOUND);
-        } else if(!user.isEnabled()) {
-            event.user(user).error(Errors.USER_DISABLED);
-        }
-        else if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
-            event.user(user).error(Errors.INVALID_EMAIL);
-        } else{
-            event.user(user);
-
-            UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
-            event.session(userSession);
-            TokenManager.attachClientSession(userSession, clientSession);
-
-            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
-
-            try {
-                UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
-                builder.queryParam("key", accessCode.getCode());
-
-                String link = builder.build(realm.getName()).toString();
-                long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
-
-                this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
-
-                event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
-            } catch (EmailException e) {
-                event.error(Errors.EMAIL_SEND_FAILED);
-                logger.error("Failed to send password reset email", e);
-                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR)
-                        .setClientSessionCode(accessCode.getCode())
-                        .createErrorPage();
-            }
-
-            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
-        }
-
-        return Flows.forms(session, realm, client,  uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset();
-    }
-
-    private String getActionCookie() {
-        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
-        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
-        return cookie != null ? cookie.getValue() : null;
-    }
-
-    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
-        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
-    }
-
-    private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
-        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
-    }
-
-    private void initEvent(ClientSessionModel clientSession) {
-        event.event(EventType.LOGIN).client(clientSession.getClient())
-                .user(clientSession.getUserSession().getUser())
-                .session(clientSession.getUserSession().getId())
-                .detail(Details.CODE_ID, clientSession.getId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code");
-
-        UserSessionModel userSession = clientSession.getUserSession();
-
-        if (userSession != null) {
-            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-            event.detail(Details.USERNAME, userSession.getLoginUsername());
-            if (userSession.isRememberMe()) {
-                event.detail(Details.REMEMBER_ME, "true");
-            }
-        }
-    }
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.services.resources;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+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.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+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.FormMessage;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.PasswordToken;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.Urls;
+import org.keycloak.services.util.CookieHelper;
+import org.keycloak.services.validation.Validation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Providers;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginActionsService {
+
+    protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
+
+    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
+
+    private RealmModel realm;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    protected Providers providers;
+
+    @Context
+    protected KeycloakSession session;
+
+    private AuthenticationManager authManager;
+
+    private EventBuilder event;
+
+    public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return loginActionsBaseUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
+        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
+    }
+
+    public static UriBuilder processLoginUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return processLoginUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
+        return uriBuilder.path(OIDCLoginProtocolService.class, "processLogin");
+    }
+
+    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return processOAuthUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder);
+        return uriBuilder.path(OIDCLoginProtocolService.class, "processOAuth");
+    }
+
+    public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) {
+        this.realm = realm;
+        this.authManager = authManager;
+        this.event = event;
+    }
+
+    private boolean checkSsl() {
+        if (uriInfo.getBaseUri().getScheme().equals("https")) {
+            return true;
+        } else {
+            return !realm.getSslRequired().isRequired(clientConnection);
+        }
+    }
+
+
+    private class Checks {
+        ClientSessionCode clientCode;
+        Response response;
+
+        boolean check(String code, ClientSessionModel.Action requiredAction) {
+            if (!check(code)) {
+                return false;
+            } else if (!clientCode.isValid(requiredAction)) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
+            if (!check(code)) {
+                return false;
+            } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE);
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        public boolean check(String code) {
+            if (!checkSsl()) {
+                event.error(Errors.SSL_REQUIRED);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+                return false;
+            }
+            if (!realm.isEnabled()) {
+                event.error(Errors.REALM_DISABLED);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+                return false;
+            }
+            clientCode = ClientSessionCode.parse(code, session, realm);
+            if (clientCode == null) {
+                event.error(Errors.INVALID_CODE);
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * protocol independent login page entry point
+     *
+     *
+     * @param code
+     * @return
+     */
+    @Path("login")
+    @GET
+    public Response loginPage(@QueryParam("code") String code) {
+        event.event(EventType.LOGIN);
+        Checks checks = new Checks();
+        if (!checks.check(code)) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
+            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        }
+
+        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
+                .setClientSessionCode(clientSessionCode.getCode());
+
+        return forms.createLogin();
+    }
+
+    /**
+     * protocol independent registration page entry point
+     *
+     * @param code
+     * @return
+     */
+    @Path("registration")
+    @GET
+    public Response registerPage(@QueryParam("code") String code) {
+        event.event(EventType.REGISTER);
+        if (!realm.isRegistrationAllowed()) {
+            event.error(Errors.REGISTRATION_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
+        }
+
+        Checks checks = new Checks();
+        if (!checks.check(code)) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+
+        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+
+        return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
+                .setClientSessionCode(clientSessionCode.getCode())
+                .createRegistration();
+    }
+
+    /**
+     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
+     *
+     * @param code
+     * @param formData
+     * @return
+     */
+    @Path("request/login")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processLogin(@QueryParam("code") String code,
+                                 final MultivaluedMap<String, String> formData) {
+        event.event(EventType.LOGIN);
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
+        if (clientCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+
+        ClientSessionModel clientSession = clientCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
+            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
+            event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
+            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createLogin();
+        }
+
+        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
+
+        String rememberMe = formData.getFirst("rememberMe");
+        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
+
+        event.client(clientSession.getClient().getClientId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
+        if (remember) {
+            event.detail(Details.REMEMBER_ME, "true");
+        }
+
+
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+        if (formData.containsKey("cancel")) {
+            event.error(Errors.REJECTED_BY_USER);
+            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+            protocol.setRealm(realm)
+                    .setHttpHeaders(headers)
+                    .setUriInfo(uriInfo);
+            return protocol.cancelLogin(clientSession);
+        }
+
+        AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
+
+        if (remember) {
+            authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
+        } else {
+            authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
+        }
+
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
+        if (user != null) {
+            event.user(user);
+        }
+
+        switch (status) {
+            case SUCCESS:
+            case ACTIONS_REQUIRED:
+                UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null);
+                TokenManager.attachClientSession(userSession, clientSession);
+                event.session(userSession);
+                return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+            case ACCOUNT_TEMPORARILY_DISABLED:
+                event.error(Errors.USER_TEMPORARILY_DISABLED);
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+            case ACCOUNT_DISABLED:
+                event.error(Errors.USER_DISABLED);
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setError(Messages.ACCOUNT_DISABLED)
+                        .setClientSessionCode(clientCode.getCode())
+                        .setFormData(formData).createLogin();
+            case MISSING_TOTP:
+                formData.remove(CredentialRepresentation.PASSWORD);
+
+                String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
+                formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLoginTotp();
+            case INVALID_USER:
+                event.error(Errors.USER_NOT_FOUND);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+            default:
+                event.error(Errors.INVALID_USER_CREDENTIALS);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
+                        .setFormData(formData)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createLogin();
+        }
+    }
+
+    /**
+     * Registration
+     *
+     * @param code
+     * @param formData
+     * @return
+     */
+    @Path("request/registration")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processRegister(@QueryParam("code") String code,
+                                    final MultivaluedMap<String, String> formData) {
+        event.event(EventType.REGISTER);
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        if (!realm.isRegistrationAllowed()) {
+            event.error(Errors.REGISTRATION_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
+        }
+        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
+        if (clientCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
+        }
+
+        String username = formData.getFirst(Validation.FIELD_USERNAME);
+        String email = formData.getFirst(Validation.FIELD_EMAIL);
+        if (realm.isRegistrationEmailAsUsername()) {
+            username = email;
+            formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
+        }
+        ClientSessionModel clientSession = clientCode.getClientSession();
+        event.client(clientSession.getClient())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.USERNAME, username)
+                .detail(Details.EMAIL, email)
+                .detail(Details.REGISTER_METHOD, "form");
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+
+        List<String> requiredCredentialTypes = new LinkedList<String>();
+        for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
+            requiredCredentialTypes.add(m.getType());
+        }
+
+        // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
+        List<FormMessage> errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy());
+
+        if (errors != null && !errors.isEmpty()) {
+            event.error(Errors.INVALID_REGISTRATION);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setErrors(errors)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        // Validate that user with this username doesn't exist in realm or any federation provider
+        if (session.users().getUserByUsername(username, realm) != null) {
+            event.error(Errors.USERNAME_IN_USE);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setError(Messages.USERNAME_EXISTS)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        // Validate that user with this email doesn't exist in realm or any federation provider
+        if (email != null && session.users().getUserByEmail(email, realm) != null) {
+            event.error(Errors.EMAIL_IN_USE);
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setError(Messages.EMAIL_EXISTS)
+                    .setFormData(formData)
+                    .setClientSessionCode(clientCode.getCode())
+                    .createRegistration();
+        }
+
+        UserModel user = session.users().addUser(realm, username);
+        user.setEnabled(true);
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+
+        user.setEmail(email);
+
+        if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+            UserCredentialModel credentials = new UserCredentialModel();
+            credentials.setType(CredentialRepresentation.PASSWORD);
+            credentials.setValue(formData.getFirst("password"));
+
+            boolean passwordUpdateSuccessful;
+            String passwordUpdateError = null;
+            Object[] passwordUpdateErrorParameters = null;
+            try {
+                session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password")));
+                passwordUpdateSuccessful = true;
+            } catch (ModelException me) {
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = me.getMessage();
+                passwordUpdateErrorParameters = me.getParameters();
+            } catch (Exception ape) {
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = ape.getMessage();
+            }
+
+            // User already registered, but force him to update password
+            if (!passwordUpdateSuccessful) {
+                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                return Flows.forms(session, realm, client, uriInfo, headers)
+                        .setError(passwordUpdateError, passwordUpdateErrorParameters)
+                        .setClientSessionCode(clientCode.getCode())
+                        .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
+        }
+
+        AttributeFormDataProcessor.process(formData, realm, user);
+
+        event.user(user).success();
+        event = new EventBuilder(realm, session, clientConnection);
+
+        return processLogin(code, formData);
+    }
+
+    /**
+     * OAuth grant page.  You should not invoked this directly!
+     *
+     * @param formData
+     * @return
+     */
+    @Path("consent")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processConsent(final MultivaluedMap<String, String> formData) {
+        event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+
+
+        if (!checkSsl()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+
+        String code = formData.getFirst("code");
+
+        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
+        if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE);
+        }
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        String redirect = clientSession.getRedirectUri();
+
+        event.client(clientSession.getClient())
+                .user(clientSession.getUserSession().getUser())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.REDIRECT_URI, redirect);
+
+        UserSessionModel userSession = clientSession.getUserSession();
+        if (userSession != null) {
+            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
+            event.detail(Details.USERNAME, userSession.getLoginUsername());
+            if (userSession.isRememberMe()) {
+                event.detail(Details.REMEMBER_ME, "true");
+            }
+        }
+
+        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+        }
+        event.session(userSession);
+
+        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+        protocol.setRealm(realm)
+                .setHttpHeaders(headers)
+                .setUriInfo(uriInfo);
+        if (formData.containsKey("cancel")) {
+            event.error(Errors.REJECTED_BY_USER);
+            return protocol.consentDenied(clientSession);
+        }
+
+        event.success();
+
+        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
+    }
+
+    @Path("profile")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateProfile(@QueryParam("code") String code,
+                                  final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_PROFILE);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(formData);
+        if (errors != null && !errors.isEmpty()) {
+            return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setErrors(errors)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PROFILE);
+        }
+
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+
+        String email = formData.getFirst("email");
+
+        String oldEmail = user.getEmail();
+        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
+
+        if (emailChanged) {
+            UserModel userByEmail = session.users().getUserByEmail(email, realm);
+
+            // check for duplicated email
+            if (userByEmail != null && !userByEmail.getId().equals(user.getId())) {
+                return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS)
+                        .setClientSessionCode(accessCode.getCode())
+                        .createResponse(RequiredAction.UPDATE_PROFILE);
+            }
+
+            user.setEmail(email);
+            user.setEmailVerified(false);
+        }
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
+        event.clone().event(EventType.UPDATE_PROFILE).success();
+
+        if (emailChanged) {
+            event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
+        }
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+    @Path("totp")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateTotp(@QueryParam("code") String code,
+                               final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_TOTP);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        String totp = formData.getFirst("totp");
+        String totpSecret = formData.getFirst("totpSecret");
+
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
+        if (Validation.isEmpty(totp)) {
+            return loginForms.setError(Messages.MISSING_TOTP)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.CONFIGURE_TOTP);
+        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+            return loginForms.setError(Messages.INVALID_TOTP)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.CONFIGURE_TOTP);
+        }
+
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setValue(totpSecret);
+        session.users().updateCredential(realm, user, credentials);
+
+        user.setTotp(true);
+
+        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
+
+        event.clone().event(EventType.UPDATE_TOTP).success();
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+    @Path("password")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updatePassword(@QueryParam("code") String code,
+                                   final MultivaluedMap<String, String> formData) {
+        event.event(EventType.UPDATE_PASSWORD);
+        Checks checks = new Checks();
+        if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            return checks.response;
+        }
+        ClientSessionCode accessCode = checks.clientCode;
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        UserSessionModel userSession = clientSession.getUserSession();
+        UserModel user = userSession.getUser();
+
+        initEvent(clientSession);
+
+        String passwordNew = formData.getFirst("password-new");
+        String passwordConfirm = formData.getFirst("password-confirm");
+
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
+        if (Validation.isEmpty(passwordNew)) {
+            return loginForms.setError(Messages.MISSING_PASSWORD)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } else if (!passwordNew.equals(passwordConfirm)) {
+            return loginForms.setError(Messages.NOTMATCH_PASSWORD)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        try {
+            session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
+        } catch (ModelException me) {
+            return loginForms.setError(me.getMessage(), me.getParameters())
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } catch (Exception ape) {
+            return loginForms.setError(ape.getMessage())
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
+
+        event.event(EventType.UPDATE_PASSWORD).success();
+
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            String actionCookieValue = getActionCookie();
+            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage();
+            }
+        }
+
+        event = event.clone().event(EventType.LOGIN);
+
+        return redirectOauth(user, accessCode, clientSession, userSession);
+    }
+
+
+    @Path("email-verification")
+    @GET
+    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
+        event.event(EventType.VERIFY_EMAIL);
+        if (key != null) {
+            Checks checks = new Checks();
+            if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            ClientSessionModel clientSession = accessCode.getClientSession();
+            UserSessionModel userSession = clientSession.getUserSession();
+            UserModel user = userSession.getUser();
+            initEvent(clientSession);
+            user.setEmailVerified(true);
+
+            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
+
+            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
+
+            String actionCookieValue = getActionCookie();
+            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage();
+            }
+
+            event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
+
+            return redirectOauth(user, accessCode, clientSession, userSession);
+        } else {
+            Checks checks = new Checks();
+            if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            ClientSessionModel clientSession = accessCode.getClientSession();
+            UserSessionModel userSession = clientSession.getUserSession();
+            initEvent(clientSession);
+
+            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
+
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(accessCode.getCode())
+                    .setUser(userSession.getUser())
+                    .createResponse(RequiredAction.VERIFY_EMAIL);
+        }
+    }
+
+    @Path("password-reset")
+    @GET
+    public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
+        event.event(EventType.RESET_PASSWORD);
+        if (key != null) {
+            Checks checks = new Checks();
+            if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) {
+                return checks.response;
+            }
+            ClientSessionCode accessCode = checks.clientCode;
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
+        } else {
+            return Flows.forms(session, realm, null, uriInfo, headers)
+                    .setClientSessionCode(code)
+                    .createPasswordReset();
+        }
+    }
+
+    @Path("password-reset")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response sendPasswordReset(@QueryParam("code") String code,
+                                      final MultivaluedMap<String, String> formData) {
+        event.event(EventType.SEND_RESET_PASSWORD);
+        if (!checkSsl()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+        }
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+        }
+        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
+        if (accessCode == null) {
+            event.error(Errors.INVALID_CODE);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
+        }
+        ClientSessionModel clientSession = accessCode.getClientSession();
+
+        String username = formData.getFirst("username");
+
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
+        }
+        if (!client.isEnabled()) {
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+        }
+
+        event.client(client.getClientId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
+        UserModel user = session.users().getUserByUsername(username, realm);
+        if (user == null && username.contains("@")) {
+            user = session.users().getUserByEmail(username, realm);
+        }
+
+        if (user == null) {
+            event.error(Errors.USER_NOT_FOUND);
+        } else if(!user.isEnabled()) {
+            event.user(user).error(Errors.USER_DISABLED);
+        }
+        else if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
+            event.user(user).error(Errors.INVALID_EMAIL);
+        } else{
+            event.user(user);
+
+            UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
+            event.session(userSession);
+            TokenManager.attachClientSession(userSession, clientSession);
+
+            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD);
+
+            try {
+                UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
+                builder.queryParam("key", accessCode.getCode());
+
+                String link = builder.build(realm.getName()).toString();
+                long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
+
+                this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
+
+                event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
+            } catch (EmailException e) {
+                event.error(Errors.EMAIL_SEND_FAILED);
+                logger.error("Failed to send password reset email", e);
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR)
+                        .setClientSessionCode(accessCode.getCode())
+                        .createErrorPage();
+            }
+
+            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
+        }
+
+        return Flows.forms(session, realm, client,  uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset();
+    }
+
+    private String getActionCookie() {
+        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
+        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
+        return cookie != null ? cookie.getValue() : null;
+    }
+
+    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
+        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
+    }
+
+    private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
+        return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+    }
+
+    private void initEvent(ClientSessionModel clientSession) {
+        event.event(EventType.LOGIN).client(clientSession.getClient())
+                .user(clientSession.getUserSession().getUser())
+                .session(clientSession.getUserSession().getId())
+                .detail(Details.CODE_ID, clientSession.getId())
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code");
+
+        UserSessionModel userSession = clientSession.getUserSession();
+
+        if (userSession != null) {
+            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
+            event.detail(Details.USERNAME, userSession.getLoginUsername());
+            if (userSession.isRememberMe()) {
+                event.detail(Details.REMEMBER_ME, "true");
+            }
+        }
+    }
+}
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 f72a630..1a4392b 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,75 +1,90 @@
 package org.keycloak.services.validation;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.MultivaluedMap;
+
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
+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.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 String validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes) {
-        if (isEmpty(formData.getFirst("firstName"))) {
-            return Messages.MISSING_FIRST_NAME;
-        }
+    public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
+        List<FormMessage> errors = new ArrayList<>();
 
-        if (isEmpty(formData.getFirst("lastName"))) {
-            return Messages.MISSING_LAST_NAME;
+        if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst(FIELD_USERNAME))) {
+            addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
         }
 
-        if (isEmpty(formData.getFirst("email"))) {
-            return Messages.MISSING_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_FIRST_NAME))) {
+            addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
         }
 
-        if (!isEmailValid(formData.getFirst("email"))) {
-            return Messages.INVALID_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_LAST_NAME))) {
+            addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
         }
 
-        if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst("username"))) {
-            return Messages.MISSING_USERNAME;
+        if (isEmpty(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 (isEmpty(formData.getFirst(CredentialRepresentation.PASSWORD))) {
-                return Messages.MISSING_PASSWORD;
-            }
-
-            if (!formData.getFirst("password").equals(formData.getFirst("password-confirm"))) {
-                return Messages.INVALID_PASSWORD_CONFIRM;
+            if (isEmpty(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);
             }
         }
 
-        return null;
+        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;
     }
-
-    public static PasswordPolicy.Error validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
-        return policy.validate(formData.getFirst("username"), formData.getFirst("password"));
+    
+    private static void addError(List<FormMessage> errors, String field, String message){
+        errors.add(new FormMessage(field, message));
     }
 
-    public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
-        if (isEmpty(formData.getFirst("firstName"))) {
-            return Messages.MISSING_FIRST_NAME;
-        }
 
-        if (isEmpty(formData.getFirst("lastName"))) {
-            return Messages.MISSING_LAST_NAME;
+    public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
+        List<FormMessage> errors = new ArrayList<>();
+        
+        if (isEmpty(formData.getFirst(FIELD_FIRST_NAME))) {
+            addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
         }
 
-        if (isEmpty(formData.getFirst("email"))) {
-            return Messages.MISSING_EMAIL;
+        if (isEmpty(formData.getFirst(FIELD_LAST_NAME))) {
+            addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
         }
 
-        if (!isEmailValid(formData.getFirst("email"))) {
-            return Messages.INVALID_EMAIL;
+        if (isEmpty(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 null;
+        return errors;
     }
 
     public static boolean isEmpty(String s) {