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" : "Фильтр",