diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties new file mode 100644 index 0000000000..ab297dddc5 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties @@ -0,0 +1,8 @@ +invalidPasswordMinLengthMessage=Invalid password: minimum length {0}. +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters. +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits. +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters. +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters. +invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. +invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). +invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index bf5662bbff..9be9349c1a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -308,8 +308,12 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use Notifications.success("The password has been reset"); $scope.password = null; $scope.confirmPassword = null; - }, function() { - Notifications.error("Failed to reset user password"); + }, function(response) { + if (response.data && response.data.errorMessage) { + Notifications.error(response.data.errorMessage); + } else { + Notifications.error("Failed to reset user password"); + } }); }, function() { $scope.password = null; diff --git a/services/src/main/java/org/keycloak/messages/MessagesProvider.java b/services/src/main/java/org/keycloak/messages/MessagesProvider.java new file mode 100644 index 0000000000..ddb72dd4e2 --- /dev/null +++ b/services/src/main/java/org/keycloak/messages/MessagesProvider.java @@ -0,0 +1,12 @@ +package org.keycloak.messages; + +import org.keycloak.provider.Provider; + +/** + * @author Leonardo Zanivan + */ +public interface MessagesProvider extends Provider { + + String getMessage(String messageKey, Object... parameters); + +} diff --git a/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java b/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java new file mode 100644 index 0000000000..92c0df8354 --- /dev/null +++ b/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.messages; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Leonardo Zanivan + */ +public interface MessagesProviderFactory extends ProviderFactory { + +} diff --git a/services/src/main/java/org/keycloak/messages/MessagesSpi.java b/services/src/main/java/org/keycloak/messages/MessagesSpi.java new file mode 100644 index 0000000000..6e820068c8 --- /dev/null +++ b/services/src/main/java/org/keycloak/messages/MessagesSpi.java @@ -0,0 +1,32 @@ +package org.keycloak.messages; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Leonardo Zanivan + */ +public class MessagesSpi implements Spi { + + @Override + public boolean isPrivate() { + return true; + } + + @Override + public String getName() { + return "messages"; + } + + @Override + public Class getProviderClass() { + return MessagesProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return MessagesProviderFactory.class; + } + +} diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java new file mode 100644 index 0000000000..1da7bfb9d8 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java @@ -0,0 +1,59 @@ +package org.keycloak.services.messages; + +import java.io.IOException; +import java.net.URL; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Properties; +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.messages.MessagesProvider; + +/** + * @author Leonardo Zanivan + */ +public class AdminMessagesProvider implements MessagesProvider { + + private static final Logger logger = Logger.getLogger(AdminMessagesProvider.class); + + private KeycloakSession session; + private Locale locale; + private Properties messagesBundle; + + public AdminMessagesProvider(KeycloakSession session, Locale locale) { + this.session = session; + this.locale = locale; + this.messagesBundle = getMessagesBundle(locale); + } + + @Override + public String getMessage(String messageKey, Object... parameters) { + String message = messagesBundle.getProperty(messageKey, messageKey); + return new MessageFormat(message, locale).format(parameters); + } + + @Override + public void close() { + } + + private Properties getMessagesBundle(Locale locale) { + Properties properties = new Properties(); + + if (locale == null) { + return properties; + } + + URL url = getClass().getClassLoader().getResource( + "theme/base/admin/messages/messages_" + locale.toString() + ".properties"); + if (url != null) { + try { + properties.load(url.openStream()); + } catch (IOException ex) { + logger.warn("Failed to load messages", ex); + } + } + + return properties; + } + +} diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java new file mode 100644 index 0000000000..7fda4fd97d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java @@ -0,0 +1,37 @@ +package org.keycloak.services.messages; + +import java.util.Locale; +import org.keycloak.Config; +import org.keycloak.messages.MessagesProvider; +import org.keycloak.messages.MessagesProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Leonardo Zanivan + */ +public class AdminMessagesProviderFactory implements MessagesProviderFactory { + + @Override + public MessagesProvider create(KeycloakSession session) { + return new AdminMessagesProvider(session, Locale.ENGLISH); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return "admin"; + } + +} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index b0f02add10..e4c821cd30 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -39,7 +39,6 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.HashSet; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; @@ -75,6 +74,7 @@ public class KeycloakApplication extends Application { singletons.add(new ServerVersionResource()); singletons.add(new RealmsResource()); singletons.add(new AdminRoot()); + singletons.add(new ModelExceptionMapper()); classes.add(SkeletonKeyContextResolver.class); classes.add(QRCodeResource.class); classes.add(ThemeResource.class); diff --git a/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java b/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java new file mode 100644 index 0000000000..c5cc88eb8e --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java @@ -0,0 +1,27 @@ +package org.keycloak.services.resources; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.keycloak.messages.MessagesProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelException; +import org.keycloak.services.ErrorResponse; + +/** + * @author Leonardo Zanivan + */ +@Provider +public class ModelExceptionMapper implements ExceptionMapper { + + @Context + private KeycloakSession session; + + @Override + public Response toResponse(ModelException ex) { + String message = session.getProvider(MessagesProvider.class, "admin") + .getMessage(ex.getMessage(), ex.getParameters()); + return ErrorResponse.error(message, Response.Status.BAD_REQUEST); + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory new file mode 100644 index 0000000000..341e768bbb --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory @@ -0,0 +1 @@ +org.keycloak.services.messages.AdminMessagesProviderFactory \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index e1c0b91282..a9b5a5438c 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -1,4 +1,5 @@ org.keycloak.protocol.LoginProtocolSpi org.keycloak.protocol.ProtocolMapperSpi org.keycloak.exportimport.ClientImportSpi -org.keycloak.wellknown.WellKnownSpi \ No newline at end of file +org.keycloak.wellknown.WellKnownSpi +org.keycloak.messages.MessagesSpi \ No newline at end of file