keycloak-aplcache
Changes
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java 119(+84 -35)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/MessageBean.java 60(+0 -60)
forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/MessagesPerFieldBean.java 73(+73 -0)
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) {