mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 05:31:22 +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;
|
package org.apache.guacamole.auth.jdbc.security;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
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;
|
||||||
@@ -49,6 +51,12 @@ public class PasswordPolicyService {
|
|||||||
@Inject
|
@Inject
|
||||||
private PasswordRecordMapper passwordRecordMapper;
|
private PasswordRecordMapper passwordRecordMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for hashing passwords.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private PasswordEncryptionService encryptionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression which matches only if the string contains at least one
|
* Regular expression which matches only if the string contains at least one
|
||||||
* lowercase character.
|
* 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
|
* Verifies that the given new password complies with the password policy
|
||||||
* configured within guacamole.properties, throwing a GuacamoleException if
|
* configured within guacamole.properties, throwing a GuacamoleException if
|
||||||
@@ -152,6 +203,12 @@ public class PasswordPolicyService {
|
|||||||
throw new PasswordRequiresSymbolException(
|
throw new PasswordRequiresSymbolException(
|
||||||
"Passwords must contain at least one non-alphanumeric character.");
|
"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
|
// 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_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_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_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."
|
"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