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