keycloak-developers
Changes
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java 8(+8 -0)
Details
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
index d664035..c2b3eb2 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
@@ -105,6 +105,11 @@ public class LDAPConfig {
return vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
}
+ public boolean isValidatePasswordPolicy() {
+ String validatePPolicy = config.getFirst(LDAPConstants.VALIDATE_PASSWORD_POLICY);
+ return Boolean.parseBoolean(validatePPolicy);
+ }
+
public String getConnectionPooling() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING);
}
@@ -137,7 +142,7 @@ public class LDAPConfig {
return uuidAttrName;
}
-
+
public boolean isObjectGUID() {
return getUuidLDAPAttributeName().equalsIgnoreCase(LDAPConstants.OBJECT_GUID);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
index e77df53..69c513d 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
@@ -44,6 +44,9 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
+import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
+import org.keycloak.policy.PasswordPolicyManagerProvider;
+import org.keycloak.policy.PolicyError;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@@ -533,7 +536,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
// Check here if user already exists
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
UserModel user = session.userLocalStorage().getUserByUsername(ldapUsername, realm);
-
+
if (user != null) {
LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
// If email attribute mapper is set to "Always Read Value From LDAP" the user may be in Keycloak DB with an old email address
@@ -599,7 +602,10 @@ public class LDAPStorageProvider implements UserStorageProvider,
PasswordUserCredentialModel cred = (PasswordUserCredentialModel)input;
String password = cred.getValue();
LDAPObject ldapUser = loadAndValidateUser(realm, user);
-
+ if (ldapIdentityStore.getConfig().isValidatePasswordPolicy()) {
+ PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(realm, user, password);
+ if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
+ }
try {
LDAPOperationDecorator operationDecorator = null;
if (updater != null) {
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index 0d4c07b..77029c0 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -142,6 +142,10 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("1")
.add()
+ .property().name(LDAPConstants.VALIDATE_PASSWORD_POLICY)
+ .type(ProviderConfigProperty.BOOLEAN_TYPE)
+ .defaultValue("false")
+ .add()
.property().name(LDAPConstants.USE_TRUSTSTORE_SPI)
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("ldapsOnly")
diff --git a/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java b/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
index 7f722bc..75bcac7 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -63,6 +63,8 @@ public class LDAPConstants {
public static final String EDIT_MODE = "editMode";
+ public static final String VALIDATE_PASSWORD_POLICY = "validatePasswordPolicy";
+
// Count of users processed per single transaction during sync process
public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
index 37b9d72..4279252 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
@@ -436,6 +436,14 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
}
+ protected void updateProviderValidatePasswordPolicy(Boolean validatePasswordPolicy) {
+ List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+ Assert.assertEquals(1, reps.size());
+ ComponentRepresentation kerberosProvider = reps.get(0);
+ kerberosProvider.getConfig().putSingle(LDAPConstants.VALIDATE_PASSWORD_POLICY, validatePasswordPolicy.toString());
+ testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+ }
+
public RealmResource testRealmResource() {
return adminClient.realm("test");
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosLdapTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosLdapTest.java
index 5d5eb73..3cdddc6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosLdapTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosLdapTest.java
@@ -17,19 +17,24 @@
package org.keycloak.testsuite.federation.kerberos;
+import java.io.File;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.events.Details;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
@@ -75,7 +80,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
protected boolean isCaseSensitiveLogin() {
return kerberosRule.isCaseSensitiveLogin();
}
-
+
@Override
protected boolean isStartEmbeddedLdapServer() {
return kerberosRule.isStartEmbeddedLdapServer();
@@ -96,6 +101,25 @@ public class KerberosLdapTest extends AbstractKerberosTest {
assertUser("hnelson", "hnelson@keycloak.org", "Horatio", "Nelson", false);
}
+ @Test
+ public void validatePasswordPolicyTest() throws Exception{
+ updateProviderEditMode(UserStorageProvider.EditMode.WRITABLE);
+
+ changePasswordPage.open();
+ loginPage.login("jduke", "theduke");
+
+ updateProviderValidatePasswordPolicy(true);
+ changePasswordPage.changePassword("theduke", "jduke", "jduke");
+ Assert.assertTrue(driver.getPageSource().contains("Invalid"));
+
+ updateProviderValidatePasswordPolicy(false);
+ changePasswordPage.changePassword("theduke", "jduke", "jduke");
+ Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
+
+ // Change password back
+ changePasswordPage.open();
+ changePasswordPage.changePassword("jduke", "theduke", "theduke");
+ }
@Test
public void writableEditModeTest() throws Exception {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/kerberosrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/kerberosrealm.json
index b48faf1..2588e4a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/kerberosrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/kerberosrealm.json
@@ -8,6 +8,7 @@
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password", "kerberos" ],
+ "passwordPolicy": "notUsername(undefined)",
"defaultRoles": [ "user" ],
"users" : [
{
@@ -42,7 +43,7 @@
],
"secret": "password"
}
- ],
+ ],
"roles" : {
"realm" : [
{
@@ -52,4 +53,4 @@
]
},
"eventsListeners": ["jboss-logging", "event-queue"]
-}
\ No newline at end of file
+}
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 3fa92bb..09a8c3f 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -808,11 +808,13 @@ search-scope=Search Scope
ldap.search-scope.tooltip=For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details
use-truststore-spi=Use Truststore SPI
ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
+validate-password-policy=Validate Password Policy
connection-pooling=Connection Pooling
ldap-connection-timeout=Connection Timeout
ldap.connection-timeout.tooltip=LDAP Connection Timeout in milliseconds
ldap-read-timeout=Read Timeout
ldap.read-timeout.tooltip=LDAP Read Timeout in milliseconds. This timeout applies for LDAP read operations
+ldap.validate-password-policy.tooltip=Does Keycloak should validate the password with the realm password policy before updating it
ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server
ldap.pagination.tooltip=Does the LDAP server support pagination.
kerberos-integration=Kerberos Integration
@@ -1367,5 +1369,3 @@ map-roles-authz-users-scope-description=Policies that decide if admin can map ro
user-impersonated-authz-users-scope-description=Policies that decide which users can be impersonated. These policies are applied to the user being impersonated.
manage-membership-authz-group-scope-description=Policies that decide if admin can add or remove users from this group
manage-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
-
-
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html
index 321d047..9473450 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html
@@ -173,6 +173,13 @@
</div>
<kc-tooltip>{{:: 'ldap.search-scope.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="validatePasswordPolicy">{{:: 'validate-password-policy' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="instance.config['validatePasswordPolicy'][0]" name="validatePasswordPolicy" id="validatePasswordPolicy" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'ldap.validate-password-policy.tooltip' | translate}}</kc-tooltip>
+ </div>
<div class="form-group">
<label class="col-md-2 control-label" for="useTruststoreSpi">{{:: 'use-truststore-spi' | translate}}</label>
<div class="col-md-6">
@@ -469,4 +476,4 @@
</form>
</div>
-<kc-menu></kc-menu>
\ No newline at end of file
+<kc-menu></kc-menu>