From a207411241a6a4bbdab80f4f8aba4da6b89be845 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Aug 2016 12:01:58 -0700 Subject: [PATCH 1/4] GUACAMOLE-36: Add password reset date to schema. --- .../guacamole/auth/jdbc/user/UserModel.java | 30 +++++++++++++++++++ .../schema/001-create-schema.sql | 1 + .../schema/upgrade/upgrade-pre-0.9.11.sql | 25 ++++++++++++++++ .../guacamole/auth/jdbc/user/UserMapper.xml | 7 +++++ .../schema/001-create-schema.sql | 1 + .../schema/upgrade/upgrade-pre-0.9.11.sql | 25 ++++++++++++++++ .../guacamole/auth/jdbc/user/UserMapper.xml | 7 +++++ 7 files changed, 96 insertions(+) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.11.sql create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.11.sql diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserModel.java index 09de5e8a6..5e81b3d72 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserModel.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.user; import java.sql.Date; import java.sql.Time; +import java.sql.Timestamp; import org.apache.guacamole.auth.jdbc.base.ObjectModel; /** @@ -41,6 +42,11 @@ public class UserModel extends ObjectModel { */ private byte[] passwordSalt; + /** + * The time this user's password was last reset. + */ + private Timestamp passwordDate; + /** * Whether the user account is disabled. Disabled accounts exist and can * be modified, but cannot be used. @@ -143,6 +149,30 @@ public class UserModel extends ObjectModel { this.passwordSalt = passwordSalt; } + /** + * Returns the date that this user's password was last set/reset. This + * value is required to be manually updated whenever the user's password is + * changed; it will not be automatically updated by the database. + * + * @return + * The date that this user's password was last set/reset. + */ + public Timestamp getPasswordDate() { + return passwordDate; + } + + /** + * Sets the date that this user's password was last set/reset. This + * value is required to be manually updated whenever the user's password is + * changed; it will not be automatically updated by the database. + * + * @param passwordDate + * The date that this user's password was last set/reset. + */ + public void setPasswordDate(Timestamp passwordDate) { + this.passwordDate = passwordDate; + } + /** * Returns whether the user has been disabled. Disabled users are not * allowed to login. Although their account data exists, all login attempts 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 e1b19b078..cb5604785 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 @@ -85,6 +85,7 @@ CREATE TABLE `guacamole_user` ( `username` varchar(128) NOT NULL, `password_hash` binary(32) NOT NULL, `password_salt` binary(32), + `password_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Account disabled/expired status `disabled` boolean NOT NULL DEFAULT 0, diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.11.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.11.sql new file mode 100644 index 000000000..3acc2a40b --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.11.sql @@ -0,0 +1,25 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +-- +-- Add per-user password set date +-- + +ALTER TABLE guacamole_user + ADD COLUMN password_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml index 65dc97c0b..3530b0b51 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -29,6 +29,7 @@ + @@ -61,6 +62,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -85,6 +87,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -112,6 +115,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -139,6 +143,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -151,6 +156,7 @@ #{object.identifier,jdbcType=VARCHAR}, #{object.passwordHash,jdbcType=BINARY}, #{object.passwordSalt,jdbcType=BINARY}, + #{object.passwordDate,jdbcType=TIMESTAMP}, #{object.disabled,jdbcType=BOOLEAN}, #{object.expired,jdbcType=BOOLEAN}, #{object.accessWindowStart,jdbcType=TIME}, @@ -167,6 +173,7 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, + password_date = #{object.passwordDate,jdbcType=TIMESTAMP}, disabled = #{object.disabled,jdbcType=BOOLEAN}, expired = #{object.expired,jdbcType=BOOLEAN}, access_window_start = #{object.accessWindowStart,jdbcType=TIME}, 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 4f780c126..e308dce11 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 @@ -126,6 +126,7 @@ CREATE TABLE guacamole_user ( username varchar(128) NOT NULL, password_hash bytea NOT NULL, password_salt bytea, + password_date timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Account disabled/expired status disabled boolean NOT NULL DEFAULT FALSE, diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.11.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.11.sql new file mode 100644 index 000000000..d2f430c60 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.11.sql @@ -0,0 +1,25 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +-- +-- Add per-user password set date +-- + +ALTER TABLE guacamole_user + ADD COLUMN password_date timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml index 2bff4b952..39ec05a01 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -29,6 +29,7 @@ + @@ -62,6 +63,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -86,6 +88,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -113,6 +116,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -140,6 +144,7 @@ username, password_hash, password_salt, + password_date, disabled, expired, access_window_start, @@ -152,6 +157,7 @@ #{object.identifier,jdbcType=VARCHAR}, #{object.passwordHash,jdbcType=BINARY}, #{object.passwordSalt,jdbcType=BINARY}, + #{object.passwordDate,jdbcType=TIMESTAMP}, #{object.disabled,jdbcType=BOOLEAN}, #{object.expired,jdbcType=BOOLEAN}, #{object.accessWindowStart,jdbcType=TIME}, @@ -168,6 +174,7 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, + password_date = #{object.passwordDate,jdbcType=TIMESTAMP}, disabled = #{object.disabled,jdbcType=BOOLEAN}, expired = #{object.expired,jdbcType=BOOLEAN}, access_window_start = #{object.accessWindowStart,jdbcType=TIME}, From 5f6fb8a1994206a85bd71b57b877fa24c630b7c3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Aug 2016 17:35:12 -0700 Subject: [PATCH 2/4] GUACAMOLE-36: Implement password aging checks. --- .../JDBCAuthenticationProviderService.java | 9 +- .../auth/jdbc/security/PasswordPolicy.java | 30 +++++++ .../jdbc/security/PasswordPolicyService.java | 83 +++++++++++++++++++ .../security/PasswordTooYoungException.java | 53 ++++++++++++ .../guacamole/auth/jdbc/user/UserService.java | 17 +++- .../src/main/resources/translations/en.json | 3 +- .../auth/mysql/MySQLPasswordPolicy.java | 36 ++++++++ .../postgresql/PostgreSQLPasswordPolicy.java | 36 ++++++++ 8 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordTooYoungException.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java index a0d422a39..983905588 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.jdbc; import com.google.inject.Inject; import com.google.inject.Provider; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService; import org.apache.guacamole.auth.jdbc.sharing.user.SharedAuthenticatedUser; import org.apache.guacamole.auth.jdbc.user.ModeledUser; import org.apache.guacamole.auth.jdbc.user.ModeledUserContext; @@ -55,6 +56,12 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider @Inject private UserService userService; + /** + * Service for enforcing password complexity policies. + */ + @Inject + private PasswordPolicyService passwordPolicyService; + /** * Provider for retrieving UserContext instances. */ @@ -101,7 +108,7 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider // Update password if password is expired UserModel userModel = user.getModel(); - if (userModel.isExpired()) + if (userModel.isExpired() || passwordPolicyService.isPasswordExpired(userModel)) userService.resetExpiredPassword(user, authenticatedUser.getCredentials()); // Link to user context diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java index 8526bf633..eb02ba314 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java @@ -42,6 +42,36 @@ public interface PasswordPolicy { */ int getMinimumLength() throws GuacamoleException; + /** + * Returns the minimum number of days which must elapse before the user's + * password may be reset. If this restriction does not apply, this will be + * zero. + * + * @return + * The minimum number of days which must elapse before the user's + * password must be reset, or zero if this restriction does not apply. + * + * @throws GuacamoleException + * If the minimum password age cannot be parsed from + * guacamole.properties. + */ + int getMinimumAge() throws GuacamoleException; + + /** + * Returns the maximum number of days which may elapse before the user's + * password must be reset. If this restriction does not apply, this will be + * zero. + * + * @return + * The maximum number of days which may elapse before the user's + * password must be reset, or zero if this restriction does not apply. + * + * @throws GuacamoleException + * If the maximum password age cannot be parsed from + * guacamole.properties. + */ + int getMaximumAge() throws GuacamoleException; + /** * Returns whether both uppercase and lowercase characters must be present * in new passwords. If true, passwords which do not have at least one diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java index 8d3c0b652..23fc367ba 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java @@ -20,10 +20,12 @@ package org.apache.guacamole.auth.jdbc.security; import com.google.inject.Inject; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.jdbc.JDBCEnvironment; +import org.apache.guacamole.auth.jdbc.user.UserModel; /** * Service which verifies compliance with the password policy configured via @@ -146,4 +148,85 @@ public class PasswordPolicyService { } + /** + * Returns the age of the given user's password, in days. The age of a + * user's password is the amount of time elapsed since the password was last + * changed or reset. + * + * @param user + * The user to calculate the password age of. + * + * @return + * The age of the given user's password, in days. + */ + private long getPasswordAge(UserModel user) { + + // Pull both current time and the time the password was last reset + long currentTime = System.currentTimeMillis(); + long lastResetTime = user.getPasswordDate().getTime(); + + // Calculate the number of days elapsed since the password was last reset + return TimeUnit.DAYS.convert(currentTime - lastResetTime, TimeUnit.MILLISECONDS); + + } + + /** + * Verifies that the given user can change their password without violating + * password aging policy. If changing the user's password would violate the + * aging policy, a GuacamoleException will be thrown. + * + * @param user + * The user whose password is changing. + * + * @throws GuacamoleException + * If the user's password cannot be changed due to the password aging + * policy, or of the password policy cannot be parsed from + * guacamole.properties. + */ + public void verifyPasswordAge(UserModel user) throws GuacamoleException { + + // Retrieve password policy from environment + PasswordPolicy policy = environment.getPasswordPolicy(); + + long minimumAge = policy.getMinimumAge(); + long passwordAge = getPasswordAge(user); + + // Require that sufficient time has elapsed before allowing the password + // to be changed + if (passwordAge < minimumAge) + throw new PasswordTooYoungException("Password was already recently changed.", + minimumAge - passwordAge); + + } + + /** + * Returns whether the given user's password is expired due to the password + * aging policy. + * + * @param user + * The user to check. + * + * @return + * true if the user needs to change their password to comply with the + * password aging policy, false otherwise. + * + * @throws GuacamoleException + * If the password policy cannot be parsed. + */ + public boolean isPasswordExpired(UserModel user) + throws GuacamoleException { + + // Retrieve password policy from environment + PasswordPolicy policy = environment.getPasswordPolicy(); + + // There is no maximum password age if 0 + int maxPasswordAge = policy.getMaximumAge(); + if (maxPasswordAge == 0) + return false; + + // Determine whether password is expired based on maximum age + return getPasswordAge(user) >= maxPasswordAge; + + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordTooYoungException.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordTooYoungException.java new file mode 100644 index 000000000..a5235dce5 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordTooYoungException.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.jdbc.security; + +import java.util.Collections; +import org.apache.guacamole.language.TranslatableMessage; + +/** + * Thrown when an attempt is made to set a user's password before sufficient + * time has elapsed since the password was last reset, in violation of the + * defined password policy. + * + * @author Michael Jumper + */ +public class PasswordTooYoungException extends PasswordPolicyException { + + /** + * Creates a new PasswordTooYoungException with the given human-readable + * message. The translatable message is already defined. + * + * @param message + * A human-readable message describing the password policy violation + * that occurred. + * + * @param wait + * The number of days the user should wait before attempting to reset + * the password again. + */ + public PasswordTooYoungException(String message, long wait) { + super(message, new TranslatableMessage( + "PASSWORD_POLICY.ERROR_TOO_YOUNG", + Collections.singletonMap("WAIT", wait) + )); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java index e131841cc..25dfa327f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.user; import com.google.inject.Inject; import com.google.inject.Provider; +import java.sql.Timestamp; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -210,6 +211,9 @@ public class UserService extends ModeledDirectoryObjectService Date: Mon, 22 Aug 2016 14:56:39 -0700 Subject: [PATCH 3/4] GUACAMOLE-36: Automatically update password date. Store previous date for comparison. --- .../JDBCAuthenticationProviderService.java | 2 +- .../jdbc/security/PasswordPolicyService.java | 10 +++--- .../guacamole/auth/jdbc/user/ModeledUser.java | 31 +++++++++++++++++++ .../guacamole/auth/jdbc/user/UserService.java | 9 +----- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java index 983905588..48fdf97cf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java @@ -108,7 +108,7 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider // Update password if password is expired UserModel userModel = user.getModel(); - if (userModel.isExpired() || passwordPolicyService.isPasswordExpired(userModel)) + if (userModel.isExpired() || passwordPolicyService.isPasswordExpired(user)) userService.resetExpiredPassword(user, authenticatedUser.getCredentials()); // Link to user context diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java index 23fc367ba..a47c03894 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java @@ -25,7 +25,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.jdbc.JDBCEnvironment; -import org.apache.guacamole.auth.jdbc.user.UserModel; +import org.apache.guacamole.auth.jdbc.user.ModeledUser; /** * Service which verifies compliance with the password policy configured via @@ -159,11 +159,11 @@ public class PasswordPolicyService { * @return * The age of the given user's password, in days. */ - private long getPasswordAge(UserModel user) { + private long getPasswordAge(ModeledUser user) { // Pull both current time and the time the password was last reset long currentTime = System.currentTimeMillis(); - long lastResetTime = user.getPasswordDate().getTime(); + long lastResetTime = user.getPreviousPasswordDate().getTime(); // Calculate the number of days elapsed since the password was last reset return TimeUnit.DAYS.convert(currentTime - lastResetTime, TimeUnit.MILLISECONDS); @@ -183,7 +183,7 @@ public class PasswordPolicyService { * policy, or of the password policy cannot be parsed from * guacamole.properties. */ - public void verifyPasswordAge(UserModel user) throws GuacamoleException { + public void verifyPasswordAge(ModeledUser user) throws GuacamoleException { // Retrieve password policy from environment PasswordPolicy policy = environment.getPasswordPolicy(); @@ -213,7 +213,7 @@ public class PasswordPolicyService { * @throws GuacamoleException * If the password policy cannot be parsed. */ - public boolean isPasswordExpired(UserModel user) + public boolean isPasswordExpired(ModeledUser user) throws GuacamoleException { // Retrieve password policy from environment diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java index 1353415e8..2f1e58382 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.jdbc.user; import com.google.inject.Inject; import java.sql.Date; import java.sql.Time; +import java.sql.Timestamp; import java.text.ParseException; import java.util.Arrays; import java.util.Calendar; @@ -186,6 +187,12 @@ public class ModeledUser extends ModeledDirectoryObject implements Us * user was retrieved from the database, this will be null. */ private String password = null; + + /** + * The time and date that this user's password was previously set (prior to + * being queried). If the user is new, this will be null. + */ + private Timestamp previousPasswordDate = null; /** * Creates a new, empty ModeledUser. @@ -193,6 +200,12 @@ public class ModeledUser extends ModeledDirectoryObject implements Us public ModeledUser() { } + @Override + public void setModel(UserModel model) { + super.setModel(model); + this.previousPasswordDate = model.getPasswordDate(); + } + @Override public String getPassword() { return password; @@ -222,6 +235,24 @@ public class ModeledUser extends ModeledDirectoryObject implements Us userModel.setPasswordHash(hash); } + userModel.setPasswordDate(new Timestamp(System.currentTimeMillis())); + + } + + /** + * Returns the time and date that this user's password was previously set. + * If the user is new, this will be null. Unlike getPasswordDate() of + * UserModel (which is updated automatically along with the password salt + * and hash whenever setPassword() is invoked), this value is unaffected by + * calls to setPassword(), and will always be the value stored in the + * database at the time this user was queried. + * + * @return + * The time and date that this user's password was previously set, or + * null if the user is new. + */ + public Timestamp getPreviousPasswordDate() { + return previousPasswordDate; } /** diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java index 25dfa327f..5bfd665ad 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java @@ -21,7 +21,6 @@ package org.apache.guacamole.auth.jdbc.user; import com.google.inject.Inject; import com.google.inject.Provider; -import java.sql.Timestamp; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -211,9 +210,6 @@ public class UserService extends ModeledDirectoryObjectService Date: Thu, 5 Jan 2017 10:07:56 -0800 Subject: [PATCH 4/4] GUACAMOLE-36: Correct description of minimum password age. --- .../org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java index eb02ba314..8413b6a8d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicy.java @@ -49,7 +49,7 @@ public interface PasswordPolicy { * * @return * The minimum number of days which must elapse before the user's - * password must be reset, or zero if this restriction does not apply. + * password may be reset, or zero if this restriction does not apply. * * @throws GuacamoleException * If the minimum password age cannot be parsed from