diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java index 98bdc0fd1..4af1d5cee 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java @@ -26,6 +26,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; +import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.form.Field; import org.glyptodon.guacamole.net.auth.Credentials; @@ -121,6 +122,14 @@ public class UserContextService { throw new GuacamoleInsufficientCredentialsException("Password expired", EXPIRED_PASSWORD); } + // New password must be different from old password + if (newPassword.equals(credentials.getPassword())) + throw new GuacamoleClientException("The new password must be different from the expired password."); + + // New password must not be blank + if (newPassword.isEmpty()) + throw new GuacamoleClientException("The new password may not be blank."); + // STUB: Change password if new password given logger.info("Resetting expired password of user \"{}\".", user.getIdentifier()); diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index 26316830c..b54ab24cd 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -64,6 +64,26 @@ angular.module('login').directive('guacLogin', [function guacLogin() { var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); + /** + * An action to be provided along with the object assigned to + * $scope.loginStatus which closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "LOGIN.ACTION_ACKNOWLEDGE", + // Handle action + callback : function acknowledgeCallback() { + $scope.loginStatus = false; + } + }; + + /** + * The currently-visible notification describing login status, or false + * if no notification should be shown. + * + * @type Notification|Boolean|Object + */ + $scope.loginStatus = false; + /** * Whether an error occurred during login. * @@ -110,12 +130,15 @@ angular.module('login').directive('guacLogin', [function guacLogin() { */ $scope.login = function login() { + // Start with cleared status + $scope.loginError = false; + $scope.loginStatus = false; + // Attempt login once existing session is destroyed authenticationService.authenticate($scope.enteredValues) // Clear and reload upon success .then(function loginSuccessful() { - $scope.loginError = false; $scope.enteredValues = {}; $route.reload(); }) @@ -123,13 +146,24 @@ angular.module('login').directive('guacLogin', [function guacLogin() { // Reset upon failure ['catch'](function loginFailed(error) { - // Flag generic error for invalid login - if (error.type === Error.Type.INVALID_CREDENTIALS) - $scope.loginError = true; - // Clear out passwords if the credentials were rejected for any reason if (error.type !== Error.Type.INSUFFICIENT_CREDENTIALS) { - angular.forEach($scope.form, function clearEnteredValueIfPassword(field) { + + // Flag generic error for invalid login + if (error.type === Error.Type.INVALID_CREDENTIALS) + $scope.loginError = true; + + // Display error if anything else goes wrong + else + $scope.loginStatus = { + 'className' : 'error', + 'title' : 'LOGIN.DIALOG_HEADER_ERROR', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }; + + // Clear all visible password fields + angular.forEach($scope.remainingFields, function clearEnteredValueIfPassword(field) { // Remove entered value only if field is a password field if (field.type === Field.Type.PASSWORD) diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index db020c86b..bcb8ed48e 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -49,4 +49,12 @@ + + +
+
+ +
+
+ diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 08d050a10..1027a45fb 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -143,7 +143,10 @@ "LOGIN": { - "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_INVALID_LOGIN" : "Invalid Login", @@ -161,7 +164,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Location:", "FIELD_HEADER_NAME" : "Name:", @@ -194,7 +197,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection Group", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Location:", "FIELD_HEADER_NAME" : "Name:", @@ -216,8 +219,8 @@ "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", - "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", @@ -414,7 +417,7 @@ "ACTION_NEW_CONNECTION" : "New Connection", "ACTION_NEW_CONNECTION_GROUP" : "New Group", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.", @@ -469,7 +472,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_NEW_USER" : "New User", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.", @@ -484,7 +487,7 @@ "ACTION_DELETE" : "Kill Sessions", "DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_PLACEHOLDER_FILTER" : "Filter", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index ba7e6d78a..05096ba5a 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -143,7 +143,10 @@ "LOGIN": { - "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_INVALID_LOGIN" : "Неверные данные для входа", @@ -161,7 +164,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить подключение", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Размещение:", "FIELD_HEADER_NAME" : "Название:", @@ -194,7 +197,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить группу подключений", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Размещение:", "FIELD_HEADER_NAME" : "Название:", @@ -216,8 +219,8 @@ "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", - "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить пользователя", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить пользователя", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", @@ -393,7 +396,7 @@ "ACTION_NEW_CONNECTION" : "Новое подключение", "ACTION_NEW_CONNECTION_GROUP" : "Новая группа", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_CONNECTIONS" : "Нажмите на подключение, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление подключений, а также изменение их свойств (протокол, название сервера, порт и пр.).", @@ -409,7 +412,7 @@ "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_UPDATE_PASSWORD" : "@:APP.ACTION_UPDATE_PASSWORD", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_PASSWORD_BLANK" : "Пароль не может быть пустым.", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", @@ -448,7 +451,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_NEW_USER" : "Новый пользователь", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_USERS" : "Нажмите на пользователя, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление пользователей, а также изменение паролей.", @@ -463,7 +466,7 @@ "ACTION_DELETE" : "Завершить сессии", "DIALOG_HEADER_CONFIRM_DELETE" : "Завершение сессий", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_PLACEHOLDER_FILTER" : "Фильтр",