GUACAMOLE-36: Record and maintain password history.

This commit is contained in:
Michael Jumper
2016-08-22 22:06:44 -07:00
parent ae695ef17b
commit a943077d40
7 changed files with 111 additions and 2 deletions

View File

@@ -72,6 +72,20 @@ public interface PasswordPolicy {
*/
int getMaximumAge() throws GuacamoleException;
/**
* Returns the number of previous passwords remembered for each user. If
* greater than zero, users will be prohibited from reusing their past
* passwords.
*
* @return
* The number of previous passwords remembered for each user.
*
* @throws GuacamoleException
* If the password history size cannot be parsed from
* guacamole.properties.
*/
int getHistorySize() 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

View File

@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.auth.jdbc.user.PasswordRecordMapper;
import org.apache.guacamole.auth.jdbc.user.PasswordRecordModel;
/**
@@ -42,6 +43,12 @@ public class PasswordPolicyService {
@Inject
private JDBCEnvironment environment;
/**
* Mapper for creating/retrieving previously-set passwords.
*/
@Inject
private PasswordRecordMapper passwordRecordMapper;
/**
* Regular expression which matches only if the string contains at least one
* lowercase character.
@@ -235,4 +242,32 @@ public class PasswordPolicyService {
}
/**
* Records the password that was associated with the given user at the time
* the user was queried, such that future attempts to set that same password
* for that user will be denied. The number of passwords remembered for each
* user is limited by the password policy.
*
* @param user
* The user whose previous password should be recorded.
*
* @throws GuacamoleException
* If the password policy cannot be parsed.
*/
public void recordPreviousPassword(ModeledUser user)
throws GuacamoleException {
// Retrieve password policy from environment
PasswordPolicy policy = environment.getPasswordPolicy();
// Nothing to do if history is not being recorded
int historySize = policy.getHistorySize();
if (historySize <= 0)
return;
// Store previous password in history
passwordRecordMapper.insert(user.getPreviousPassword(), historySize);
}
}

View File

@@ -242,6 +242,9 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
// Always verify password complexity
passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword());
// Store previous password in history
passwordPolicyService.recordPreviousPassword(object);
}
}

View File

@@ -71,6 +71,19 @@ public class MySQLPasswordPolicy implements PasswordPolicy {
};
/**
* The property which specifies the number of previous passwords remembered
* for each user. If set to zero, the default, then this restriction does
* not apply.
*/
private static final IntegerGuacamoleProperty HISTORY_SIZE =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "mysql-user-password-history-size"; }
};
/**
* The property which specifies whether all user passwords must have at
* least one lowercase character and one uppercase character. By default,
@@ -155,6 +168,11 @@ public class MySQLPasswordPolicy implements PasswordPolicy {
return environment.getProperty(MAX_AGE, 0);
}
@Override
public int getHistorySize() throws GuacamoleException {
return environment.getProperty(HISTORY_SIZE, 0);
}
@Override
public boolean isMultipleCaseRequired() throws GuacamoleException {
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);

View File

@@ -63,7 +63,19 @@
#{record.passwordHash,jdbcType=BINARY},
#{record.passwordSalt,jdbcType=BINARY},
#{record.passwordDate,jdbcType=TIMESTAMP}
)
);
DELETE FROM guacamole_user_password_history
WHERE password_history_id <= (
SELECT password_history_id
FROM (
SELECT password_history_id
FROM guacamole_user_password_history
WHERE user_id = #{record.userID,jdbcType=INTEGER}
ORDER BY password_date DESC
LIMIT 1 OFFSET #{maxHistorySize}
) old_password_record
);
</insert>

View File

@@ -71,6 +71,19 @@ public class PostgreSQLPasswordPolicy implements PasswordPolicy {
};
/**
* The property which specifies the number of previous passwords remembered
* for each user. If set to zero, the default, then this restriction does
* not apply.
*/
private static final IntegerGuacamoleProperty HISTORY_SIZE =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "postgresql-user-password-history-size"; }
};
/**
* The property which specifies whether all user passwords must have at
* least one lowercase character and one uppercase character. By default,
@@ -155,6 +168,11 @@ public class PostgreSQLPasswordPolicy implements PasswordPolicy {
return environment.getProperty(MAX_AGE, 0);
}
@Override
public int getHistorySize() throws GuacamoleException {
return environment.getProperty(HISTORY_SIZE, 0);
}
@Override
public boolean isMultipleCaseRequired() throws GuacamoleException {
return environment.getProperty(REQUIRE_MULTIPLE_CASE, false);

View File

@@ -63,7 +63,16 @@
#{record.passwordHash,jdbcType=BINARY},
#{record.passwordSalt,jdbcType=BINARY},
#{record.passwordDate,jdbcType=TIMESTAMP}
)
);
DELETE FROM guacamole_user_password_history
WHERE password_history_id IN (
SELECT password_history_id
FROM guacamole_user_password_history
WHERE user_id = #{record.userID,jdbcType=INTEGER}
ORDER BY password_date DESC
OFFSET #{maxHistorySize}
);
</insert>