Merge pull request #4194 from stianst/KEYCLOAK-4888

KEYCLOAK-4888
This commit is contained in:
Stian Thorgersen 2017-05-30 14:49:22 +02:00 committed by GitHub
commit a6e4245185
16 changed files with 490 additions and 252 deletions

View file

@ -37,12 +37,14 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
private final String providerId;
private final String pbkdf2Algorithm;
private int defaultIterations;
public static final int DERIVED_KEY_SIZE = 512;
public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm) {
public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm, int defaultIterations) {
this.providerId = providerId;
this.pbkdf2Algorithm = pbkdf2Algorithm;
this.defaultIterations = defaultIterations;
}
@Override
@ -52,6 +54,10 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
@Override
public void encode(String rawPassword, int iterations, CredentialModel credential) {
if (iterations == -1) {
iterations = defaultIterations;
}
byte[] salt = getSalt();
String encodedPassword = encode(rawPassword, iterations, salt);

View file

@ -30,9 +30,11 @@ public class Pbkdf2PasswordHashProviderFactory implements PasswordHashProviderFa
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
public static final int DEFAULT_ITERATIONS = 20000;
@Override
public PasswordHashProvider create(KeycloakSession session) {
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, 20000);
}
@Override

View file

@ -15,9 +15,11 @@ public class Pbkdf2Sha256PasswordHashProviderFactory implements PasswordHashProv
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
public static final int DEFAULT_ITERATIONS = 27500;
@Override
public PasswordHashProvider create(KeycloakSession session) {
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, DEFAULT_ITERATIONS);
}
@Override

View file

@ -15,9 +15,11 @@ public class Pbkdf2Sha512PasswordHashProviderFactory implements PasswordHashProv
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA512";
public static final int DEFAULT_ITERATIONS = 30000;
@Override
public PasswordHashProvider create(KeycloakSession session) {
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, DEFAULT_ITERATIONS);
}
@Override

View file

@ -34,6 +34,7 @@ import org.keycloak.migration.migrators.MigrateTo2_3_0;
import org.keycloak.migration.migrators.MigrateTo2_5_0;
import org.keycloak.migration.migrators.MigrateTo3_0_0;
import org.keycloak.migration.migrators.MigrateTo3_1_0;
import org.keycloak.migration.migrators.MigrateTo3_2_0;
import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession;
@ -60,7 +61,8 @@ public class MigrationModelManager {
new MigrateTo2_3_0(),
new MigrateTo2_5_0(),
new MigrateTo3_0_0(),
new MigrateTo3_1_0()
new MigrateTo3_1_0(),
new MigrateTo3_2_0()
};
public static void migrate(KeycloakSession session) {

View file

@ -0,0 +1,45 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
public class MigrateTo3_2_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("3.1.0");
@Override
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
PasswordPolicy.Builder builder = realm.getPasswordPolicy().toBuilder();
if (!builder.contains(PasswordPolicy.HASH_ALGORITHM_ID) && "20000".equals(builder.get(PasswordPolicy.HASH_ITERATIONS_ID))) {
realm.setPasswordPolicy(builder.remove(PasswordPolicy.HASH_ITERATIONS_ID).build(session));
}
}
}
@Override
public ModelVersion getVersion() {
return VERSION;
}
}

View file

@ -60,7 +60,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
@Override
public Object parseConfig(String value) {
return value != null ? Integer.parseInt(value) : PasswordPolicy.HASH_ITERATIONS_DEFAULT;
return parseInteger(value, -1);
}
@Override

View file

@ -22,6 +22,7 @@ import org.keycloak.policy.PasswordPolicyProvider;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@ -32,62 +33,33 @@ public class PasswordPolicy implements Serializable {
public static final String HASH_ALGORITHM_ID = "hashAlgorithm";
public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2";
public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2-sha256";
public static final String HASH_ITERATIONS_ID = "hashIterations";
public static final int HASH_ITERATIONS_DEFAULT = 20000;
public static final int HASH_ITERATIONS_DEFAULT = 27500;
public static final String PASSWORD_HISTORY_ID = "passwordHistory";
public static final String FORCE_EXPIRED_ID = "forceExpiredPasswordChange";
private String policyString;
private Map<String, Object> policyConfig;
private Builder builder;
public static PasswordPolicy empty() {
return new PasswordPolicy(null, new HashMap<>());
}
public static PasswordPolicy parse(KeycloakSession session, String policyString) {
Map<String, Object> policyConfig = new HashMap<>();
if (policyString != null && !policyString.trim().isEmpty()) {
for (String policy : policyString.split(" and ")) {
policy = policy.trim();
String key;
String config = null;
int i = policy.indexOf('(');
if (i == -1) {
key = policy.trim();
} else {
key = policy.substring(0, i).trim();
config = policy.substring(i + 1, policy.length() - 1);
}
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
if (provider == null) {
throw new PasswordPolicyConfigException("Password policy not found");
}
Object o;
try {
o = provider.parseConfig(config);
} catch (PasswordPolicyConfigException e) {
throw new ModelException("Invalid config for " + key + ": " + e.getMessage());
}
policyConfig.put(key, o);
}
}
return new PasswordPolicy(policyString, policyConfig);
public static Builder build() {
return new Builder();
}
private PasswordPolicy(String policyString, Map<String, Object> policyConfig) {
this.policyString = policyString;
public static PasswordPolicy parse(KeycloakSession session, String policyString) {
return new Builder(policyString).build(session);
}
private PasswordPolicy(Builder builder, Map<String, Object> policyConfig) {
this.builder = builder;
this.policyConfig = policyConfig;
}
@ -111,7 +83,7 @@ public class PasswordPolicy implements Serializable {
if (policyConfig.containsKey(HASH_ITERATIONS_ID)) {
return getPolicyConfig(HASH_ITERATIONS_ID);
} else {
return HASH_ITERATIONS_DEFAULT;
return -1;
}
}
@ -133,7 +105,117 @@ public class PasswordPolicy implements Serializable {
@Override
public String toString() {
return policyString;
return builder.asString();
}
public Builder toBuilder() {
return builder.clone();
}
public static class Builder {
private LinkedHashMap<String, String> map;
private Builder() {
this.map = new LinkedHashMap<>();
}
private Builder(LinkedHashMap<String, String> map) {
this.map = map;
}
private Builder(String policyString) {
map = new LinkedHashMap<>();
if (policyString != null && !policyString.trim().isEmpty()) {
for (String policy : policyString.split(" and ")) {
policy = policy.trim();
String key;
String config = null;
int i = policy.indexOf('(');
if (i == -1) {
key = policy.trim();
} else {
key = policy.substring(0, i).trim();
config = policy.substring(i + 1, policy.length() - 1);
}
map.put(key, config);
}
}
}
public boolean contains(String key) {
return map.containsKey(key);
}
public String get(String key) {
return map.get(key);
}
public Builder put(String key, String value) {
map.put(key, value);
return this;
}
public Builder remove(String key) {
map.remove(key);
return this;
}
public PasswordPolicy build(KeycloakSession session) {
Map<String, Object> config = new HashMap<>();
for (Map.Entry<String, String> e : map.entrySet()) {
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, e.getKey());
if (provider == null) {
throw new PasswordPolicyConfigException("Password policy not found");
}
Object o;
try {
o = provider.parseConfig(e.getValue());
} catch (PasswordPolicyConfigException ex) {
throw new ModelException("Invalid config for " + e.getKey() + ": " + ex.getMessage());
}
config.put(e.getKey(), o);
}
return new PasswordPolicy(this, config);
}
public String asString() {
if (map.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> e : map.entrySet()) {
if (first) {
first = false;
} else {
sb.append(" and ");
}
sb.append(e.getKey());
String c = e.getValue();
if (c != null && !c.trim().isEmpty()) {
sb.append("(");
sb.append(c);
sb.append(")");
}
}
return sb.toString();
}
public Builder clone() {
return new Builder((LinkedHashMap<String, String>) map.clone());
}
}
}

View file

@ -226,8 +226,6 @@ public class RealmManager {
realm.setLoginWithEmailAllowed(true);
realm.setEventsListeners(Collections.singleton("jboss-logging"));
realm.setPasswordPolicy(PasswordPolicy.parse(session, "hashIterations(20000)"));
}
public boolean removeRealm(RealmModel realm) {

View file

@ -31,7 +31,7 @@ public class LogChecker {
private static final Logger log = Logger.getLogger(LogChecker.class);
private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*" };
private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*", ".*org.keycloak.events.*" };
public static void checkServerLog(File logFile) throws IOException {
log.info(String.format("Checking server log: '%s'", logFile.getAbsolutePath()));

View file

@ -173,7 +173,7 @@ public class RealmTest extends AbstractAdminTest {
adminClient.realms().create(rep);
assertEquals("hashIterations(20000)", adminClient.realm("new-realm").toRepresentation().getPasswordPolicy());
assertEquals(null, adminClient.realm("new-realm").toRepresentation().getPasswordPolicy());
adminClient.realms().realm("new-realm").remove();

View file

@ -103,34 +103,38 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
@Test
public void testPasswordRehashedOnAlgorithmChanged() throws Exception {
setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ") and hashIterations(1)");
String username = "testPasswordRehashedOnAlgorithmChanged";
createUser(username);
CredentialModel credential = fetchCredentials(username);
assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm());
assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm());
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 1);
setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
setPasswordPolicy("hashAlgorithm(" + Pbkdf2PasswordHashProviderFactory.ID + ") and hashIterations(1)");
loginPage.open();
loginPage.login(username, "password");
credential = fetchCredentials(username);
assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm());
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm());
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1);
}
@Test
public void testPasswordRehashedOnIterationsChanged() throws Exception {
setPasswordPolicy("hashIterations(10000)");
String username = "testPasswordRehashedOnIterationsChanged";
createUser(username);
CredentialModel credential = fetchCredentials(username);
assertEquals(20000, credential.getHashIterations());
assertEquals(10000, credential.getHashIterations());
setPasswordPolicy("hashIterations(1)");
@ -140,7 +144,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
credential = fetchCredentials(username);
assertEquals(1, credential.getHashIterations());
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 1);
}
@Test
@ -153,6 +157,16 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000);
}
@Test
public void testDefault() throws Exception {
setPasswordPolicy("");
String username = "testDefault";
createUser(username);
CredentialModel credential = fetchCredentials(username);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 27500);
}
@Test
public void testPbkdf2Sha256() throws Exception {
setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
@ -160,7 +174,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
createUser(username);
CredentialModel credential = fetchCredentials(username);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 27500);
}
@Test
@ -170,7 +184,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
createUser(username);
CredentialModel credential = fetchCredentials(username);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 20000);
assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 30000);
}

View file

@ -59,6 +59,7 @@ import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.OAuthClient;
import static org.junit.Assert.assertNull;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
@ -132,6 +133,7 @@ public class MigrationTest extends AbstractKeycloakTest {
testMigrationTo2_5_0();
testMigrationTo2_5_1();
testMigrationTo3_0_0();
testMigrationTo3_2_0();
}
@Test
@ -210,7 +212,12 @@ public class MigrationTest extends AbstractKeycloakTest {
private void testMigrationTo3_0_0() {
testRoleManageAccountLinks(masterRealm, migrationRealm);
}
private void testMigrationTo3_2_0() {
assertNull(masterRealm.toRepresentation().getPasswordPolicy());
assertNull(migrationRealm.toRepresentation().getPasswordPolicy());
}
private void testRoleManageAccountLinks(RealmResource... realms) {
log.info("testing role manage account links");
for (RealmResource realm : realms) {

View file

@ -0,0 +1,266 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.policy;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.RealmBuilder;
import java.util.List;
import java.util.regex.PatternSyntaxException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PasswordPolicyTest extends AbstractKeycloakTest {
@Deployment
public static WebArchive deploy() {
return RunOnServerDeployment.create();
}
@Test
public void testLength() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length"));
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "1234567").getMessage());
Assert.assertArrayEquals(new Object[]{8}, policyManager.validate("jdoe", "1234567").getParameters());
assertNull(policyManager.validate("jdoe", "12345678"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(4)"));
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "123").getMessage());
Assert.assertArrayEquals(new Object[]{4}, policyManager.validate("jdoe", "123").getParameters());
assertNull(policyManager.validate("jdoe", "1234"));
});
}
@Test
public void testDigits() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits"));
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd").getParameters());
assertNull(policyManager.validate("jdoe", "abcd1"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits(2)"));
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd1").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abcd1").getParameters());
assertNull(policyManager.validate("jdoe", "abcd12"));
});
}
@Test
public void testLowerCase() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase"));
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABCD1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "ABCD1234").getParameters());
assertNull(policyManager.validate("jdoe", "ABcD1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase(2)"));
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABcD1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ABcD1234").getParameters());
assertNull(policyManager.validate("jdoe", "aBcD1234"));
});
}
@Test
public void testUpperCase() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase"));
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
assertNull(policyManager.validate("jdoe", "abCd1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase(2)"));
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abCd1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abCd1234").getParameters());
assertNull(policyManager.validate("jdoe", "AbCd1234"));
});
}
@Test
public void testSpecialChars() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars"));
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
assertNull(policyManager.validate("jdoe", "ab&d1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars(2)"));
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "ab&d1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ab&d1234").getParameters());
assertNull(policyManager.validate("jdoe", "ab&d-234"));
});
}
@Test
public void testNotUsername() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "notUsername"));
Assert.assertEquals("invalidPasswordNotUsernameMessage", policyManager.validate("jdoe", "jdoe").getMessage());
assertNull(policyManager.validate("jdoe", "ab&d1234"));
});
}
@Test
public void testInvalidPolicyName() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "noSuchPolicy"));
Assert.fail("Expected exception");
} catch (ModelException e) {
assertEquals("Password policy not found", e.getMessage());
}
});
}
@Test
public void testRegexPatterns() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
PasswordPolicy policy = null;
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern"));
fail("Expected NullPointerException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Config required", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*,**)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
//Fails to match one of the regex pattern
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe) and regexPattern(j*d)"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
////Fails to match all of the regex patterns
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe)"));
assertNull(policyManager.validate("jdoe", "jdoe"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
assertNull(policyManager.validate("jdoe", "jdoe0"));
});
}
@Test
public void testComplex() {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBB&"));
Assert.assertNotNull(policyManager.validate("jdoe", "aaaaBB&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12AABB&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aabb&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBBcc"));
Assert.assertNotNull(policyManager.validate("12aaBB&-", "12aaBB&-"));
assertNull(policyManager.validate("jdoe", "12aaBB&-"));
});
}
@Test
public void testBuilder() {
testingClient.server("passwordPolicy").run(session -> {
PasswordPolicy.Builder builder = PasswordPolicy.parse(session, "hashIterations(20000)").toBuilder();
assertFalse(builder.contains(PasswordPolicy.HASH_ALGORITHM_ID));
assertTrue("20000".equals(builder.get(PasswordPolicy.HASH_ITERATIONS_ID)));
builder.remove(PasswordPolicy.HASH_ITERATIONS_ID);
assertNull(builder.asString());
builder = PasswordPolicy.parse(session, "hashIterations(20000) and hashAlgorithm(pbkdf2)").toBuilder();
assertTrue(builder.contains(PasswordPolicy.HASH_ALGORITHM_ID));
builder = PasswordPolicy.parse(session, "hashIterations(20000) and length(100)").toBuilder();
builder.remove(PasswordPolicy.HASH_ITERATIONS_ID);
assertEquals("length(100)", builder.asString());
builder = PasswordPolicy.parse(session, "digits(10) and hashIterations(20000) and length(100)").toBuilder();
builder.remove(PasswordPolicy.HASH_ITERATIONS_ID);
assertEquals("digits(10) and length(100)", builder.asString());
});
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(RealmBuilder.create().name("passwordPolicy").build());
}
}

View file

@ -26,7 +26,7 @@ import org.junit.rules.TemporaryFolder;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory;
import org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@ -85,7 +85,7 @@ public class AddUserTest {
CredentialRepresentation credentials = user.getCredentials().get(0);
assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credentials.getAlgorithm());
assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credentials.getAlgorithm());
assertEquals(new Integer(100000), credentials.getHashIterations());
KeycloakServer server = new KeycloakServer();

View file

@ -1,188 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.model;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import java.util.regex.PatternSyntaxException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PasswordPolicyTest extends AbstractModelTest {
private RealmModel realmModel;
private PasswordPolicyManagerProvider policyManager;
@Before
public void before() throws Exception {
super.before();
realmModel = realmManager.createRealm("JUGGLER");
session.getContext().setRealm(realmModel);
policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
}
@Test
public void testLength() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length"));
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "1234567").getMessage());
Assert.assertArrayEquals(new Object[]{8}, policyManager.validate("jdoe", "1234567").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "12345678"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(4)"));
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "123").getMessage());
Assert.assertArrayEquals(new Object[]{4}, policyManager.validate("jdoe", "123").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "1234"));
}
@Test
public void testDigits() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits"));
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "abcd1"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits(2)"));
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd1").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abcd1").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "abcd12"));
}
@Test
public void testLowerCase() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase"));
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABCD1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "ABCD1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "ABcD1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase(2)"));
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABcD1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ABcD1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "aBcD1234"));
}
@Test
public void testUpperCase() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase"));
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "abCd1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase(2)"));
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abCd1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abCd1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "AbCd1234"));
}
@Test
public void testSpecialChars() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars"));
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars(2)"));
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "ab&d1234").getMessage());
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ab&d1234").getParameters());
Assert.assertNull(policyManager.validate("jdoe", "ab&d-234"));
}
@Test
public void testNotUsername() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "notUsername"));
Assert.assertEquals("invalidPasswordNotUsernameMessage", policyManager.validate("jdoe", "jdoe").getMessage());
Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
}
@Test
public void testInvalidPolicyName() {
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "noSuchPolicy"));
Assert.fail("Expected exception");
} catch (ModelException e) {
assertEquals("Password policy not found", e.getMessage());
}
}
@Test
public void testRegexPatterns() {
PasswordPolicy policy = null;
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern"));
fail("Expected NullPointerException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Config required", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*,**)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (ModelException e) {
assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
//Fails to match one of the regex pattern
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe) and regexPattern(j*d)"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
////Fails to match all of the regex patterns
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe)"));
Assert.assertNull(policyManager.validate("jdoe", "jdoe"));
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
Assert.assertNull(policyManager.validate("jdoe", "jdoe0"));
}
@Test
public void testComplex() {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBB&"));
Assert.assertNotNull(policyManager.validate("jdoe", "aaaaBB&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12AABB&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aabb&-"));
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBBcc"));
Assert.assertNotNull(policyManager.validate("12aaBB&-", "12aaBB&-"));
Assert.assertNull(policyManager.validate("jdoe", "12aaBB&-"));
}
}