mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 05:31:22 +00:00
GUACAMOLE-36: Implement password aging checks.
This commit is contained in:
@@ -22,6 +22,7 @@ package org.apache.guacamole.auth.jdbc;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
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.sharing.user.SharedAuthenticatedUser;
|
||||||
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
|
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
|
||||||
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
|
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
|
||||||
@@ -55,6 +56,12 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider
|
|||||||
@Inject
|
@Inject
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for enforcing password complexity policies.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private PasswordPolicyService passwordPolicyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider for retrieving UserContext instances.
|
* Provider for retrieving UserContext instances.
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +108,7 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider
|
|||||||
|
|
||||||
// Update password if password is expired
|
// Update password if password is expired
|
||||||
UserModel userModel = user.getModel();
|
UserModel userModel = user.getModel();
|
||||||
if (userModel.isExpired())
|
if (userModel.isExpired() || passwordPolicyService.isPasswordExpired(userModel))
|
||||||
userService.resetExpiredPassword(user, authenticatedUser.getCredentials());
|
userService.resetExpiredPassword(user, authenticatedUser.getCredentials());
|
||||||
|
|
||||||
// Link to user context
|
// Link to user context
|
||||||
|
@@ -42,6 +42,36 @@ public interface PasswordPolicy {
|
|||||||
*/
|
*/
|
||||||
int getMinimumLength() throws GuacamoleException;
|
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
|
* Returns whether both uppercase and lowercase characters must be present
|
||||||
* in new passwords. If true, passwords which do not have at least one
|
* in new passwords. If true, passwords which do not have at least one
|
||||||
|
@@ -20,10 +20,12 @@
|
|||||||
package org.apache.guacamole.auth.jdbc.security;
|
package org.apache.guacamole.auth.jdbc.security;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
|
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
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.user;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
import java.sql.Timestamp;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -210,6 +211,9 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
|||||||
if (object.getPassword() != null)
|
if (object.getPassword() != null)
|
||||||
passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword());
|
passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword());
|
||||||
|
|
||||||
|
// Update password reset date
|
||||||
|
model.setPasswordDate(new Timestamp(System.currentTimeMillis()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -233,9 +237,20 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify new password does not violate defined policies (if specified)
|
// Verify new password does not violate defined policies (if specified)
|
||||||
if (object.getPassword() != null)
|
if (object.getPassword() != null) {
|
||||||
|
|
||||||
|
// Enforce password age only for non-adminstrators
|
||||||
|
if (!user.getUser().isAdministrator())
|
||||||
|
passwordPolicyService.verifyPasswordAge(model);
|
||||||
|
|
||||||
|
// Always verify password complexity
|
||||||
passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword());
|
passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword());
|
||||||
|
|
||||||
|
// Update password reset date
|
||||||
|
model.setPasswordDate(new Timestamp(System.currentTimeMillis()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -60,7 +60,8 @@
|
|||||||
"ERROR_REQUIRES_DIGIT" : "Passwords must contain at least one digit.",
|
"ERROR_REQUIRES_DIGIT" : "Passwords must contain at least one digit.",
|
||||||
"ERROR_REQUIRES_MULTIPLE_CASE" : "Passwords must contain both uppercase and lowercase characters.",
|
"ERROR_REQUIRES_MULTIPLE_CASE" : "Passwords must contain both uppercase and lowercase characters.",
|
||||||
"ERROR_REQUIRES_NON_ALNUM" : "Passwords must contain at least one symbol.",
|
"ERROR_REQUIRES_NON_ALNUM" : "Passwords must contain at least one symbol.",
|
||||||
"ERROR_TOO_SHORT" : "Passwords must be at least {LENGTH} {LENGTH, plural, one{character} other{characters}} long."
|
"ERROR_TOO_SHORT" : "Passwords must be at least {LENGTH} {LENGTH, plural, one{character} other{characters}} long.",
|
||||||
|
"ERROR_TOO_YOUNG" : "The password for this account has already been reset. Please wait at least {WAIT} more {WAIT, plural, one{day} other{days}} before changing the password again."
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -45,6 +45,32 @@ public class MySQLPasswordPolicy implements PasswordPolicy {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property which specifies the minimum number of days which must
|
||||||
|
* elapse before a user may reset their password. If set to zero, the
|
||||||
|
* default, then this restriction does not apply.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty MIN_AGE =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "mysql-user-password-min-age"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property which specifies the maximum number of days which may
|
||||||
|
* elapse before a user is required to reset their password. If set to zero,
|
||||||
|
* the default, then this restriction does not apply.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty MAX_AGE =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "mysql-user-password-max-age"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The property which specifies whether all user passwords must have at
|
* The property which specifies whether all user passwords must have at
|
||||||
* least one lowercase character and one uppercase character. By default,
|
* least one lowercase character and one uppercase character. By default,
|
||||||
@@ -119,6 +145,16 @@ public class MySQLPasswordPolicy implements PasswordPolicy {
|
|||||||
return environment.getProperty(MIN_LENGTH, 0);
|
return environment.getProperty(MIN_LENGTH, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinimumAge() throws GuacamoleException {
|
||||||
|
return environment.getProperty(MIN_AGE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximumAge() throws GuacamoleException {
|
||||||
|
return environment.getProperty(MAX_AGE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMultipleCaseRequired() throws GuacamoleException {
|
public boolean isMultipleCaseRequired() throws GuacamoleException {
|
||||||
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);
|
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);
|
||||||
|
@@ -45,6 +45,32 @@ public class PostgreSQLPasswordPolicy implements PasswordPolicy {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property which specifies the minimum number of days which must
|
||||||
|
* elapse before a user may reset their password. If set to zero, the
|
||||||
|
* default, then this restriction does not apply.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty MIN_AGE =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "postgresql-user-password-min-age"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property which specifies the maximum number of days which may
|
||||||
|
* elapse before a user is required to reset their password. If set to zero,
|
||||||
|
* the default, then this restriction does not apply.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty MAX_AGE =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "postgresql-user-password-max-age"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The property which specifies whether all user passwords must have at
|
* The property which specifies whether all user passwords must have at
|
||||||
* least one lowercase character and one uppercase character. By default,
|
* least one lowercase character and one uppercase character. By default,
|
||||||
@@ -119,6 +145,16 @@ public class PostgreSQLPasswordPolicy implements PasswordPolicy {
|
|||||||
return environment.getProperty(MIN_LENGTH, 0);
|
return environment.getProperty(MIN_LENGTH, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinimumAge() throws GuacamoleException {
|
||||||
|
return environment.getProperty(MIN_AGE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximumAge() throws GuacamoleException {
|
||||||
|
return environment.getProperty(MAX_AGE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMultipleCaseRequired() throws GuacamoleException {
|
public boolean isMultipleCaseRequired() throws GuacamoleException {
|
||||||
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);
|
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);
|
||||||
|
Reference in New Issue
Block a user