[KEYCLOAK-405] - Feature that doesn't allow old password to be reused
This commit is contained in:
parent
375d061f58
commit
e3bb61248a
19 changed files with 551 additions and 97 deletions
|
@ -32,6 +32,9 @@
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
</createTable>
|
</createTable>
|
||||||
|
<addColumn tableName="CREDENTIAL">
|
||||||
|
<column name="CREATED_DATE" type="BIGINT"/>
|
||||||
|
</addColumn>
|
||||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_IDPM" tableName="IDENTITY_PROVIDER_MAPPER"/>
|
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_IDPM" tableName="IDENTITY_PROVIDER_MAPPER"/>
|
||||||
<addPrimaryKey columnNames="IDP_MAPPER_ID, NAME" constraintName="CONSTRAINT_IDPMConfig" tableName="IDP_MAPPER_CONFIG"/>
|
<addPrimaryKey columnNames="IDP_MAPPER_ID, NAME" constraintName="CONSTRAINT_IDPMConfig" tableName="IDP_MAPPER_CONFIG"/>
|
||||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER_MAPPER" constraintName="FK_IDPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER_MAPPER" constraintName="FK_IDPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||||
|
|
|
@ -128,7 +128,8 @@
|
||||||
<para>
|
<para>
|
||||||
In the admin console, per realm, you can set up a password policy to enforce that users pick hard to guess passwords.
|
In the admin console, per realm, you can set up a password policy to enforce that users pick hard to guess passwords.
|
||||||
A password has to match all policies. The password policies that can be configured are hash iterations, length, digits,
|
A password has to match all policies. The password policies that can be configured are hash iterations, length, digits,
|
||||||
lowercase, uppercase, special characters, not username and regex patterns. Multiple regex patterns, separated by comma,
|
lowercase, uppercase, special characters, not username, regex patterns and expired passwords. Expired Passwords policy
|
||||||
|
restricts a user from resetting his password to N old expired passwords. Multiple regex patterns, separated by comma,
|
||||||
can be specified. If there's more than one regex added, password has to match all fully.
|
can be specified. If there's more than one regex added, password has to match all fully.
|
||||||
Increasing number of Hash Iterations (n) does not worsen anything (and certainly not the cipher) and it greatly increases the
|
Increasing number of Hash Iterations (n) does not worsen anything (and certainly not the cipher) and it greatly increases the
|
||||||
resistance to dictionary attacks. However the drawback to increasing n is that it has some cost (CPU usage, energy, delay) for
|
resistance to dictionary attacks. However the drawback to increasing n is that it has some cost (CPU usage, energy, delay) for
|
||||||
|
|
|
@ -93,8 +93,10 @@ invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort: minimum l\u00E4nge {0}
|
||||||
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
|
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
|
||||||
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
|
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
|
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
|
||||||
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
|
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
|
||||||
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
|
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort: darf nicht gleich sein wie Benutzername.
|
||||||
|
invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort: nicht Regex-Muster (n) entsprechen.
|
||||||
|
invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort: muss nicht gleich einem der letzten {0} Kennw\u00F6rter sein.
|
||||||
|
|
||||||
locale_de=Deutsch
|
locale_de=Deutsch
|
||||||
locale_en=Englisch
|
locale_en=Englisch
|
||||||
|
|
|
@ -93,7 +93,9 @@ invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least
|
||||||
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
|
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
||||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
||||||
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username.
|
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||||
|
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||||
|
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||||
|
|
||||||
locale_de=German
|
locale_de=German
|
||||||
locale_en=English
|
locale_en=English
|
||||||
|
|
|
@ -46,12 +46,14 @@ accountDisabledMessage=Conta desativada, contate administrador
|
||||||
doLogOutAllSessions=Sair de todas sess\u00F5es
|
doLogOutAllSessions=Sair de todas sess\u00F5es
|
||||||
|
|
||||||
accountTemporarilyDisabledMessage=A conta est\u00E1 temporariamente indispon\u00EDvel, contate administrador ou tente novamente mais tarde
|
accountTemporarilyDisabledMessage=A conta est\u00E1 temporariamente indispon\u00EDvel, contate administrador ou tente novamente mais tarde
|
||||||
invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
|
invalidPasswordMinLengthMessage=Senha inv\u00E1lida\: comprimento m\u00EDnimo {0}
|
||||||
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres min\u00FAsculos
|
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres min\u00FAsculos
|
||||||
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
|
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres mai\u00FAsculos
|
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres mai\u00FAsculos
|
||||||
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres especiais
|
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
|
||||||
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
|
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
|
||||||
|
invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
|
||||||
|
invalidPasswordHistoryMessage=Senha inv\u00E1lida\: não deve ser igual a qualquer um dos últimos {0} senhas.
|
||||||
|
|
||||||
locale_de=Deutsch
|
locale_de=Deutsch
|
||||||
locale_en=English
|
locale_en=English
|
||||||
|
|
|
@ -900,14 +900,15 @@ module.factory('PasswordPolicy', function() {
|
||||||
var p = {};
|
var p = {};
|
||||||
|
|
||||||
p.policyMessages = {
|
p.policyMessages = {
|
||||||
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
|
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
|
||||||
length: "Minimal password length (integer type). Default value is 8.",
|
length: "Minimal password length (integer type). Default value is 8.",
|
||||||
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
||||||
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
|
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
|
||||||
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
|
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
|
||||||
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
|
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
|
||||||
notUsername: "Block passwords that are equal to the username",
|
notUsername: "Block passwords that are equal to the username",
|
||||||
regexPatterns: "Block passwords that do not match all of the regex patterns (string type)."
|
regexPatterns: "Block passwords that do not match all of the regex patterns (string type).",
|
||||||
|
passwordHistory: "Block passwords that are equal to previous passwords. Default value is 3."
|
||||||
}
|
}
|
||||||
|
|
||||||
p.allPolicies = [
|
p.allPolicies = [
|
||||||
|
@ -918,7 +919,8 @@ module.factory('PasswordPolicy', function() {
|
||||||
{ name: 'upperCase', value: 1 },
|
{ name: 'upperCase', value: 1 },
|
||||||
{ name: 'specialChars', value: 1 },
|
{ name: 'specialChars', value: 1 },
|
||||||
{ name: 'notUsername', value: 1 },
|
{ name: 'notUsername', value: 1 },
|
||||||
{ name: 'regexPatterns', value: ''}
|
{ name: 'regexPatterns', value: ''},
|
||||||
|
{ name: 'passwordHistory', value: 3 }
|
||||||
];
|
];
|
||||||
|
|
||||||
p.parse = function(policyString) {
|
p.parse = function(policyString) {
|
||||||
|
|
|
@ -125,12 +125,14 @@ accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
|
||||||
|
|
||||||
noAccessMessage=Kein Zugriff
|
noAccessMessage=Kein Zugriff
|
||||||
|
|
||||||
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort: minimum l\u00E4nge {0}.
|
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort\: minimum l\u00E4nge {0}.
|
||||||
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
|
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Zahl(en) beinhalten.
|
||||||
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
|
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Kleinbuchstaben beinhalten.
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
|
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Grossbuchstaben beinhalten.
|
||||||
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
|
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
|
||||||
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
|
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
|
||||||
|
invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster (n) entsprechen.
|
||||||
|
invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort\: muss nicht gleich einem der letzten {0} Kennw\u00F6rter sein.
|
||||||
|
|
||||||
failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
|
failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
|
||||||
httpsRequiredMessage=HTTPS erforderlich.
|
httpsRequiredMessage=HTTPS erforderlich.
|
||||||
|
|
|
@ -127,7 +127,9 @@ invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} nume
|
||||||
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
|
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
||||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
||||||
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username.
|
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||||
|
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||||
|
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||||
|
|
||||||
failedToProcessResponseMessage=Failed to process response
|
failedToProcessResponseMessage=Failed to process response
|
||||||
httpsRequiredMessage=HTTPS required
|
httpsRequiredMessage=HTTPS required
|
||||||
|
|
|
@ -120,11 +120,13 @@ accountPasswordUpdatedMessage=Sua senha foi atualizada
|
||||||
noAccessMessage=Sem acesso
|
noAccessMessage=Sem acesso
|
||||||
|
|
||||||
invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
|
invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
|
||||||
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
|
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
|
||||||
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres min\u00FAsculos
|
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres min\u00FAsculos
|
||||||
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres mai\u00FAsculos
|
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres mai\u00FAsculos
|
||||||
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres especiais
|
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
|
||||||
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida: n\u00E3o deve ser igual ao nome de usu\u00E1rio
|
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
|
||||||
|
invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
|
||||||
|
invalidPasswordHistoryMessage=Senha inv\u00E1lida\: não deve ser igual a qualquer um dos últimos {0} senhas.
|
||||||
|
|
||||||
failedToProcessResponseMessage=Falha ao processar a resposta
|
failedToProcessResponseMessage=Falha ao processar a resposta
|
||||||
httpsRequiredMessage=HTTPS requerido
|
httpsRequiredMessage=HTTPS requerido
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -18,6 +22,7 @@ public class PasswordPolicy {
|
||||||
public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
|
public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
|
||||||
public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
|
public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
|
||||||
public static final String INVALID_PASSWORD_REGEX_PATTERN = "invalidPasswordRegexPatternMessage";
|
public static final String INVALID_PASSWORD_REGEX_PATTERN = "invalidPasswordRegexPatternMessage";
|
||||||
|
public static final String INVALID_PASSWORD_HISTORY = "invalidPasswordHistoryMessage";
|
||||||
|
|
||||||
private List<Policy> policies;
|
private List<Policy> policies;
|
||||||
private String policyString;
|
private String policyString;
|
||||||
|
@ -67,10 +72,12 @@ public class PasswordPolicy {
|
||||||
} else if (name.equals(HashIterations.NAME)) {
|
} else if (name.equals(HashIterations.NAME)) {
|
||||||
list.add(new HashIterations(args));
|
list.add(new HashIterations(args));
|
||||||
} else if (name.equals(RegexPatterns.NAME)) {
|
} else if (name.equals(RegexPatterns.NAME)) {
|
||||||
for(String regexPattern : args) {
|
for (String regexPattern : args) {
|
||||||
Pattern.compile(regexPattern);
|
Pattern.compile(regexPattern);
|
||||||
}
|
}
|
||||||
list.add(new RegexPatterns(args));
|
list.add(new RegexPatterns(args));
|
||||||
|
} else if (name.equals(PasswordHistory.NAME)) {
|
||||||
|
list.add(new PasswordHistory(args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -92,9 +99,35 @@ public class PasswordPolicy {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Error validate(String username, String password) {
|
/**
|
||||||
|
*
|
||||||
|
* @return -1 if no expired passwords setting
|
||||||
|
*/
|
||||||
|
public int getExpiredPasswords() {
|
||||||
|
if (policies == null)
|
||||||
|
return -1;
|
||||||
for (Policy p : policies) {
|
for (Policy p : policies) {
|
||||||
Error error = p.validate(username, password);
|
if (p instanceof PasswordHistory) {
|
||||||
|
return ((PasswordHistory) p).passwordHistoryPolicyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
for (Policy p : policies) {
|
||||||
|
Error error = p.validate(user, password);
|
||||||
|
if (error != null) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Error validate(String user, String password) {
|
||||||
|
for (Policy p : policies) {
|
||||||
|
Error error = p.validate(user, password);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +136,8 @@ public class PasswordPolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static interface Policy {
|
private static interface Policy {
|
||||||
public Error validate(String username, String password);
|
public Error validate(UserModel user, String password);
|
||||||
|
public Error validate(String user, String password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Error {
|
public static class Error {
|
||||||
|
@ -131,9 +165,15 @@ public class PasswordPolicy {
|
||||||
public HashIterations(String[] args) {
|
public HashIterations(String[] args) {
|
||||||
iterations = intArg(NAME, 1, args);
|
iterations = intArg(NAME, 1, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +188,11 @@ public class PasswordPolicy {
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
|
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Length implements Policy {
|
private static class Length implements Policy {
|
||||||
|
@ -157,11 +202,17 @@ public class PasswordPolicy {
|
||||||
public Length(String[] args) {
|
public Length(String[] args) {
|
||||||
min = intArg(NAME, 8, args);
|
min = intArg(NAME, 8, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
|
return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Digits implements Policy {
|
private static class Digits implements Policy {
|
||||||
|
@ -171,6 +222,7 @@ public class PasswordPolicy {
|
||||||
public Digits(String[] args) {
|
public Digits(String[] args) {
|
||||||
min = intArg(NAME, 1, args);
|
min = intArg(NAME, 1, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
|
@ -182,6 +234,11 @@ public class PasswordPolicy {
|
||||||
}
|
}
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
|
return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LowerCase implements Policy {
|
private static class LowerCase implements Policy {
|
||||||
|
@ -191,7 +248,7 @@ public class PasswordPolicy {
|
||||||
public LowerCase(String[] args) {
|
public LowerCase(String[] args) {
|
||||||
min = intArg(NAME, 1, args);
|
min = intArg(NAME, 1, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
@ -202,6 +259,11 @@ public class PasswordPolicy {
|
||||||
}
|
}
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min) : null;
|
return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class UpperCase implements Policy {
|
private static class UpperCase implements Policy {
|
||||||
|
@ -222,6 +284,11 @@ public class PasswordPolicy {
|
||||||
}
|
}
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
|
return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SpecialChars implements Policy {
|
private static class SpecialChars implements Policy {
|
||||||
|
@ -231,7 +298,7 @@ public class PasswordPolicy {
|
||||||
public SpecialChars(String[] args) {
|
public SpecialChars(String[] args) {
|
||||||
min = intArg(NAME, 1, args);
|
min = intArg(NAME, 1, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
@ -242,6 +309,11 @@ public class PasswordPolicy {
|
||||||
}
|
}
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
|
return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RegexPatterns implements Policy {
|
private static class RegexPatterns implements Policy {
|
||||||
|
@ -256,17 +328,96 @@ public class PasswordPolicy {
|
||||||
public Error validate(String username, String password) {
|
public Error validate(String username, String password) {
|
||||||
Pattern pattern = null;
|
Pattern pattern = null;
|
||||||
Matcher matcher = null;
|
Matcher matcher = null;
|
||||||
for(String regexPattern : regexPatterns) {
|
for (String regexPattern : regexPatterns) {
|
||||||
pattern = Pattern.compile(regexPattern);
|
pattern = Pattern.compile(regexPattern);
|
||||||
matcher = pattern.matcher(password);
|
matcher = pattern.matcher(password);
|
||||||
if (!matcher.matches()) {
|
if (!matcher.matches()) {
|
||||||
return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object)regexPatterns);
|
return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object) regexPatterns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class PasswordHistory implements Policy {
|
||||||
|
private static final String NAME = "passwordHistory";
|
||||||
|
private int passwordHistoryPolicyValue;
|
||||||
|
|
||||||
|
public PasswordHistory(String[] args) {
|
||||||
|
passwordHistoryPolicyValue = intArg(NAME, 3, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(String user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Error validate(UserModel user, String password) {
|
||||||
|
|
||||||
|
if (passwordHistoryPolicyValue != -1) {
|
||||||
|
|
||||||
|
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
|
||||||
|
if (cred != null) {
|
||||||
|
if(new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue(), cred.getHashIterations())) {
|
||||||
|
return new Error(INVALID_PASSWORD_HISTORY, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
|
||||||
|
UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
|
||||||
|
if (new Pbkdf2PasswordEncoder(credential.getSalt()).verify(password, credential.getValue(), credential.getHashIterations())) {
|
||||||
|
return new Error(INVALID_PASSWORD_HISTORY, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
|
||||||
|
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
||||||
|
if (model.getType().equals(credType)) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue,
|
||||||
|
String credType) {
|
||||||
|
List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
|
||||||
|
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
||||||
|
if (model.getType().equals(credType)) {
|
||||||
|
credentialModels.add(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
|
||||||
|
public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
|
||||||
|
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
||||||
|
return -1;
|
||||||
|
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (credentialModels.size() > expiredPasswordsPolicyValue) {
|
||||||
|
return credentialModels.subList(0, expiredPasswordsPolicyValue);
|
||||||
|
}
|
||||||
|
return credentialModels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int intArg(String policy, int defaultValue, String... args) {
|
private static int intArg(String policy, int defaultValue, String... args) {
|
||||||
if (args == null || args.length == 0) {
|
if (args == null || args.length == 0) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public class UserCredentialModel {
|
public class UserCredentialModel {
|
||||||
public static final String PASSWORD = "password";
|
public static final String PASSWORD = "password";
|
||||||
|
public static final String PASSWORD_HISTORY = "password-history";
|
||||||
public static final String PASSWORD_TOKEN = "password-token";
|
public static final String PASSWORD_TOKEN = "password-token";
|
||||||
|
|
||||||
// Secret is same as password but it is not hashed
|
// Secret is same as password but it is not hashed
|
||||||
|
|
|
@ -12,6 +12,7 @@ public class UserCredentialValueModel {
|
||||||
private String device;
|
private String device;
|
||||||
private byte[] salt;
|
private byte[] salt;
|
||||||
private int hashIterations;
|
private int hashIterations;
|
||||||
|
private long createdDate;
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
|
@ -52,4 +53,13 @@ public class UserCredentialValueModel {
|
||||||
public void setHashIterations(int iterations) {
|
public void setHashIterations(int iterations) {
|
||||||
this.hashIterations = iterations;
|
this.hashIterations = iterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(long createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,7 +323,7 @@ public class UserFederationManager implements UserProvider {
|
||||||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||||
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
if (realm.getPasswordPolicy() != null) {
|
if (realm.getPasswordPolicy() != null) {
|
||||||
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue());
|
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user, credential.getValue());
|
||||||
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,23 @@ package org.keycloak.models.entities;
|
||||||
*/
|
*/
|
||||||
public class CredentialEntity {
|
public class CredentialEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
private String type;
|
private String type;
|
||||||
private String value;
|
private String value;
|
||||||
private String device;
|
private String device;
|
||||||
private byte[] salt;
|
private byte[] salt;
|
||||||
private int hashIterations;
|
private int hashIterations;
|
||||||
|
private long createdDate;
|
||||||
|
private UserEntity user;
|
||||||
|
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
|
@ -50,4 +62,21 @@ public class CredentialEntity {
|
||||||
public void setHashIterations(int hashIterations) {
|
public void setHashIterations(int hashIterations) {
|
||||||
this.hashIterations = hashIterations;
|
this.hashIterations = hashIterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(long createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserEntity getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(UserEntity user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.file.adapter;
|
package org.keycloak.models.file.adapter;
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||||
|
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -28,11 +30,14 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.connections.file.InMemoryModel;
|
import org.keycloak.connections.file.InMemoryModel;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||||
|
@ -209,29 +214,76 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCredential(UserCredentialModel cred) {
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
|
||||||
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
|
updatePasswordCredential(cred);
|
||||||
|
} else {
|
||||||
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
|
if (credentialEntity == null) {
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePasswordCredential(UserCredentialModel cred) {
|
||||||
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
if (credentialEntity == null) {
|
if (credentialEntity == null) {
|
||||||
credentialEntity = new CredentialEntity();
|
credentialEntity = setCredentials(user, cred);
|
||||||
credentialEntity.setType(cred.getType());
|
setValue(credentialEntity, cred);
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
}
|
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
byte[] salt = Pbkdf2PasswordEncoder.getSalt();
|
|
||||||
int hashIterations = 1;
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
if (policy != null) {
|
|
||||||
hashIterations = policy.getHashIterations();
|
|
||||||
if (hashIterations == -1) hashIterations = 1;
|
|
||||||
}
|
|
||||||
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
|
||||||
credentialEntity.setSalt(salt);
|
|
||||||
credentialEntity.setHashIterations(hashIterations);
|
|
||||||
} else {
|
} else {
|
||||||
credentialEntity.setValue(cred.getValue());
|
|
||||||
|
int expiredPasswordsPolicyValue = -1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if(policy != null) {
|
||||||
|
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredPasswordsPolicyValue != -1) {
|
||||||
|
user.getCredentials().remove(credentialEntity);
|
||||||
|
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
|
||||||
|
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
|
||||||
|
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
|
||||||
|
CredentialEntity credentialEntity = new CredentialEntity();
|
||||||
|
credentialEntity.setType(cred.getType());
|
||||||
|
credentialEntity.setCreatedDate(new Date().getTime());
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
credentialEntity.setDevice(cred.getDevice());
|
||||||
|
return credentialEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
|
||||||
|
byte[] salt = getSalt();
|
||||||
|
int hashIterations = 1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if (policy != null) {
|
||||||
|
hashIterations = policy.getHashIterations();
|
||||||
|
if (hashIterations == -1)
|
||||||
|
hashIterations = 1;
|
||||||
|
}
|
||||||
|
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
||||||
|
credentialEntity.setSalt(salt);
|
||||||
|
credentialEntity.setHashIterations(hashIterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
|
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
|
||||||
|
@ -244,6 +296,30 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
|
||||||
|
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
|
||||||
|
for (CredentialEntity entity : userEntity.getCredentials()) {
|
||||||
|
if (entity.getType().equals(credType)) {
|
||||||
|
credentialEntities.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
|
||||||
|
// Orders from most recent to least recent
|
||||||
|
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
|
||||||
|
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
|
||||||
|
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
||||||
|
return -1;
|
||||||
|
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return credentialEntities;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||||
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
|
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
|
||||||
|
@ -253,6 +329,7 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
UserCredentialValueModel credModel = new UserCredentialValueModel();
|
UserCredentialValueModel credModel = new UserCredentialValueModel();
|
||||||
credModel.setType(credEntity.getType());
|
credModel.setType(credEntity.getType());
|
||||||
credModel.setDevice(credEntity.getDevice());
|
credModel.setDevice(credEntity.getDevice());
|
||||||
|
credModel.setCreatedDate(credEntity.getCreatedDate());
|
||||||
credModel.setValue(credEntity.getValue());
|
credModel.setValue(credEntity.getValue());
|
||||||
credModel.setSalt(credEntity.getSalt());
|
credModel.setSalt(credEntity.getSalt());
|
||||||
credModel.setHashIterations(credEntity.getHashIterations());
|
credModel.setHashIterations(credEntity.getHashIterations());
|
||||||
|
@ -272,6 +349,7 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
// credentialEntity.setId(KeycloakModelUtils.generateId());
|
// credentialEntity.setId(KeycloakModelUtils.generateId());
|
||||||
credentialEntity.setType(credModel.getType());
|
credentialEntity.setType(credModel.getType());
|
||||||
// credentialEntity.setUser(user);
|
// credentialEntity.setUser(user);
|
||||||
|
credModel.setCreatedDate(credModel.getCreatedDate());
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,11 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -160,7 +164,6 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return user.getFirstName();
|
return user.getFirstName();
|
||||||
|
@ -208,33 +211,82 @@ public class UserAdapter implements UserModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCredential(UserCredentialModel cred) {
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
|
||||||
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
|
updatePasswordCredential(cred);
|
||||||
|
} else {
|
||||||
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
|
if (credentialEntity == null) {
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
em.persist(credentialEntity);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
em.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePasswordCredential(UserCredentialModel cred) {
|
||||||
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
if (credentialEntity == null) {
|
if (credentialEntity == null) {
|
||||||
credentialEntity = new CredentialEntity();
|
credentialEntity = setCredentials(user, cred);
|
||||||
credentialEntity.setId(KeycloakModelUtils.generateId());
|
setValue(credentialEntity, cred);
|
||||||
credentialEntity.setType(cred.getType());
|
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
|
||||||
credentialEntity.setUser(user);
|
|
||||||
em.persist(credentialEntity);
|
em.persist(credentialEntity);
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
}
|
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
byte[] salt = getSalt();
|
|
||||||
int hashIterations = 1;
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
if (policy != null) {
|
|
||||||
hashIterations = policy.getHashIterations();
|
|
||||||
if (hashIterations == -1) hashIterations = 1;
|
|
||||||
}
|
|
||||||
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
|
||||||
credentialEntity.setSalt(salt);
|
|
||||||
credentialEntity.setHashIterations(hashIterations);
|
|
||||||
} else {
|
} else {
|
||||||
credentialEntity.setValue(cred.getValue());
|
|
||||||
|
int expiredPasswordsPolicyValue = -1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if(policy != null) {
|
||||||
|
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredPasswordsPolicyValue != -1) {
|
||||||
|
user.getCredentials().remove(credentialEntity);
|
||||||
|
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
|
||||||
|
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
|
||||||
|
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
em.persist(credentialEntity);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
|
||||||
|
CredentialEntity credentialEntity = new CredentialEntity();
|
||||||
|
credentialEntity.setId(KeycloakModelUtils.generateId());
|
||||||
|
credentialEntity.setType(cred.getType());
|
||||||
|
credentialEntity.setCreatedDate(new Date().getTime());
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
credentialEntity.setDevice(cred.getDevice());
|
||||||
em.flush();
|
credentialEntity.setUser(user);
|
||||||
|
return credentialEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
|
||||||
|
byte[] salt = getSalt();
|
||||||
|
int hashIterations = 1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if (policy != null) {
|
||||||
|
hashIterations = policy.getHashIterations();
|
||||||
|
if (hashIterations == -1)
|
||||||
|
hashIterations = 1;
|
||||||
|
}
|
||||||
|
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
||||||
|
credentialEntity.setSalt(salt);
|
||||||
|
credentialEntity.setHashIterations(hashIterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
|
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
|
||||||
|
@ -247,6 +299,30 @@ public class UserAdapter implements UserModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
|
||||||
|
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
|
||||||
|
for (CredentialEntity entity : userEntity.getCredentials()) {
|
||||||
|
if (entity.getType().equals(credType)) {
|
||||||
|
credentialEntities.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
|
||||||
|
// Orders from most recent to least recent
|
||||||
|
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
|
||||||
|
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
|
||||||
|
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
||||||
|
return -1;
|
||||||
|
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return credentialEntities;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||||
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
|
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
|
||||||
|
@ -258,6 +334,7 @@ public class UserAdapter implements UserModel {
|
||||||
credModel.setType(credEntity.getType());
|
credModel.setType(credEntity.getType());
|
||||||
credModel.setDevice(credEntity.getDevice());
|
credModel.setDevice(credEntity.getDevice());
|
||||||
credModel.setValue(credEntity.getValue());
|
credModel.setValue(credEntity.getValue());
|
||||||
|
credModel.setCreatedDate(credEntity.getCreatedDate());
|
||||||
credModel.setSalt(credEntity.getSalt());
|
credModel.setSalt(credEntity.getSalt());
|
||||||
credModel.setHashIterations(credEntity.getHashIterations());
|
credModel.setHashIterations(credEntity.getHashIterations());
|
||||||
|
|
||||||
|
@ -276,6 +353,7 @@ public class UserAdapter implements UserModel {
|
||||||
credentialEntity = new CredentialEntity();
|
credentialEntity = new CredentialEntity();
|
||||||
credentialEntity.setId(KeycloakModelUtils.generateId());
|
credentialEntity.setId(KeycloakModelUtils.generateId());
|
||||||
credentialEntity.setType(credModel.getType());
|
credentialEntity.setType(credModel.getType());
|
||||||
|
credentialEntity.setCreatedDate(credModel.getCreatedDate());
|
||||||
credentialEntity.setUser(user);
|
credentialEntity.setUser(user);
|
||||||
em.persist(credentialEntity);
|
em.persist(credentialEntity);
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
|
|
|
@ -37,7 +37,9 @@ public class CredentialEntity {
|
||||||
protected byte[] salt;
|
protected byte[] salt;
|
||||||
@Column(name="HASH_ITERATIONS")
|
@Column(name="HASH_ITERATIONS")
|
||||||
protected int hashIterations;
|
protected int hashIterations;
|
||||||
|
@Column(name="CREATED_DATE")
|
||||||
|
protected long createdDate;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name="USER_ID")
|
@JoinColumn(name="USER_ID")
|
||||||
protected UserEntity user;
|
protected UserEntity user;
|
||||||
|
@ -97,4 +99,13 @@ public class CredentialEntity {
|
||||||
public void setHashIterations(int hashIterations) {
|
public void setHashIterations(int hashIterations) {
|
||||||
this.hashIterations = hashIterations;
|
this.hashIterations = hashIterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(long createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.models.mongo.keycloak.adapters;
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||||
|
|
||||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -17,6 +19,8 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -29,7 +33,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implements UserModel {
|
public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implements UserModel {
|
||||||
|
|
||||||
private final MongoUserEntity user;
|
private final MongoUserEntity user;
|
||||||
private final RealmModel realm;
|
private final RealmModel realm;
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
@ -177,31 +181,77 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCredential(UserCredentialModel cred) {
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
|
||||||
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
|
updatePasswordCredential(cred);
|
||||||
|
} else {
|
||||||
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
|
if (credentialEntity == null) {
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
credentialEntity.setValue(cred.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getMongoStore().updateEntity(user, invocationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePasswordCredential(UserCredentialModel cred) {
|
||||||
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
|
||||||
|
|
||||||
if (credentialEntity == null) {
|
if (credentialEntity == null) {
|
||||||
credentialEntity = new CredentialEntity();
|
credentialEntity = setCredentials(user, cred);
|
||||||
credentialEntity.setType(cred.getType());
|
setValue(credentialEntity, cred);
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
}
|
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
byte[] salt = Pbkdf2PasswordEncoder.getSalt();
|
|
||||||
int hashIterations = 1;
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
if (policy != null) {
|
|
||||||
hashIterations = policy.getHashIterations();
|
|
||||||
if (hashIterations == -1) hashIterations = 1;
|
|
||||||
}
|
|
||||||
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
|
||||||
credentialEntity.setSalt(salt);
|
|
||||||
credentialEntity.setHashIterations(hashIterations);
|
|
||||||
} else {
|
} else {
|
||||||
credentialEntity.setValue(cred.getValue());
|
|
||||||
}
|
|
||||||
credentialEntity.setDevice(cred.getDevice());
|
|
||||||
|
|
||||||
getMongoStore().updateEntity(user, invocationContext);
|
int expiredPasswordsPolicyValue = -1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if(policy != null) {
|
||||||
|
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredPasswordsPolicyValue != -1) {
|
||||||
|
user.getCredentials().remove(credentialEntity);
|
||||||
|
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
|
||||||
|
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
|
||||||
|
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialEntity = setCredentials(user, cred);
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
user.getCredentials().add(credentialEntity);
|
||||||
|
} else {
|
||||||
|
setValue(credentialEntity, cred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialEntity setCredentials(MongoUserEntity user, UserCredentialModel cred) {
|
||||||
|
CredentialEntity credentialEntity = new CredentialEntity();
|
||||||
|
credentialEntity.setType(cred.getType());
|
||||||
|
credentialEntity.setCreatedDate(new Date().getTime());
|
||||||
|
credentialEntity.setDevice(cred.getDevice());
|
||||||
|
return credentialEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
|
||||||
|
byte[] salt = getSalt();
|
||||||
|
int hashIterations = 1;
|
||||||
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
|
if (policy != null) {
|
||||||
|
hashIterations = policy.getHashIterations();
|
||||||
|
if (hashIterations == -1)
|
||||||
|
hashIterations = 1;
|
||||||
|
}
|
||||||
|
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
||||||
|
credentialEntity.setSalt(salt);
|
||||||
|
credentialEntity.setHashIterations(hashIterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
|
private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
|
||||||
|
@ -214,6 +264,30 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<CredentialEntity> getCredentialEntities(MongoUserEntity userEntity, String credType) {
|
||||||
|
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
|
||||||
|
for (CredentialEntity entity : userEntity.getCredentials()) {
|
||||||
|
if (entity.getType().equals(credType)) {
|
||||||
|
credentialEntities.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
|
||||||
|
// Orders from most recent to least recent
|
||||||
|
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
|
||||||
|
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
|
||||||
|
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
||||||
|
return -1;
|
||||||
|
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return credentialEntities;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||||
List<CredentialEntity> credentials = user.getCredentials();
|
List<CredentialEntity> credentials = user.getCredentials();
|
||||||
|
@ -222,6 +296,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
UserCredentialValueModel credModel = new UserCredentialValueModel();
|
UserCredentialValueModel credModel = new UserCredentialValueModel();
|
||||||
credModel.setType(credEntity.getType());
|
credModel.setType(credEntity.getType());
|
||||||
credModel.setDevice(credEntity.getDevice());
|
credModel.setDevice(credEntity.getDevice());
|
||||||
|
credModel.setCreatedDate(credEntity.getCreatedDate());
|
||||||
credModel.setValue(credEntity.getValue());
|
credModel.setValue(credEntity.getValue());
|
||||||
credModel.setSalt(credEntity.getSalt());
|
credModel.setSalt(credEntity.getSalt());
|
||||||
credModel.setHashIterations(credEntity.getHashIterations());
|
credModel.setHashIterations(credEntity.getHashIterations());
|
||||||
|
@ -239,6 +314,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
if (credentialEntity == null) {
|
if (credentialEntity == null) {
|
||||||
credentialEntity = new CredentialEntity();
|
credentialEntity = new CredentialEntity();
|
||||||
credentialEntity.setType(credModel.getType());
|
credentialEntity.setType(credModel.getType());
|
||||||
|
credModel.setCreatedDate(credModel.getCreatedDate());
|
||||||
user.getCredentials().add(credentialEntity);
|
user.getCredentials().add(credentialEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -657,6 +657,8 @@ public class UsersResource {
|
||||||
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
|
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
|
||||||
try {
|
try {
|
||||||
session.users().updateCredential(realm, user, cred);
|
session.users().updateCredential(realm, user, cred);
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
throw new BadRequestException("Resetting to N old passwords is not allowed.");
|
||||||
} catch (ModelReadOnlyException mre) {
|
} catch (ModelReadOnlyException mre) {
|
||||||
throw new BadRequestException("Can't reset password as account is read only");
|
throw new BadRequestException("Can't reset password as account is read only");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue