mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-36: Verify new passwords against history.
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
package org.apache.guacamole.auth.jdbc.security;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -49,6 +51,12 @@ public class PasswordPolicyService {
|
||||
@Inject
|
||||
private PasswordRecordMapper passwordRecordMapper;
|
||||
|
||||
/**
|
||||
* Service for hashing passwords.
|
||||
*/
|
||||
@Inject
|
||||
private PasswordEncryptionService encryptionService;
|
||||
|
||||
/**
|
||||
* Regular expression which matches only if the string contains at least one
|
||||
* lowercase character.
|
||||
@@ -105,6 +113,49 @@ public class PasswordPolicyService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given password matches any of the user's previous
|
||||
* passwords. Regardless of the value specified here, the maximum number of
|
||||
* passwords involved in this check depends on how many previous passwords
|
||||
* were actually recorded, which depends on the password policy.
|
||||
*
|
||||
* @param password
|
||||
* The password to check.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user whose history should be compared against
|
||||
* the given password.
|
||||
*
|
||||
* @param historySize
|
||||
* The maximum number of history records to compare the password
|
||||
* against.
|
||||
*
|
||||
* @return
|
||||
* true if the given password matches any of the user's previous
|
||||
* passwords, up to the specified limit, false otherwise.
|
||||
*/
|
||||
private boolean matchesPreviousPasswords(String password, String username,
|
||||
int historySize) {
|
||||
|
||||
// No need to compare if no history is relevant
|
||||
if (historySize <= 0)
|
||||
return false;
|
||||
|
||||
// Check password against all recorded hashes
|
||||
List<PasswordRecordModel> history = passwordRecordMapper.select(username, historySize);
|
||||
for (PasswordRecordModel record : history) {
|
||||
|
||||
byte[] hash = encryptionService.createPasswordHash(password, record.getPasswordSalt());
|
||||
if (Arrays.equals(hash, record.getPasswordHash()))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// No passwords match
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the given new password complies with the password policy
|
||||
* configured within guacamole.properties, throwing a GuacamoleException if
|
||||
@@ -152,6 +203,12 @@ public class PasswordPolicyService {
|
||||
throw new PasswordRequiresSymbolException(
|
||||
"Passwords must contain at least one non-alphanumeric character.");
|
||||
|
||||
// Prohibit password reuse
|
||||
int historySize = policy.getHistorySize();
|
||||
if (matchesPreviousPasswords(password, username, historySize))
|
||||
throw new PasswordReusedException(
|
||||
"Password matches a previously-used password.", historySize);
|
||||
|
||||
// Password passes all defined restrictions
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 reuse a previous password, in violation of
|
||||
* the defined password policy.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class PasswordReusedException extends PasswordPolicyException {
|
||||
|
||||
/**
|
||||
* Creates a new PasswordReusedException 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 historySize
|
||||
* The number of previous passwords which are remembered for each user,
|
||||
* and must not be reused.
|
||||
*/
|
||||
public PasswordReusedException(String message, int historySize) {
|
||||
super(message, new TranslatableMessage(
|
||||
"PASSWORD_POLICY.ERROR_REUSED",
|
||||
Collections.singletonMap("HISTORY_SIZE", historySize)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@@ -60,6 +60,7 @@
|
||||
"ERROR_REQUIRES_DIGIT" : "Passwords must contain at least one digit.",
|
||||
"ERROR_REQUIRES_MULTIPLE_CASE" : "Passwords must contain both uppercase and lowercase characters.",
|
||||
"ERROR_REQUIRES_NON_ALNUM" : "Passwords must contain at least one symbol.",
|
||||
"ERROR_REUSED" : "This password has already been used. Please do not reuse any of the previous {HISTORY_SIZE} {HISTORY_SIZE, plural, one{password} other{passwords}}.",
|
||||
"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."
|
||||
|
||||
|
Reference in New Issue
Block a user