From 10aea5d0a39f9a73f85921bebc3060190df068eb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 27 May 2015 13:08:26 -0700 Subject: [PATCH 01/21] GUAC-1176: Add password expiration attribute. --- .../guacamole/auth/jdbc/user/ModeledUser.java | 19 ++++++++++-- .../guacamole/auth/jdbc/user/UserModel.java | 31 +++++++++++++++++++ .../src/main/resources/translations/en.json | 1 + .../schema/001-create-schema.sql | 1 + .../schema/upgrade/upgrade-pre-0.9.7.sql | 6 ++++ .../guacamole/auth/jdbc/user/UserMapper.xml | 19 ++++++++---- .../schema/001-create-schema.sql | 1 + .../schema/upgrade/upgrade-pre-0.9.7.sql | 6 ++++ .../guacamole/auth/jdbc/user/UserMapper.xml | 19 ++++++++---- 9 files changed, 88 insertions(+), 15 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java index 0f234c494..d35b9c07b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java @@ -58,12 +58,19 @@ public class ModeledUser extends ModeledDirectoryObject implements Us */ public static final String DISABLED_ATTRIBUTE_NAME = "disabled"; + /** + * The name of the attribute which controls whether a user's password is + * expired and must be reset upon login. + */ + public static final String EXPIRED_ATTRIBUTE_NAME = "expired"; + /** * All attributes related to restricting user accounts, within a logical * form. */ public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", "Account Restrictions", Arrays.asList( - new Field(DISABLED_ATTRIBUTE_NAME, "Disabled", "true") + new Field(DISABLED_ATTRIBUTE_NAME, "Disabled", "true"), + new Field(EXPIRED_ATTRIBUTE_NAME, "Password expired", "true") )); /** @@ -214,7 +221,10 @@ public class ModeledUser extends ModeledDirectoryObject implements Us Map attributes = new HashMap(); // Set disabled attribute - attributes.put("disabled", getModel().isDisabled() ? "true" : null); + attributes.put(DISABLED_ATTRIBUTE_NAME, getModel().isDisabled() ? "true" : null); + + // Set password expired attribute + attributes.put(EXPIRED_ATTRIBUTE_NAME, getModel().isExpired() ? "true" : null); return attributes; } @@ -223,7 +233,10 @@ public class ModeledUser extends ModeledDirectoryObject implements Us public void setAttributes(Map attributes) { // Translate disabled attribute - getModel().setDisabled("true".equals(attributes.get("disabled"))); + getModel().setDisabled("true".equals(attributes.get(DISABLED_ATTRIBUTE_NAME))); + + // Translate password expired attribute + getModel().setExpired("true".equals(attributes.get(EXPIRED_ATTRIBUTE_NAME))); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java index 1f84ab566..5057b2f5c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java @@ -48,6 +48,13 @@ public class UserModel extends ObjectModel { */ private boolean disabled; + /** + * Whether the user's password is expired. If a user's password is expired, + * it must be changed immediately upon login, and the account cannot be + * used until this occurs. + */ + private boolean expired; + /** * Creates a new, empty user. */ @@ -127,4 +134,28 @@ public class UserModel extends ObjectModel { this.disabled = disabled; } + /** + * Returns whether the user's password has expired. If a user's password is + * expired, it must be immediately changed upon login. A user account with + * an expired password cannot be used until the password has been changed. + * + * @return + * true if the user's password has expired, false otherwise. + */ + public boolean isExpired() { + return expired; + } + + /** + * Sets whether the user's password is expired. If a user's password is + * expired, it must be immediately changed upon login. A user account with + * an expired password cannot be used until the password has been changed. + * + * @param expired + * true to expire the user's password, false otherwise. + */ + public void setExpired(boolean expired) { + this.expired = expired; + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json index 920ded08f..adeadf508 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json @@ -2,6 +2,7 @@ "USER_ATTRIBUTES" : { "FIELD_HEADER_DISABLED" : "Login disabled:", + "FIELD_HEADER_EXPIRED" : "Password expired:", "SECTION_HEADER_RESTRICTIONS" : "Account Restrictions" diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql index e9545adae..d3cd2ce0d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql @@ -77,6 +77,7 @@ CREATE TABLE `guacamole_user` ( `password_hash` binary(32) NOT NULL, `password_salt` binary(32), `disabled` boolean NOT NULL DEFAULT 0, + `expired` boolean NOT NULL DEFAULT 0, PRIMARY KEY (`user_id`), UNIQUE KEY `username` (`username`) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.7.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.7.sql index 8bc41ec26..761d9be41 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.7.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.7.sql @@ -26,3 +26,9 @@ ALTER TABLE guacamole_user ADD COLUMN disabled BOOLEAN NOT NULL DEFAULT 0; +-- +-- Add per-user password expiration flag +-- + +ALTER TABLE guacamole_user ADD COLUMN expired BOOLEAN NOT NULL DEFAULT 0; + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml index de4b311cd..31a8fe407 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml @@ -33,6 +33,7 @@ + @@ -59,7 +60,8 @@ username, password_hash, password_salt, - disabled + disabled, + expired FROM guacamole_user WHERE username IN @@ -135,7 +141,8 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, - disabled = #{object.disabled,jdbcType=BOOLEAN} + disabled = #{object.disabled,jdbcType=BOOLEAN}, + expired = #{object.expired,jdbcType=BOOLEAN} WHERE user_id = #{object.objectID,jdbcType=VARCHAR} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql index d228b5844..7f14986be 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql @@ -118,6 +118,7 @@ CREATE TABLE guacamole_user ( password_hash bytea NOT NULL, password_salt bytea, disabled boolean NOT NULL DEFAULT FALSE, + expired boolean NOT NULL DEFAULT FALSE, PRIMARY KEY (user_id), diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql index ba8649629..0853e90c8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql @@ -26,3 +26,9 @@ ALTER TABLE guacamole_user ADD COLUMN disabled boolean NOT NULL DEFAULT FALSE; +-- +-- Add per-user password expiration flag +-- + +ALTER TABLE guacamole_user ADD COLUMN expired boolean NOT NULL DEFAULT FALSE; + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml index 6b2438229..d78b30abb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml @@ -33,6 +33,7 @@ + @@ -59,7 +60,8 @@ username, password_hash, password_salt, - disabled + disabled, + expired FROM guacamole_user WHERE username IN @@ -135,7 +141,8 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, - disabled = #{object.disabled,jdbcType=BOOLEAN} + disabled = #{object.disabled,jdbcType=BOOLEAN}, + expired = #{object.expired,jdbcType=BOOLEAN} WHERE user_id = #{object.objectID,jdbcType=VARCHAR} From e51363f9c510a255fe089a0254cee43736b6b74d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 27 May 2015 13:22:39 -0700 Subject: [PATCH 02/21] GUAC-1176: Define USERNAME and PASSWORD fields publicly. They aren't very helpful isolated within the USERNAME_PASSWORD object. --- .../net/auth/credentials/CredentialsInfo.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java index b68e06d5d..b3c7c1175 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java @@ -67,13 +67,25 @@ public class CredentialsInfo { */ public static final CredentialsInfo EMPTY = new CredentialsInfo(Collections.emptyList()); + /** + * A field describing the username HTTP parameter expected by Guacamole + * during login, if usernames are being used. + */ + public static final Field USERNAME = new Field("username", "username", Field.Type.USERNAME); + + /** + * A field describing the password HTTP parameter expected by Guacamole + * during login, if passwords are being used. + */ + public static final Field PASSWORD = new Field("password", "password", Field.Type.PASSWORD); + /** * CredentialsInfo object which describes standard username/password * credentials. */ public static final CredentialsInfo USERNAME_PASSWORD = new CredentialsInfo(Arrays.asList( - new Field("username", "username", Field.Type.USERNAME), - new Field("password", "password", Field.Type.PASSWORD) + USERNAME, + PASSWORD )); } From 542c87d63169106dbb60081336011f1ce8faf57e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 27 May 2015 15:15:22 -0700 Subject: [PATCH 03/21] GUAC-1176: Stub password reset logic. --- .../auth/jdbc/user/UserContextService.java | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) 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 589edd149..98bdc0fd1 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 @@ -24,8 +24,16 @@ package org.glyptodon.guacamole.auth.jdbc.user; import com.google.inject.Inject; import com.google.inject.Provider; +import java.util.Arrays; +import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.form.Field; import org.glyptodon.guacamole.net.auth.Credentials; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service which creates new UserContext instances for valid users based on @@ -35,6 +43,11 @@ import org.glyptodon.guacamole.net.auth.Credentials; */ public class UserContextService { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(UserService.class); + /** * Service for accessing users. */ @@ -47,19 +60,45 @@ public class UserContextService { @Inject private Provider userContextProvider; + /** + * The name of the HTTP parameter to expect if the user is changing their + * expired password upon login. + */ + private static final String NEW_PASSWORD_PARAMETER = "new-password"; + + /** + * The field to provide the user when their password is expired and must + * be changed. + */ + private static final Field NEW_PASSWORD = new Field(NEW_PASSWORD_PARAMETER, "New password", Field.Type.PASSWORD); + + /** + * Information describing the expected credentials if a user's password is + * expired. If a user's password is expired, it must be changed during the + * login process. + */ + private static final CredentialsInfo EXPIRED_PASSWORD = new CredentialsInfo(Arrays.asList( + CredentialsInfo.USERNAME, + CredentialsInfo.PASSWORD, + NEW_PASSWORD + )); + /** * Authenticates the user having the given credentials, returning a new - * UserContext instance if the credentials are valid. + * UserContext instance only if the credentials are valid. If the + * credentials are invalid or expired, an appropriate GuacamoleException + * will be thrown. * * @param credentials * The credentials to use to produce the UserContext. * * @return * A new UserContext instance for the user identified by the given - * credentials, or null if the credentials are not valid. + * credentials. * * @throws GuacamoleException - * If an error occurs during authentication. + * If an error occurs during authentication, or if the given + * credentials are invalid or expired. */ public org.glyptodon.guacamole.net.auth.UserContext getUserContext(Credentials credentials) @@ -69,6 +108,24 @@ public class UserContextService { ModeledUser user = userService.retrieveUser(credentials); if (user != null && !user.getModel().isDisabled()) { + // Update password if password is expired + if (user.getModel().isExpired()) { + + // Pull new password from HTTP request + HttpServletRequest request = credentials.getRequest(); + String newPassword = request.getParameter(NEW_PASSWORD_PARAMETER); + + // Require new password if account is expired + if (newPassword == null) { + logger.info("The password of user \"{}\" has expired and must be reset.", user.getIdentifier()); + throw new GuacamoleInsufficientCredentialsException("Password expired", EXPIRED_PASSWORD); + } + + // STUB: Change password if new password given + logger.info("Resetting expired password of user \"{}\".", user.getIdentifier()); + + } + // Upon successful authentication, return new user context UserContext context = userContextProvider.get(); context.init(user.getCurrentUser()); @@ -77,7 +134,7 @@ public class UserContextService { } // Otherwise, unauthorized - return null; + throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD); } From 7a3503a40e64fe8a5d5c45335d9f2320770c64fe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 00:29:36 -0700 Subject: [PATCH 04/21] GUAC-1176: Handle insufficient credentials distinctly from invalid credentials. --- .../app/auth/service/authenticationService.js | 2 +- .../app/index/controllers/indexController.js | 16 ++++- .../main/webapp/app/login/directives/login.js | 68 ++++++++++++++++--- .../webapp/app/login/templates/login.html | 2 +- guacamole/src/main/webapp/index.html | 3 +- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 6671052ed..2c0bd6bc0 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -160,7 +160,7 @@ angular.module('auth').factory('authenticationService', ['$injector', else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) $rootScope.$broadcast('guacInsufficientCredentials', parameters, error.expected); - authenticationProcess.reject(); + authenticationProcess.reject(error); }); return authenticationProcess.promise; diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index cd0897b2f..283e4a0cf 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -36,11 +36,22 @@ angular.module('index').controller('indexController', ['$scope', '$injector', */ $scope.guacNotification = guacNotification; + /** + * The credentials that the authentication service is has already accepted, + * pending additional credentials, if any. If the user is logged in, or no + * credentials have been accepted, this will be null. If credentials have + * been accepted, this will be a map of name/value pairs corresponding to + * the parameters submitted in a previous authentication attempt. + * + * @type Object. + */ + $scope.acceptedCredentials = null; + /** * The credentials that the authentication service is currently expecting, * if any. If the user is logged in, this will be null. * - * @type Form[]|Form|Field[]|Field + * @type Field[] */ $scope.expectedCredentials = null; @@ -112,6 +123,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, expected) { $scope.page.title = 'APP.NAME'; $scope.page.bodyClassName = ''; + $scope.acceptedCredentials = {}; $scope.expectedCredentials = expected; }); @@ -119,11 +131,13 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, expected) { $scope.page.title = 'APP.NAME'; $scope.page.bodyClassName = ''; + $scope.acceptedCredentials = parameters; $scope.expectedCredentials = expected; }); // Clear login screen if login was successful $scope.$on('guacLogin', function loginSuccessful() { + $scope.acceptedCredentials = null; $scope.expectedCredentials = null; }); diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index 1b8b407ca..fd05d4be3 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -39,9 +39,16 @@ angular.module('login').directive('guacLogin', [function guacLogin() { * The login form or set of fields. This will be displayed to the user * to capture their credentials. * - * @type Form[]|Form|Field[]|Field + * @type Field[] */ - form : '=' + form : '=', + + /** + * A map of all field name/value pairs that have already been provided. + * If not null, the user will be prompted to continue their login + * attempt using only the fields which remain. + */ + values : '=' }; @@ -49,6 +56,10 @@ angular.module('login').directive('guacLogin', [function guacLogin() { directive.controller = ['$scope', '$injector', function loginController($scope, $injector) { + // Required types + var Error = $injector.get('Error'); + var Field = $injector.get('Field'); + // Required services var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); @@ -61,9 +72,37 @@ angular.module('login').directive('guacLogin', [function guacLogin() { $scope.loginError = false; /** - * All form values entered by the user. + * All form values entered by the user, as parameter name/value pairs. + * + * @type Object. */ - $scope.values = {}; + $scope.enteredValues = {}; + + /** + * All form fields which have not yet been filled by the user. + * + * @type Field[] + */ + $scope.remainingFields = []; + + $scope.$watch('values', function resetEnteredValues(values) { + angular.extend($scope.enteredValues, values || {}); + }); + + $scope.$watch('form', function resetRemainingFields(fields) { + + // If no fields are provided, then no fields remain + if (!fields) { + $scope.remainingFields = []; + return; + } + + // Filter provided fields against provided values + $scope.remainingFields = fields.filter(function isRemaining(field) { + return !(field.name in $scope.values); + }); + + }); /** * Submits the currently-specified username and password to the @@ -72,19 +111,30 @@ angular.module('login').directive('guacLogin', [function guacLogin() { $scope.login = function login() { // Attempt login once existing session is destroyed - authenticationService.authenticate($scope.values) + authenticationService.authenticate($scope.enteredValues) // Clear and reload upon success .then(function loginSuccessful() { $scope.loginError = false; - $scope.values = {}; + $scope.enteredValues = {}; $route.reload(); }) // Reset upon failure - ['catch'](function loginFailed() { - $scope.loginError = true; - $scope.values.password = ''; + ['catch'](function loginFailed(error) { + + // Clear out passwords and flag error if credentials are invalid + if (error.type === Error.Type.INVALID_CREDENTIALS) { + $scope.loginError = true; + angular.forEach($scope.form, function clearEnteredValueIfPassword(field) { + + // Remove entered value only if field is a password field + if (field.type === Field.Type.PASSWORD) + delete $scope.enteredValues[field.name]; + + }); + } + }); }; diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index ac6879a14..db020c86b 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -36,7 +36,7 @@ diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 3303ec1a8..046876f10 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -50,7 +50,8 @@ - + From 85dae9e52a80d169584f3341e62fddf855d6942f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 00:35:10 -0700 Subject: [PATCH 05/21] GUAC-1176: Clear passwords if credentials are rejected for any reason (even client error). Flag generic login error only for specifically invalid credentials. --- guacamole/src/main/webapp/app/login/directives/login.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index fd05d4be3..26316830c 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -123,9 +123,12 @@ angular.module('login').directive('guacLogin', [function guacLogin() { // Reset upon failure ['catch'](function loginFailed(error) { - // Clear out passwords and flag error if credentials are invalid - if (error.type === Error.Type.INVALID_CREDENTIALS) { + // 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) { // Remove entered value only if field is a password field From 304e150a1e3ad0e67a8a55422a2bb85847486948 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 16:04:44 -0700 Subject: [PATCH 06/21] GUAC-1176: Validate new password set due to expiration. Allow errors dialogs during login process. --- .../auth/jdbc/user/UserContextService.java | 9 ++++ .../main/webapp/app/login/directives/login.js | 46 ++++++++++++++++--- .../webapp/app/login/templates/login.html | 8 ++++ .../src/main/webapp/translations/en.json | 19 ++++---- .../src/main/webapp/translations/ru.json | 21 +++++---- 5 files changed, 80 insertions(+), 23 deletions(-) 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" : "Фильтр", From 904e54090f8a5b85e391f8c6e6395927741c30dc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 16:28:47 -0700 Subject: [PATCH 07/21] GUAC-1176: Use translation strings for client error messages. --- .../guacamole/auth/jdbc/user/UserContextService.java | 4 ++-- .../src/main/resources/translations/en.json | 7 +++++++ .../src/main/resources/translations/ru_RU.json | 7 +++++++ guacamole/src/main/webapp/translations/en.json | 3 ++- guacamole/src/main/webapp/translations/ru.json | 3 ++- 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json 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 4af1d5cee..131cd19ce 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 @@ -124,11 +124,11 @@ public class UserContextService { // 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."); + throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_SAME"); // New password must not be blank if (newPassword.isEmpty()) - throw new GuacamoleClientException("The new password may not be blank."); + throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_BLANK"); // STUB: Change password if new password given logger.info("Resetting expired password of user \"{}\".", user.getIdentifier()); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json index adeadf508..2a4098d9a 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json @@ -1,4 +1,10 @@ { + + "LOGIN" : { + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", + "ERROR_PASSWORD_SAME" : "The new password must be different from the expired password." + }, + "USER_ATTRIBUTES" : { "FIELD_HEADER_DISABLED" : "Login disabled:", @@ -7,4 +13,5 @@ "SECTION_HEADER_RESTRICTIONS" : "Account Restrictions" } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json new file mode 100644 index 000000000..30a0c2ad6 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json @@ -0,0 +1,7 @@ +{ + + "LOGIN" : { + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK" + } + +} diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 1027a45fb..5a761501f 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -23,6 +23,7 @@ "DIALOG_HEADER_ERROR" : "Error", + "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", "ERROR_PASSWORD_MISMATCH" : "The provided passwords do not match.", "FIELD_HEADER_PASSWORD" : "Password:", @@ -435,7 +436,7 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", "FIELD_HEADER_LANGUAGE" : "Display language:", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index 05096ba5a..dd3947b83 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -23,6 +23,7 @@ "DIALOG_HEADER_ERROR" : "Ошибка", + "ERROR_PASSWORD_BLANK" : "Пароль не может быть пустым.", "ERROR_PASSWORD_MISMATCH" : "Указанные пароли не совпадают.", "FIELD_HEADER_PASSWORD" : "Пароль:", @@ -414,7 +415,7 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "ERROR_PASSWORD_BLANK" : "Пароль не может быть пустым.", + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", "FIELD_HEADER_LANGUAGE" : "Язык:", From 91fb8e3c8852dc0b2bc2bd437c5d09c1077fc442 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 16:32:13 -0700 Subject: [PATCH 08/21] GUAC-1176: Only clear entered passwords - do not delete the fields entirely. --- guacamole/src/main/webapp/app/login/directives/login.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index b54ab24cd..a4b55fe63 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -166,8 +166,8 @@ angular.module('login').directive('guacLogin', [function guacLogin() { angular.forEach($scope.remainingFields, function clearEnteredValueIfPassword(field) { // Remove entered value only if field is a password field - if (field.type === Field.Type.PASSWORD) - delete $scope.enteredValues[field.name]; + if (field.type === Field.Type.PASSWORD && field.name in $scope.enteredValues) + $scope.enteredValues[field.name] = ''; }); } From 4c0cf58d9f34de3406555ba17901aea913f911a5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 16:34:52 -0700 Subject: [PATCH 09/21] GUAC-1176: Add Russian translation strings to JDBC authentication. --- .../src/main/resources/guac-manifest.json | 3 ++- .../src/main/resources/guac-manifest.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json index 6566bcc4b..93d2a5bfe 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json @@ -10,7 +10,8 @@ ], "translations" : [ - "translations/en_US.json" + "translations/en_US.json", + "translations/ru_RU.json" ] } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json index a3ae33e4e..e838ac7d9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json @@ -10,7 +10,8 @@ ], "translations" : [ - "translations/en_US.json" + "translations/en_US.json", + "translations/ru_RU.json" ] } From a3d32032113e396145ce277aa808c8020d14644c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 3 Jun 2015 16:52:20 -0700 Subject: [PATCH 10/21] GUAC-1176: Add password confirmation to reset procedure. --- .../auth/jdbc/user/UserContextService.java | 30 +++++++++++++++---- .../src/main/resources/translations/en.json | 10 +++++-- .../main/resources/translations/ru_RU.json | 8 ++++- 3 files changed, 39 insertions(+), 9 deletions(-) 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 131cd19ce..9aae125b6 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 @@ -62,17 +62,29 @@ public class UserContextService { private Provider userContextProvider; /** - * The name of the HTTP parameter to expect if the user is changing their - * expired password upon login. + * The name of the HTTP password parameter to expect if the user is + * changing their expired password upon login. */ private static final String NEW_PASSWORD_PARAMETER = "new-password"; /** - * The field to provide the user when their password is expired and must - * be changed. + * The password field to provide the user when their password is expired + * and must be changed. */ private static final Field NEW_PASSWORD = new Field(NEW_PASSWORD_PARAMETER, "New password", Field.Type.PASSWORD); + /** + * The name of the HTTP password confirmation parameter to expect if the + * user is changing their expired password upon login. + */ + private static final String CONFIRM_NEW_PASSWORD_PARAMETER = "confirm-new-password"; + + /** + * The password confirmation field to provide the user when their password + * is expired and must be changed. + */ + private static final Field CONFIRM_NEW_PASSWORD = new Field(CONFIRM_NEW_PASSWORD_PARAMETER, "Confirm new password", Field.Type.PASSWORD); + /** * Information describing the expected credentials if a user's password is * expired. If a user's password is expired, it must be changed during the @@ -81,7 +93,8 @@ public class UserContextService { private static final CredentialsInfo EXPIRED_PASSWORD = new CredentialsInfo(Arrays.asList( CredentialsInfo.USERNAME, CredentialsInfo.PASSWORD, - NEW_PASSWORD + NEW_PASSWORD, + CONFIRM_NEW_PASSWORD )); /** @@ -115,9 +128,10 @@ public class UserContextService { // Pull new password from HTTP request HttpServletRequest request = credentials.getRequest(); String newPassword = request.getParameter(NEW_PASSWORD_PARAMETER); + String confirmNewPassword = request.getParameter(CONFIRM_NEW_PASSWORD_PARAMETER); // Require new password if account is expired - if (newPassword == null) { + if (newPassword == null || confirmNewPassword == null) { logger.info("The password of user \"{}\" has expired and must be reset.", user.getIdentifier()); throw new GuacamoleInsufficientCredentialsException("Password expired", EXPIRED_PASSWORD); } @@ -130,6 +144,10 @@ public class UserContextService { if (newPassword.isEmpty()) throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_BLANK"); + // Confirm that the password was entered correctly twice + if (!newPassword.equals(confirmNewPassword)) + throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_MISMATCH"); + // STUB: Change password if new password given logger.info("Resetting expired password of user \"{}\".", user.getIdentifier()); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json index 2a4098d9a..6bf4fb97b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json @@ -1,8 +1,14 @@ { "LOGIN" : { - "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", - "ERROR_PASSWORD_SAME" : "The new password must be different from the expired password." + + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", + "ERROR_PASSWORD_SAME" : "The new password must be different from the expired password.", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "FIELD_HEADER_NEW_PASSWORD" : "New password", + "FIELD_HEADER_CONFIRM_NEW_PASSWORD" : "Confirm new password" + }, "USER_ATTRIBUTES" : { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json index 30a0c2ad6..4811b495e 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json @@ -1,7 +1,13 @@ { "LOGIN" : { - "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK" + + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "FIELD_HEADER_NEW_PASSWORD" : "Новый пароль", + "FIELD_HEADER_CONFIRM_NEW_PASSWORD" : "Подтверждение пароля" + } } From 02ffe81f2c70cbf2a0252cf89f8451303a19ce49 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Jun 2015 12:00:03 -0700 Subject: [PATCH 11/21] GUAC-1176: Bring up-to-date with changes from GUAC-1217. --- .../src/main/resources/translations/fr.json | 13 +++++++++++++ .../resources/translations/{ru_RU.json => ru.json} | 0 .../src/main/resources/guac-manifest.json | 5 +++-- .../src/main/resources/guac-manifest.json | 5 +++-- 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/fr.json rename extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/{ru_RU.json => ru.json} (100%) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/fr.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/fr.json new file mode 100644 index 000000000..5369d51b2 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/fr.json @@ -0,0 +1,13 @@ +{ + + "LOGIN" : { + + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "FIELD_HEADER_NEW_PASSWORD" : "Mot de passe", + "FIELD_HEADER_CONFIRM_NEW_PASSWORD" : "Répéter mot de passe" + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru.json similarity index 100% rename from extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru_RU.json rename to extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/ru.json diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json index 93d2a5bfe..0ca039a5e 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json @@ -10,8 +10,9 @@ ], "translations" : [ - "translations/en_US.json", - "translations/ru_RU.json" + "translations/en.json", + "translations/fr.json", + "translations/ru.json" ] } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json index e838ac7d9..18f97e207 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json @@ -10,8 +10,9 @@ ], "translations" : [ - "translations/en_US.json", - "translations/ru_RU.json" + "translations/en.json", + "translations/fr.json", + "translations/ru.json" ] } From 7826039d1fcb51c120d6842348daebb0fc073d7c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Jun 2015 22:15:02 -0700 Subject: [PATCH 12/21] GUAC-1176: Set CSS "continuation" class on login interface if login is continuing. --- .../main/webapp/app/login/directives/login.js | 18 +++++++++++++++++- .../main/webapp/app/login/templates/login.html | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index a4b55fe63..c818671a8 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -86,7 +86,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() { /** * Whether an error occurred during login. - * + * * @type Boolean */ $scope.loginError = false; @@ -105,6 +105,22 @@ angular.module('login').directive('guacLogin', [function guacLogin() { */ $scope.remainingFields = []; + /** + * Returns whether a previous login attempt is continuing. + * + * @return {Boolean} + * true if a previous login attempt is continuing, false otherwise. + */ + $scope.isContinuation = function isContinuation() { + + // The login is continuing if any parameter values are provided + for (var name in $scope.values) + return true; + + return false; + + }; + $scope.$watch('values', function resetEnteredValues(values) { angular.extend($scope.enteredValues, values || {}); }); diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index bcb8ed48e..d00f4a174 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -1,4 +1,4 @@ -