Merge pull request #1934 from stianst/KEYCLOAK-1900-PASS-HASH
KEYCLOAK-1900 Password Hash SPI
This commit is contained in:
commit
7313b843b9
39 changed files with 447 additions and 314 deletions
|
@ -1103,6 +1103,7 @@ module.factory('PasswordPolicy', function() {
|
|||
var p = {};
|
||||
|
||||
p.policyMessages = {
|
||||
hashAlgorithm: "Default hashing algorithm. Default is 'pbkdf2'.",
|
||||
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
|
||||
length: "Minimal password length (integer type). Default value is 8.",
|
||||
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
||||
|
@ -1116,6 +1117,7 @@ module.factory('PasswordPolicy', function() {
|
|||
}
|
||||
|
||||
p.allPolicies = [
|
||||
{ name: 'hashAlgorithm', value: 'pbkdf2' },
|
||||
{ name: 'hashIterations', value: 1 },
|
||||
{ name: 'length', value: 8 },
|
||||
{ name: 'digits', value: 1 },
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.hash;
|
||||
|
||||
import org.keycloak.models.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class PasswordHashManager {
|
||||
|
||||
public static UserCredentialValueModel encode(KeycloakSession session, RealmModel realm, String rawPassword) {
|
||||
PasswordPolicy passwordPolicy = realm.getPasswordPolicy();
|
||||
String algorithm = passwordPolicy.getHashAlgorithm();
|
||||
int iterations = passwordPolicy.getHashIterations();
|
||||
if (iterations < 1) {
|
||||
iterations = 1;
|
||||
}
|
||||
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
|
||||
if (provider == null) {
|
||||
throw new RuntimeException("Password hash provider for algorithm " + algorithm + " not found");
|
||||
}
|
||||
return provider.encode(rawPassword, iterations);
|
||||
}
|
||||
|
||||
public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
|
||||
String algorithm = credential.getAlgorithm() != null ? credential.getAlgorithm() : realm.getPasswordPolicy().getHashAlgorithm();
|
||||
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
|
||||
return provider.verify(password, credential);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.keycloak.hash;
|
||||
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
|
||||
*/
|
||||
public interface PasswordHashProvider extends Provider {
|
||||
|
||||
UserCredentialValueModel encode(String rawPassword, int iterations);
|
||||
|
||||
boolean verify(String rawPassword, UserCredentialValueModel credential);
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.keycloak.hash;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
|
||||
*/
|
||||
public interface PasswordHashProviderFactory extends ProviderFactory<PasswordHashProvider> {
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.hash;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
|
||||
*/
|
||||
public class PasswordHashSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "password-hash";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return PasswordHashProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return PasswordHashProviderFactory.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package org.keycloak.hash;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
|
||||
*/
|
||||
public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider {
|
||||
|
||||
public static final String ID = "pbkdf2";
|
||||
|
||||
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
private static final int DERIVED_KEY_SIZE = 512;
|
||||
|
||||
public UserCredentialValueModel encode(String rawPassword, int iterations) {
|
||||
byte[] salt = getSalt();
|
||||
String encodedPassword = encode(rawPassword, iterations, salt);
|
||||
|
||||
UserCredentialValueModel credentials = new UserCredentialValueModel();
|
||||
credentials.setAlgorithm(ID);
|
||||
credentials.setType(UserCredentialModel.PASSWORD);
|
||||
credentials.setSalt(salt);
|
||||
credentials.setHashIterations(iterations);
|
||||
credentials.setValue(encodedPassword);
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public boolean verify(String rawPassword, UserCredentialValueModel credential) {
|
||||
return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordHashProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
private String encode(String rawPassword, int iterations, byte[] salt) {
|
||||
KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
|
||||
|
||||
try {
|
||||
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
|
||||
return Base64.encodeBytes(key);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new RuntimeException("Credential could not be encoded");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getSalt() {
|
||||
byte[] buffer = new byte[16];
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private SecretKeyFactory getSecretKeyFactory() {
|
||||
try {
|
||||
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("PBKDF2 algorithm not found");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,8 @@ public interface Constants {
|
|||
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
|
||||
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
|
||||
|
||||
String DEFAULT_HASH_ALGORITHM = "pbkdf2";
|
||||
|
||||
// 15 minutes
|
||||
int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
|
||||
// 30 days
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.hash.PasswordHashManager;
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
@ -67,6 +68,8 @@ public class PasswordPolicy implements Serializable {
|
|||
list.add(new SpecialChars(arg));
|
||||
} else if (name.equals(NotUsername.NAME)) {
|
||||
list.add(new NotUsername(arg));
|
||||
} else if (name.equals(HashAlgorithm.NAME)) {
|
||||
list.add(new HashAlgorithm(arg));
|
||||
} else if (name.equals(HashIterations.NAME)) {
|
||||
list.add(new HashIterations(arg));
|
||||
} else if (name.equals(RegexPatterns.NAME)) {
|
||||
|
@ -83,6 +86,18 @@ public class PasswordPolicy implements Serializable {
|
|||
return list;
|
||||
}
|
||||
|
||||
public String getHashAlgorithm() {
|
||||
if (policies == null)
|
||||
return Constants.DEFAULT_HASH_ALGORITHM;
|
||||
for (Policy p : policies) {
|
||||
if (p instanceof HashAlgorithm) {
|
||||
return ((HashAlgorithm) p).algorithm;
|
||||
}
|
||||
|
||||
}
|
||||
return Constants.DEFAULT_HASH_ALGORITHM;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return -1 if no hash iterations setting
|
||||
|
@ -131,9 +146,9 @@ public class PasswordPolicy implements Serializable {
|
|||
return -1;
|
||||
}
|
||||
|
||||
public Error validate(UserModel user, String password) {
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
for (Policy p : policies) {
|
||||
Error error = p.validate(user, password);
|
||||
Error error = p.validate(session, user, password);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
|
@ -141,9 +156,9 @@ public class PasswordPolicy implements Serializable {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Error validate(String user, String password) {
|
||||
public Error validate(KeycloakSession session, String user, String password) {
|
||||
for (Policy p : policies) {
|
||||
Error error = p.validate(user, password);
|
||||
Error error = p.validate(session, user, password);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
|
@ -152,8 +167,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
private static interface Policy extends Serializable {
|
||||
public Error validate(UserModel user, String password);
|
||||
public Error validate(String user, String password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password);
|
||||
public Error validate(KeycloakSession session, String user, String password);
|
||||
}
|
||||
|
||||
public static class Error {
|
||||
|
@ -174,6 +189,25 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
private static class HashAlgorithm implements Policy {
|
||||
private static final String NAME = "hashAlgorithm";
|
||||
private String algorithm;
|
||||
|
||||
public HashAlgorithm(String arg) {
|
||||
algorithm = stringArg(NAME, Constants.DEFAULT_HASH_ALGORITHM, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(KeycloakSession session, String user, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HashIterations implements Policy {
|
||||
private static final String NAME = "hashIterations";
|
||||
private int iterations;
|
||||
|
@ -181,14 +215,14 @@ public class PasswordPolicy implements Serializable {
|
|||
public HashIterations(String arg) {
|
||||
iterations = intArg(NAME, 1, arg);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Error validate(String user, String password) {
|
||||
public Error validate(KeycloakSession session, String user, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -200,13 +234,13 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,13 +255,13 @@ public class PasswordPolicy implements Serializable {
|
|||
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
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);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +276,7 @@ public class PasswordPolicy implements Serializable {
|
|||
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isDigit(c)) {
|
||||
|
@ -253,8 +287,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +302,7 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
|
@ -279,8 +313,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +327,7 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
|
@ -304,8 +338,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,7 +353,7 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (!Character.isLetterOrDigit(c)) {
|
||||
|
@ -330,8 +364,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +379,7 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
Pattern pattern = Pattern.compile(regexPattern);
|
||||
Matcher matcher = pattern.matcher(password);
|
||||
if (!matcher.matches()) {
|
||||
|
@ -355,8 +389,8 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
return validate(user.getUsername(), password);
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return validate(session, user.getUsername(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,18 +404,19 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String user, String password) {
|
||||
public Error validate(KeycloakSession session, String user, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
public Error validate(KeycloakSession session, 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())) {
|
||||
PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
|
||||
if(hashProvider.verify(password, cred)) {
|
||||
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +424,8 @@ public class PasswordPolicy implements Serializable {
|
|||
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())) {
|
||||
PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
|
||||
if (hashProvider.verify(password, credential)) {
|
||||
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
|
||||
}
|
||||
}
|
||||
|
@ -444,12 +480,12 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
public Error validate(KeycloakSession session, String username, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(UserModel user, String password) {
|
||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -462,6 +498,14 @@ public class PasswordPolicy implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
private static String stringArg(String policy, String defaultValue, String arg) {
|
||||
if (arg == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return policyString;
|
||||
|
|
|
@ -21,6 +21,7 @@ public class UserCredentialModel {
|
|||
protected String type;
|
||||
protected String value;
|
||||
protected String device;
|
||||
protected String algorithm;
|
||||
|
||||
public UserCredentialModel() {
|
||||
}
|
||||
|
@ -107,4 +108,12 @@ public class UserCredentialModel {
|
|||
public void setDevice(String device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -395,7 +395,7 @@ public class UserFederationManager implements UserProvider {
|
|||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
if (realm.getPasswordPolicy() != null) {
|
||||
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user, credential.getValue());
|
||||
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(session, user, credential.getValue());
|
||||
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +403,7 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
UserFederationProvider link = getFederationLink(realm, user);
|
||||
if (link != null) {
|
||||
validateUser(realm, user);
|
||||
|
@ -421,10 +421,10 @@ public class UserFederationManager implements UserProvider {
|
|||
if (!link.validCredentials(realm, user, fedCreds)) {
|
||||
return false;
|
||||
}
|
||||
return session.userStorage().validCredentials(realm, user, localCreds);
|
||||
return session.userStorage().validCredentials(session, realm, user, localCreds);
|
||||
}
|
||||
}
|
||||
return session.userStorage().validCredentials(realm, user, input);
|
||||
return session.userStorage().validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -466,12 +466,12 @@ public class UserFederationManager implements UserProvider {
|
|||
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return validCredentials(realm, user, Arrays.asList(input));
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return validCredentials(session, realm, user, Arrays.asList(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) {
|
||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||
List<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
|
||||
List<UserFederationProvider> fedProviders = new ArrayList<UserFederationProvider>();
|
||||
for (UserFederationProviderModel fedProviderModel : fedProviderModels) {
|
||||
|
|
|
@ -59,9 +59,9 @@ public interface UserProvider extends Provider {
|
|||
void preRemove(RealmModel realm, ClientModel client);
|
||||
void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper);
|
||||
|
||||
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
|
||||
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
|
||||
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
|
||||
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
|
||||
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
|
||||
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.hash.PasswordHashManager;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -11,8 +14,8 @@ import org.keycloak.models.UserCredentialValueModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.PasswordToken;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +41,7 @@ public class CredentialValidation {
|
|||
* @param password
|
||||
* @return
|
||||
*/
|
||||
public static boolean validPassword(RealmModel realm, UserModel user, String password) {
|
||||
public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password) {
|
||||
UserCredentialValueModel passwordCred = null;
|
||||
for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
|
||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
|
@ -47,25 +50,23 @@ public class CredentialValidation {
|
|||
}
|
||||
if (passwordCred == null) return false;
|
||||
|
||||
return validateHashedCredential(realm, user, password, passwordCred);
|
||||
return validateHashedCredential(session, realm, user, password, passwordCred);
|
||||
|
||||
}
|
||||
|
||||
public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
|
||||
|
||||
public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
|
||||
if(unhashedCredValue == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations());
|
||||
boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
|
||||
|
||||
if (validated) {
|
||||
int iterations = hashIterations(realm);
|
||||
if (iterations > -1 && iterations != credential.getHashIterations()) {
|
||||
UserCredentialValueModel newCred = new UserCredentialValueModel();
|
||||
newCred.setType(credential.getType());
|
||||
newCred.setDevice(credential.getDevice());
|
||||
newCred.setSalt(credential.getSalt());
|
||||
newCred.setHashIterations(iterations);
|
||||
newCred.setValue(new Pbkdf2PasswordEncoder(newCred.getSalt()).encode(unhashedCredValue, iterations));
|
||||
|
||||
UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
|
||||
user.updateCredentialDirectly(newCred);
|
||||
}
|
||||
|
||||
|
@ -157,9 +158,9 @@ public class CredentialValidation {
|
|||
* @param credentials
|
||||
* @return
|
||||
*/
|
||||
public static boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> credentials) {
|
||||
public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> credentials) {
|
||||
for (UserCredentialModel credential : credentials) {
|
||||
if (!validCredential(realm, user, credential)) return false;
|
||||
if (!validCredential(session, realm, user, credential)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -172,16 +173,16 @@ public class CredentialValidation {
|
|||
* @param credentials
|
||||
* @return
|
||||
*/
|
||||
public static boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... credentials) {
|
||||
public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... credentials) {
|
||||
for (UserCredentialModel credential : credentials) {
|
||||
if (!validCredential(realm, user, credential)) return false;
|
||||
if (!validCredential(session, realm, user, credential)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean validCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||
private static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
if (!validPassword(realm, user, credential.getValue())) {
|
||||
if (!validPassword(session, realm, user, credential.getValue())) {
|
||||
return false;
|
||||
}
|
||||
} else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) {
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.common.util.Base64;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Encoder that uses PBKDF2 function to cryptographically derive passwords.
|
||||
* </p>
|
||||
* <p>Passwords are returned with a Base64 encoding.</p>
|
||||
*
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
|
||||
*
|
||||
*/
|
||||
public class Pbkdf2PasswordEncoder {
|
||||
|
||||
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
public static final String RNG_ALGORITHM = "SHA1PRNG";
|
||||
|
||||
private static final int DERIVED_KEY_SIZE = 512;
|
||||
private static final int ITERATIONS = 1;
|
||||
|
||||
private final int iterations;
|
||||
private byte[] salt;
|
||||
|
||||
public Pbkdf2PasswordEncoder(byte[] salt, int iterations) {
|
||||
this.salt = salt;
|
||||
this.iterations = iterations;
|
||||
}
|
||||
|
||||
public Pbkdf2PasswordEncoder(byte[] salt) {
|
||||
this(salt, ITERATIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the raw password provided
|
||||
* @param rawPassword The password used as a master key to derive into a session key
|
||||
* @return encoded password in Base64
|
||||
*/
|
||||
public String encode(String rawPassword, int iterations) {
|
||||
|
||||
String encodedPassword;
|
||||
|
||||
KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
|
||||
|
||||
try {
|
||||
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
|
||||
encodedPassword = Base64.encodeBytes(key);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new RuntimeException("Credential could not be encoded");
|
||||
}
|
||||
|
||||
return encodedPassword;
|
||||
}
|
||||
|
||||
public String encode(String rawPassword) {
|
||||
return encode(rawPassword, iterations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the password provided and compare with the hash stored into the database
|
||||
* @param rawPassword The password provided
|
||||
* @param encodedPassword Encoded hash stored into the database
|
||||
* @return true if the password is valid, otherwise false for invalid credentials
|
||||
*/
|
||||
public boolean verify(String rawPassword, String encodedPassword) {
|
||||
return encode(rawPassword).equals(encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the password provided and compare with the hash stored into the database
|
||||
* @param rawPassword The password provided
|
||||
* @param encodedPassword Encoded hash stored into the database
|
||||
* @return true if the password is valid, otherwise false for invalid credentials
|
||||
*/
|
||||
public boolean verify(String rawPassword, String encodedPassword, int iterations) {
|
||||
return encode(rawPassword, iterations).equals(encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for each password
|
||||
* @return cryptographically strong random number
|
||||
*/
|
||||
public static byte[] getSalt() {
|
||||
byte[] buffer = new byte[16];
|
||||
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static SecretKeyFactory getSecretKeyFactory() {
|
||||
try {
|
||||
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("PBKDF2 algorithm not found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.hash.Pbkdf2PasswordHashProvider
|
|
@ -4,4 +4,5 @@ org.keycloak.models.RealmSpi
|
|||
org.keycloak.models.UserSessionSpi
|
||||
org.keycloak.models.UserSpi
|
||||
org.keycloak.models.session.UserSessionPersisterSpi
|
||||
org.keycloak.migration.MigrationSpi
|
||||
org.keycloak.migration.MigrationSpi
|
||||
org.keycloak.hash.PasswordHashSpi
|
||||
|
|
|
@ -15,73 +15,73 @@ public class PasswordPolicyTest {
|
|||
@Test
|
||||
public void testLength() {
|
||||
PasswordPolicy policy = new PasswordPolicy("length");
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "12345678"));
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "1234567").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{8}, policy.validate(null, "jdoe", "1234567").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "12345678"));
|
||||
|
||||
policy = new PasswordPolicy("length(4)");
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "123").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{4}, policy.validate(null, "jdoe", "123").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigits() {
|
||||
PasswordPolicy policy = new PasswordPolicy("digits");
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abcd1"));
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "abcd1"));
|
||||
|
||||
policy = new PasswordPolicy("digits(2)");
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abcd12"));
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd1").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abcd1").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "abcd12"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("lowerCase");
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ABcD1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABCD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "ABCD1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "ABcD1234"));
|
||||
|
||||
policy = new PasswordPolicy("lowerCase(2)");
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "aBcD1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABcD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ABcD1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "aBcD1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("upperCase");
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abCd1234"));
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "abCd1234"));
|
||||
|
||||
policy = new PasswordPolicy("upperCase(2)");
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "AbCd1234"));
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abCd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abCd1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "AbCd1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecialChars() {
|
||||
PasswordPolicy policy = new PasswordPolicy("specialChars");
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
|
||||
|
||||
policy = new PasswordPolicy("specialChars(2)");
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d-234"));
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "ab&d1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ab&d1234").getParameters());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d-234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotUsername() {
|
||||
PasswordPolicy policy = new PasswordPolicy("notUsername");
|
||||
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
|
||||
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -119,33 +119,33 @@ public class PasswordPolicyTest {
|
|||
|
||||
//Fails to match one of the regex pattern
|
||||
policy = new PasswordPolicy("regexPattern(jdoe) and regexPattern(j*d)");
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
||||
|
||||
////Fails to match all of the regex patterns
|
||||
policy = new PasswordPolicy("regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)");
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
||||
|
||||
policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
||||
|
||||
policy = new PasswordPolicy("regexPattern(jdoe)");
|
||||
Assert.assertNull(policy.validate("jdoe", "jdoe"));
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "jdoe"));
|
||||
|
||||
policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
|
||||
Assert.assertNull(policy.validate("jdoe", "jdoe0"));
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "jdoe0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplex() {
|
||||
PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()");
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aaBB&"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12AABB&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aabb&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc"));
|
||||
Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-"));
|
||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBB&"));
|
||||
Assert.assertNotNull(policy.validate(null, "jdoe", "aaaaBB&-"));
|
||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12AABB&-"));
|
||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aabb&-"));
|
||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBBcc"));
|
||||
Assert.assertNotNull(policy.validate(null, "12aaBB&-", "12aaBB&-"));
|
||||
|
||||
Assert.assertNull(policy.validate("jdoe", "12aaBB&-"));
|
||||
Assert.assertNull(policy.validate(null, "jdoe", "12aaBB&-"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -299,18 +299,18 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return getDelegate().validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return getDelegate().validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return getDelegate().validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return getDelegate().validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) {
|
||||
return getDelegate().validCredentials(realm, input);
|
||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||
return getDelegate().validCredentials(session, realm, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
|
@ -29,7 +30,6 @@ import org.keycloak.models.jpa.entities.UserEntity;
|
|||
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
@ -43,8 +43,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
|
|
@ -59,7 +59,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
entity.setRealmId(realm.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
UserModel userModel = new UserAdapter(realm, em, entity);
|
||||
UserModel userModel = new UserAdapter(session, realm, em, entity);
|
||||
|
||||
if (addDefaultRoles) {
|
||||
for (String r : realm.getDefaultRoles()) {
|
||||
|
@ -241,7 +241,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity user : results) {
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
users.add(new UserAdapter(session, realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
query.setParameter("realmId", realm.getId());
|
||||
List<UserEntity> entities = query.getResultList();
|
||||
if (entities.size() == 0) return null;
|
||||
return new UserAdapter(realm, em, entities.get(0));
|
||||
return new UserAdapter(session, realm, em, entities.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -269,7 +269,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
query.setParameter("realmId", realm.getId());
|
||||
List<UserEntity> results = query.getResultList();
|
||||
if (results.size() == 0) return null;
|
||||
return new UserAdapter(realm, em, results.get(0));
|
||||
return new UserAdapter(session, realm, em, results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -278,7 +278,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
query.setParameter("email", email.toLowerCase());
|
||||
query.setParameter("realmId", realm.getId());
|
||||
List<UserEntity> results = query.getResultList();
|
||||
return results.isEmpty() ? null : new UserAdapter(realm, em, results.get(0));
|
||||
return results.isEmpty() ? null : new UserAdapter(session, realm, em, results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -299,7 +299,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
", userId=" + identity.getUserId() + ", results=" + results);
|
||||
} else {
|
||||
UserEntity user = results.get(0);
|
||||
return new UserAdapter(realm, em, user);
|
||||
return new UserAdapter(session, realm, em, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
", results=" + results);
|
||||
} else {
|
||||
UserEntity user = results.get(0);
|
||||
return new UserAdapter(client.getRealm(), em, user);
|
||||
return new UserAdapter(session, client.getRealm(), em, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,7 +347,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity user : results) {
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
users.add(new UserAdapter(session, realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -446,7 +446,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -460,7 +460,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserAttributeEntity attr : results) {
|
||||
UserEntity user = attr.getUser();
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
users.add(new UserAdapter(session, realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
@ -495,17 +495,17 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return CredentialValidation.validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return CredentialValidation.validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return CredentialValidation.validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return CredentialValidation.validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) {
|
||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.hash.PasswordHashManager;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
|
@ -24,9 +26,9 @@ import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
|
|||
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
@ -41,8 +43,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -52,11 +52,13 @@ public class UserAdapter implements UserModel {
|
|||
protected UserEntity user;
|
||||
protected EntityManager em;
|
||||
protected RealmModel realm;
|
||||
private final KeycloakSession session;
|
||||
|
||||
public UserAdapter(RealmModel realm, EntityManager em, UserEntity user) {
|
||||
public UserAdapter(KeycloakSession session, RealmModel realm, EntityManager em, UserEntity user) {
|
||||
this.em = em;
|
||||
this.user = user;
|
||||
this.realm = realm;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
|
@ -387,18 +389,12 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
|
||||
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
|
||||
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
||||
credentialEntity.setSalt(salt);
|
||||
credentialEntity.setHashIterations(hashIterations);
|
||||
credentialEntity.setAlgorithm(encoded.getAlgorithm());
|
||||
credentialEntity.setValue(encoded.getValue());
|
||||
credentialEntity.setSalt(encoded.getSalt());
|
||||
credentialEntity.setHashIterations(encoded.getHashIterations());
|
||||
}
|
||||
|
||||
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
|
||||
|
|
|
@ -474,17 +474,17 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return CredentialValidation.validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
return CredentialValidation.validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return CredentialValidation.validCredentials(realm, user, input);
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return CredentialValidation.validCredentials(session, realm, user, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) {
|
||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.hash.PasswordHashManager;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
|
@ -26,8 +25,8 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
|||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -329,18 +328,12 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
|
||||
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
|
||||
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
|
||||
credentialEntity.setSalt(salt);
|
||||
credentialEntity.setHashIterations(hashIterations);
|
||||
credentialEntity.setAlgorithm(encoded.getAlgorithm());
|
||||
credentialEntity.setValue(encoded.getValue());
|
||||
credentialEntity.setSalt(encoded.getSalt());
|
||||
credentialEntity.setHashIterations(encoded.getHashIterations());
|
||||
}
|
||||
|
||||
private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
|
||||
|
|
|
@ -149,7 +149,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
|||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
credentials.add(UserCredentialModel.password(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
|
||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), user, credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
|
|
|
@ -49,7 +49,7 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
|
|||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().user(context.getUser())
|
||||
.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
|
|
|
@ -69,7 +69,7 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple
|
|||
String spnegoToken = tokens[1];
|
||||
UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken);
|
||||
|
||||
CredentialValidationOutput output = context.getSession().users().validCredentials(context.getRealm(), spnegoCredential);
|
||||
CredentialValidationOutput output = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), spnegoCredential);
|
||||
|
||||
if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) {
|
||||
context.setUser(output.getAuthenticatedUser());
|
||||
|
|
|
@ -51,7 +51,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
|
|||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().user(context.getUser());
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
|
|
|
@ -32,7 +32,7 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
|
|||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
credentials.add(UserCredentialModel.password(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().user(context.getUser());
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
|
|
|
@ -53,7 +53,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
|
|||
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
|
||||
}
|
||||
if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
|
||||
PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
|
||||
PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getSession(), context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
|
||||
if (err != null)
|
||||
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
|
|||
UserCredentialModel cred = new UserCredentialModel();
|
||||
cred.setType(context.getRealm().getOTPPolicy().getType());
|
||||
cred.setValue(totp);
|
||||
context.getSession().users().validCredentials(context.getRealm(), context.getUser(), cred);
|
||||
context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), cred);
|
||||
|
||||
context.getUser().setOtpEnabled(true);
|
||||
context.success();
|
||||
|
|
|
@ -572,7 +572,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
UserCredentialModel cred = new UserCredentialModel();
|
||||
cred.setType(realm.getOTPPolicy().getType());
|
||||
cred.setValue(totp);
|
||||
session.users().validCredentials(realm, user, cred);
|
||||
session.users().validCredentials(session, realm, user, cred);
|
||||
|
||||
event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
|
@ -619,7 +619,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
}
|
||||
|
||||
UserCredentialModel cred = UserCredentialModel.password(password);
|
||||
if (!session.users().validCredentials(realm, user, cred)) {
|
||||
if (!session.users().validCredentials(session, realm, user, cred)) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.services.validation;
|
||||
|
||||
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
|
@ -25,7 +26,7 @@ public class Validation {
|
|||
// Actually allow same emails like angular. See ValidationTest.testEmailValidation()
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
|
||||
|
||||
public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
|
||||
public static List<FormMessage> validateRegistrationForm(KeycloakSession session, RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
|
||||
List<FormMessage> errors = new ArrayList<>();
|
||||
|
||||
if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) {
|
||||
|
@ -55,7 +56,7 @@ public class Validation {
|
|||
}
|
||||
|
||||
if (formData.getFirst(FIELD_PASSWORD) != null) {
|
||||
PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
|
||||
PasswordPolicy.Error err = policy.validate(session, realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
|
||||
if (err != null)
|
||||
errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
||||
}
|
||||
|
|
|
@ -8,4 +8,4 @@ org.keycloak.authentication.ClientAuthenticatorSpi
|
|||
org.keycloak.authentication.RequiredActionSpi
|
||||
org.keycloak.authentication.FormAuthenticatorSpi
|
||||
org.keycloak.authentication.FormActionSpi
|
||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package org.keycloak.testsuite.adduser;
|
||||
|
||||
import org.codehaus.jackson.type.TypeReference;
|
||||
import org.junit.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.RoleMappingResource;
|
||||
import org.keycloak.admin.client.resource.RoleScopeResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.representations.idm.*;
|
||||
import org.keycloak.testsuite.KeycloakServer;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -19,7 +20,6 @@ import org.keycloak.wildfly.adduser.AddUser;
|
|||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -59,7 +59,11 @@ public class AddUserTest {
|
|||
UserRepresentation user = realms.get(0).getUsers().get(0);
|
||||
assertEquals(new Integer(100000), user.getCredentials().get(0).getHashIterations());
|
||||
assertNull(user.getCredentials().get(0).getValue());
|
||||
assertEquals(new Pbkdf2PasswordEncoder(Base64Url.decode(user.getCredentials().get(0).getSalt()), 100000).encode("password"), user.getCredentials().get(0).getHashedSaltedValue());
|
||||
|
||||
CredentialRepresentation credentials = user.getCredentials().get(0);
|
||||
|
||||
assertEquals(Pbkdf2PasswordHashProvider.ID, credentials.getAlgorithm());
|
||||
assertEquals(new Integer(100000), credentials.getHashIterations());
|
||||
|
||||
KeycloakServer server = new KeycloakServer();
|
||||
try {
|
||||
|
|
|
@ -322,7 +322,12 @@ public class ExportImportTest {
|
|||
Assert.fail("user " + username + " not found");
|
||||
}
|
||||
|
||||
Assert.assertTrue(userProvider.validCredentials(realm, user, UserCredentialModel.password(password)));
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
Assert.assertTrue(userProvider.validCredentials(session, realm, user, UserCredentialModel.password(password)));
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNotAuthenticated(UserProvider userProvider, RealmProvider realmProvider, String realmName, String username, String password) {
|
||||
|
@ -336,7 +341,12 @@ public class ExportImportTest {
|
|||
return;
|
||||
}
|
||||
|
||||
Assert.assertFalse(userProvider.validCredentials(realm, user, UserCredentialModel.password(password)));
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
Assert.assertFalse(userProvider.validCredentials(session, realm, user, UserCredentialModel.password(password)));
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addUser(UserProvider userProvider, RealmModel appRealm, String username, String password) {
|
||||
|
|
|
@ -678,7 +678,7 @@ public class FederationProvidersIntegrationTest {
|
|||
user.updateCredential(cred);
|
||||
UserCredentialValueModel userCredentialValueModel = user.getCredentialsDirectly().get(0);
|
||||
Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
|
||||
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
|
||||
Assert.assertTrue(session.users().validCredentials(session, appRealm, user, cred));
|
||||
|
||||
// LDAP password is still unchanged
|
||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model);
|
||||
|
|
|
@ -144,11 +144,11 @@ public class AdapterTest extends AbstractModelTest {
|
|||
cred.setType(CredentialRepresentation.PASSWORD);
|
||||
cred.setValue("geheim");
|
||||
user.updateCredential(cred);
|
||||
Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
|
||||
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
||||
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
|
||||
Assert.assertEquals(creds.get(0).getHashIterations(), 1);
|
||||
realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)"));
|
||||
Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
|
||||
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
||||
creds = user.getCredentialsDirectly();
|
||||
Assert.assertEquals(creds.get(0).getHashIterations(), 200);
|
||||
realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)"));
|
||||
|
|
|
@ -39,10 +39,10 @@ public class MultipleRealmsTest extends AbstractModelTest {
|
|||
r1user1.updateCredential(UserCredentialModel.password("pass1"));
|
||||
r2user1.updateCredential(UserCredentialModel.password("pass2"));
|
||||
|
||||
Assert.assertTrue(session.users().validCredentials(realm1, r1user1, UserCredentialModel.password("pass1")));
|
||||
Assert.assertFalse(session.users().validCredentials(realm1, r1user1, UserCredentialModel.password("pass2")));
|
||||
Assert.assertFalse(session.users().validCredentials(realm2, r2user1, UserCredentialModel.password("pass1")));
|
||||
Assert.assertTrue(session.users().validCredentials(realm2, r2user1, UserCredentialModel.password("pass2")));
|
||||
Assert.assertTrue(session.users().validCredentials(session, realm1, r1user1, UserCredentialModel.password("pass1")));
|
||||
Assert.assertFalse(session.users().validCredentials(session, realm1, r1user1, UserCredentialModel.password("pass2")));
|
||||
Assert.assertFalse(session.users().validCredentials(session, realm2, r2user1, UserCredentialModel.password("pass1")));
|
||||
Assert.assertTrue(session.users().validCredentials(session, realm2, r2user1, UserCredentialModel.password("pass2")));
|
||||
|
||||
// Test searching
|
||||
Assert.assertEquals(2, session.users().searchForUser("user", realm1).size());
|
||||
|
|
|
@ -102,7 +102,7 @@ public class ReadUsersWorker implements Worker {
|
|||
|
||||
// Validate password (shoould be same as username)
|
||||
if (readPassword) {
|
||||
session.users().validCredentials(realm, user, UserCredentialModel.password(username));
|
||||
session.users().validCredentials(session, realm, user, UserCredentialModel.password(username));
|
||||
}
|
||||
|
||||
// Read federatedIdentities of user
|
||||
|
|
|
@ -12,8 +12,8 @@ import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|||
import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
|
||||
import org.jboss.aesh.console.command.registry.CommandRegistry;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -138,14 +138,14 @@ public class AddUser {
|
|||
user.setUsername(userName);
|
||||
user.setCredentials(new LinkedList<CredentialRepresentation>());
|
||||
|
||||
byte[] salt = Pbkdf2PasswordEncoder.getSalt();
|
||||
iterations = iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS;
|
||||
UserCredentialValueModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS);
|
||||
|
||||
CredentialRepresentation credentials = new CredentialRepresentation();
|
||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||
credentials.setHashIterations(iterations);
|
||||
credentials.setSalt(Base64.encodeBytes(salt));
|
||||
credentials.setHashedSaltedValue(new Pbkdf2PasswordEncoder(salt).encode(password, iterations));
|
||||
credentials.setType(credentialValueModel.getType());
|
||||
credentials.setAlgorithm(credentialValueModel.getAlgorithm());
|
||||
credentials.setHashIterations(credentialValueModel.getHashIterations());
|
||||
credentials.setSalt(Base64.encodeBytes(credentialValueModel.getSalt()));
|
||||
credentials.setHashedSaltedValue(credentialValueModel.getValue());
|
||||
|
||||
user.getCredentials().add(credentials);
|
||||
|
||||
|
@ -289,4 +289,4 @@ public class AddUser {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue