Merge pull request #4559 from micedre/KEYCLOAK-4052bis

KEYCLOAK-4052 - add an option to validate Password Policy for ldap user storage
This commit is contained in:
Bill Burke 2017-10-13 18:44:57 -04:00 committed by GitHub
commit c66ce3a209
9 changed files with 66 additions and 9 deletions

View file

@ -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);
}

View file

@ -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) {

View file

@ -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")

View file

@ -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;

View file

@ -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");
}

View file

@ -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 {

View file

@ -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"]
}
}

View file

@ -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

View file

@ -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>
<kc-menu></kc-menu>