keycloak-uncached
Changes
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java 26(+17 -9)
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java 2(+1 -1)
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java 87(+2 -85)
Details
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 8edb6ca..7e112b1 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
@@ -92,6 +92,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
protected LDAPProviderKerberosConfig kerberosConfig;
protected PasswordUpdateCallback updater;
protected LDAPStorageMapperManager mapperManager;
+ protected LDAPStorageUserManager userManager;
protected final Set<String> supportedCredentialTypes = new HashSet<>();
@@ -103,6 +104,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
this.editMode = ldapIdentityStore.getConfig().getEditMode();
this.mapperManager = new LDAPStorageMapperManager(this);
+ this.userManager = new LDAPStorageUserManager(this);
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
if (kerberosConfig.isAllowKerberosAuthentication()) {
@@ -134,6 +136,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
return mapperManager;
}
+ public LDAPStorageUserManager getUserManager() {
+ return userManager;
+ }
+
+
@Override
public UserModel validate(RealmModel realm, UserModel local) {
LDAPObject ldapObject = loadAndValidateUser(realm, local);
@@ -145,6 +152,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
}
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
+ UserModel existing = userManager.getManagedProxiedUser(local.getId());
+ if (existing != null) {
+ return existing;
+ }
+
UserModel proxied = local;
checkDNChanged(realm, local, ldapObject);
@@ -167,6 +179,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
proxied = ldapMapper.proxy(ldapObject, proxied, realm);
}
+ userManager.setManagedProxiedUser(proxied, ldapObject);
+
return proxied;
}
@@ -227,6 +241,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
}
ldapIdentityStore.remove(ldapObject);
+ userManager.removeManagedUserEntry(user.getId());
+
return true;
}
@@ -385,6 +401,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
+ LDAPObject existing = userManager.getManagedLDAPUser(local.getId());
+ if (existing != null) {
+ return existing;
+ }
+
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
if (ldapUser == null) {
return null;
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageUserManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageUserManager.java
new file mode 100644
index 0000000..5155c12
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageUserManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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.storage.ldap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.LDAPTransaction;
+
+/**
+ * Track which LDAP users were already enlisted during this transaction
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPStorageUserManager {
+
+ private final Map<String, ManagedUserEntry> managedUsers = new HashMap<>();
+ private final LDAPStorageProvider provider;
+
+ public LDAPStorageUserManager(LDAPStorageProvider provider) {
+ this.provider = provider;
+ }
+
+ public UserModel getManagedProxiedUser(String userId) {
+ ManagedUserEntry entry = managedUsers.get(userId);
+ return entry==null ? null : entry.getManagedProxiedUser();
+ }
+
+ public LDAPObject getManagedLDAPUser(String userId) {
+ ManagedUserEntry entry = managedUsers.get(userId);
+ return entry==null ? null : entry.getLdapUser();
+ }
+
+ public LDAPTransaction getTransaction(String userId) {
+ ManagedUserEntry entry = managedUsers.get(userId);
+ if (entry == null) {
+ throw new IllegalStateException("Shouldn't happen to not have entry for userId: " + userId);
+ }
+
+ return entry.getLdapTransaction();
+
+ }
+
+ public void setManagedProxiedUser(UserModel proxiedUser, LDAPObject ldapObject) {
+ String userId = proxiedUser.getId();
+ ManagedUserEntry entry = managedUsers.get(userId);
+ if (entry != null) {
+ throw new IllegalStateException("Don't expect to have entry for user " + userId);
+ }
+
+ LDAPTransaction ldapTransaction = new LDAPTransaction(provider, ldapObject);
+ ManagedUserEntry newEntry = new ManagedUserEntry(proxiedUser, ldapObject, ldapTransaction);
+ managedUsers.put(userId, newEntry);
+ }
+
+ public void removeManagedUserEntry(String userId) {
+ managedUsers.remove(userId);
+ }
+
+
+
+ private static class ManagedUserEntry {
+
+ private final UserModel managedProxiedUser;
+ private final LDAPObject ldapUser;
+ private final LDAPTransaction ldapTransaction;
+
+ public ManagedUserEntry(UserModel managedProxiedUser, LDAPObject ldapUser, LDAPTransaction ldapTransaction) {
+ this.managedProxiedUser = managedProxiedUser;
+ this.ldapUser = ldapUser;
+ this.ldapTransaction = ldapTransaction;
+ }
+
+ public UserModel getManagedProxiedUser() {
+ return managedProxiedUser;
+ }
+
+ public LDAPObject getLdapUser() {
+ return ldapUser;
+ }
+
+ public LDAPTransaction getLdapTransaction() {
+ return ldapTransaction;
+ }
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPTransaction.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPTransaction.java
new file mode 100644
index 0000000..1f2473b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPTransaction.java
@@ -0,0 +1,95 @@
+/*
+ * 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.storage.ldap.mappers;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPTransaction implements KeycloakTransaction {
+
+ public static final Logger logger = Logger.getLogger(LDAPTransaction.class);
+
+ protected TransactionState state = TransactionState.NOT_STARTED;
+
+ private final LDAPStorageProvider ldapProvider;
+ private final LDAPObject ldapUser;
+
+ public LDAPTransaction(LDAPStorageProvider ldapProvider, LDAPObject ldapUser) {
+ this.ldapProvider = ldapProvider;
+ this.ldapUser = ldapUser;
+ }
+
+ @Override
+ public void begin() {
+ if (state != TransactionState.NOT_STARTED) {
+ throw new IllegalStateException("Transaction already started");
+ }
+
+ state = TransactionState.STARTED;
+ }
+
+ @Override
+ public void commit() {
+ if (state != TransactionState.STARTED) {
+ throw new IllegalStateException("Transaction in illegal state for commit: " + state);
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
+ }
+
+ ldapProvider.getLdapIdentityStore().update(ldapUser);
+ state = TransactionState.FINISHED;
+ }
+
+ @Override
+ public void rollback() {
+ if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
+ throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
+ }
+
+ logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
+ state = TransactionState.FINISHED;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ state = TransactionState.ROLLBACK_ONLY;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return state == TransactionState.ROLLBACK_ONLY;
+ }
+
+ @Override
+ public boolean isActive() {
+ return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
+ }
+
+
+ protected enum TransactionState {
+ NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
+ }
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
index 4b926bb..2bbe839 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
@@ -33,6 +33,7 @@ import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
+import org.keycloak.storage.ldap.mappers.TxAwareLDAPUserModelDelegate;
import javax.naming.AuthenticationException;
import java.util.HashSet;
@@ -101,7 +102,7 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
control.remove(UserAccountControl.ACCOUNTDISABLE);
}
- updateUserAccountControl(ldapUser, control);
+ updateUserAccountControl(true, ldapUser, control);
}
@Override
@@ -187,23 +188,26 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
return new UserAccountControl(longValue);
}
- // Update user in LDAP
- protected void updateUserAccountControl(LDAPObject ldapUser, UserAccountControl accountControl) {
+ // Update user in LDAP if "updateInLDAP" is true. Otherwise it is assumed that LDAP update will be called at the end of transaction
+ protected void updateUserAccountControl(boolean updateInLDAP, LDAPObject ldapUser, UserAccountControl accountControl) {
String userAccountControlValue = String.valueOf(accountControl.getValue());
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
- ldapProvider.getLdapIdentityStore().update(ldapUser);
+
+ if (updateInLDAP) {
+ ldapProvider.getLdapIdentityStore().update(ldapUser);
+ }
}
- public class MSADUserModelDelegate extends UserModelDelegate {
+ public class MSADUserModelDelegate extends TxAwareLDAPUserModelDelegate {
private final LDAPObject ldapUser;
public MSADUserModelDelegate(UserModel delegate, LDAPObject ldapUser) {
- super(delegate);
+ super(delegate, ldapProvider, ldapUser);
this.ldapUser = ldapUser;
}
@@ -235,7 +239,9 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
control.add(UserAccountControl.ACCOUNTDISABLE);
}
- updateUserAccountControl(ldapUser, control);
+ ensureTransactionStarted();
+
+ updateUserAccountControl(false, ldapUser, control);
}
}
@@ -257,7 +263,8 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "0");
- ldapProvider.getLdapIdentityStore().update(ldapUser);
+
+ ensureTransactionStarted();
}
}
@@ -283,7 +290,8 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
- ldapProvider.getLdapIdentityStore().update(ldapUser);
+
+ ensureTransactionStarted();
}
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
index 0eac7ae..81566f7 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
@@ -48,7 +48,7 @@ public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStor
return ProviderConfigurationBuilder.create()
.property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
.label("Password Policy Hints Enabled")
- .helpText("Applicable just for writable MSAD. If on, then updating password in MSAD will use LDAP_SERVER_POLICY_HINTS_OID " +
+ .helpText("Applicable just for writable MSAD. If on, then updating password of MSAD user will use LDAP_SERVER_POLICY_HINTS_OID " +
"extension, which means that advanced MSAD password policies like 'password history' or 'minimal password age' will be applied. This extension works just for MSAD 2008 R2 or newer.")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java
index 4fc5caf..2bf88f2 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java
@@ -18,7 +18,6 @@
package org.keycloak.storage.ldap.mappers;
import org.jboss.logging.Logger;
-import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.storage.ldap.LDAPStorageProvider;
@@ -33,39 +32,16 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
protected LDAPStorageProvider provider;
protected LDAPObject ldapUser;
- private final LDAPTransaction transaction;
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapUser) {
super(delegate);
this.provider = provider;
this.ldapUser = ldapUser;
- this.transaction = findOrCreateTransaction();
- }
-
- public LDAPTransaction getTransaction() {
- return transaction;
- }
-
- // Try to find transaction in any delegate. We want to enlist just single transaction per all delegates
- private LDAPTransaction findOrCreateTransaction() {
- UserModelDelegate delegate = this;
- while (true) {
- UserModel deleg = delegate.getDelegate();
- if (!(deleg instanceof UserModelDelegate)) {
- return new LDAPTransaction();
- } else {
- delegate = (UserModelDelegate) deleg;
- }
-
- if (delegate instanceof TxAwareLDAPUserModelDelegate) {
- TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
- return txDelegate.getTransaction();
- }
- }
}
protected void ensureTransactionStarted() {
- if (transaction.state == TransactionState.NOT_STARTED) {
+ LDAPTransaction transaction = provider.getUserManager().getTransaction(getId());
+ if (transaction.state == LDAPTransaction.TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) {
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
}
@@ -74,63 +50,4 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
}
}
-
-
- protected class LDAPTransaction implements KeycloakTransaction {
-
- protected TransactionState state = TransactionState.NOT_STARTED;
-
- @Override
- public void begin() {
- if (state != TransactionState.NOT_STARTED) {
- throw new IllegalStateException("Transaction already started");
- }
-
- state = TransactionState.STARTED;
- }
-
- @Override
- public void commit() {
- if (state != TransactionState.STARTED) {
- throw new IllegalStateException("Transaction in illegal state for commit: " + state);
- }
-
- if (logger.isTraceEnabled()) {
- logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
- }
-
- provider.getLdapIdentityStore().update(ldapUser);
- state = TransactionState.FINISHED;
- }
-
- @Override
- public void rollback() {
- if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
- throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
- }
-
- logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
- state = TransactionState.FINISHED;
- }
-
- @Override
- public void setRollbackOnly() {
- state = TransactionState.ROLLBACK_ONLY;
- }
-
- @Override
- public boolean getRollbackOnly() {
- return state == TransactionState.ROLLBACK_ONLY;
- }
-
- @Override
- public boolean isActive() {
- return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
- }
- }
-
- protected enum TransactionState {
- NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
- }
-
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
index 4ea8ed7..1524981 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.federation.storage.ldap;
+import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
@@ -74,6 +75,8 @@ import static org.junit.Assert.assertEquals;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPProvidersIntegrationTest {
+ private static final Logger log = Logger.getLogger(LDAPProvidersIntegrationTest.class);
+
private static LDAPRule ldapRule = new LDAPRule();
private static ComponentModel ldapModel = null;
@@ -388,6 +391,10 @@ public class LDAPProvidersIntegrationTest {
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
+ Assert.assertEquals("registerusersuccess2", user.getUsername());
+ Assert.assertEquals("firstName", user.getFirstName());
+ Assert.assertEquals("lastName", user.getLastName());
+ Assert.assertTrue(user.isEnabled());
} finally {
keycloakRule.stopSession(session, false);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADMapperTest.java
new file mode 100644
index 0000000..646e039
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADMapperTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.federation.storage.ldap;
+
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class MSADMapperTest {
+
+ // Run this test just on MSAD
+ private static LDAPRule ldapRule = new LDAPRule((Map<String, String> ldapConfig) -> {
+
+ String vendor = ldapConfig.get(LDAPConstants.VENDOR);
+ return !(vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY));
+
+ });
+
+
+ private static ComponentModel ldapModel = null;
+
+
+ private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+ ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
+ UserStorageProviderModel model = new UserStorageProviderModel();
+ model.setLastSync(0);
+ model.setChangedSyncPeriod(-1);
+ model.setFullSyncPeriod(-1);
+ model.setName("test-ldap");
+ model.setPriority(0);
+ model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
+ model.setConfig(ldapConfig);
+
+ ldapModel = appRealm.addComponentModel(model);
+ LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
+ // Delete all LDAP users and add some new for testing
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+ LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+ appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected AppPage appPage;
+
+ @WebResource
+ protected RegisterPage registerPage;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @WebResource
+ protected AccountUpdateProfilePage profilePage;
+
+ @WebResource
+ protected AccountPasswordPage changePasswordPage;
+
+ @WebResource
+ protected LoginPasswordUpdatePage passwordUpdatePage;
+
+
+ @Test
+ public void test01RegisterUserWithWeakPasswordFirst() {
+ loginPage.open();
+ loginPage.clickRegister();
+ registerPage.assertCurrent();
+
+ // Weak password. This will fail to update password to MSAD due to password policy.
+ registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "password", "password");
+
+ // Another weak password
+ passwordUpdatePage.assertCurrent();
+ passwordUpdatePage.changePassword("pass", "pass");
+ Assert.assertEquals("Invalid password: new password doesn't match password policies.", passwordUpdatePage.getError());
+
+ // Strong password. Successfully update password and being redirected to the app
+ passwordUpdatePage.changePassword("Password1", "Password1");
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+ UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
+ Assert.assertNotNull(user);
+ Assert.assertNotNull(user.getFederationLink());
+ Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
+ Assert.assertEquals("registerusersuccess2", user.getUsername());
+ Assert.assertEquals("firstName", user.getFirstName());
+ Assert.assertEquals("lastName", user.getLastName());
+ Assert.assertTrue(user.isEnabled());
+ Assert.assertEquals(0, user.getRequiredActions().size());
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+}