KEYCLOAK-4266 MSAD: User is disabled after registration
This commit is contained in:
parent
536b88790e
commit
3444fb62f1
8 changed files with 418 additions and 95 deletions
|
@ -92,6 +92,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
protected LDAPProviderKerberosConfig kerberosConfig;
|
||||||
protected PasswordUpdateCallback updater;
|
protected PasswordUpdateCallback updater;
|
||||||
protected LDAPStorageMapperManager mapperManager;
|
protected LDAPStorageMapperManager mapperManager;
|
||||||
|
protected LDAPStorageUserManager userManager;
|
||||||
|
|
||||||
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
||||||
|
|
||||||
|
@ -103,6 +104,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
|
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
|
||||||
this.editMode = ldapIdentityStore.getConfig().getEditMode();
|
this.editMode = ldapIdentityStore.getConfig().getEditMode();
|
||||||
this.mapperManager = new LDAPStorageMapperManager(this);
|
this.mapperManager = new LDAPStorageMapperManager(this);
|
||||||
|
this.userManager = new LDAPStorageUserManager(this);
|
||||||
|
|
||||||
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
|
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
|
||||||
if (kerberosConfig.isAllowKerberosAuthentication()) {
|
if (kerberosConfig.isAllowKerberosAuthentication()) {
|
||||||
|
@ -134,6 +136,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
return mapperManager;
|
return mapperManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LDAPStorageUserManager getUserManager() {
|
||||||
|
return userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel validate(RealmModel realm, UserModel local) {
|
public UserModel validate(RealmModel realm, UserModel local) {
|
||||||
LDAPObject ldapObject = loadAndValidateUser(realm, local);
|
LDAPObject ldapObject = loadAndValidateUser(realm, local);
|
||||||
|
@ -145,6 +152,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
|
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
|
||||||
|
UserModel existing = userManager.getManagedProxiedUser(local.getId());
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
UserModel proxied = local;
|
UserModel proxied = local;
|
||||||
|
|
||||||
checkDNChanged(realm, local, ldapObject);
|
checkDNChanged(realm, local, ldapObject);
|
||||||
|
@ -167,6 +179,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
proxied = ldapMapper.proxy(ldapObject, proxied, realm);
|
proxied = ldapMapper.proxy(ldapObject, proxied, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userManager.setManagedProxiedUser(proxied, ldapObject);
|
||||||
|
|
||||||
return proxied;
|
return proxied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +241,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapIdentityStore.remove(ldapObject);
|
ldapIdentityStore.remove(ldapObject);
|
||||||
|
userManager.removeManagedUserEntry(user.getId());
|
||||||
|
|
||||||
return true;
|
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
|
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
|
||||||
*/
|
*/
|
||||||
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
|
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
|
||||||
|
LDAPObject existing = userManager.getManagedLDAPUser(local.getId());
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
|
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
|
||||||
if (ldapUser == null) {
|
if (ldapUser == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.keycloak.storage.ldap.mappers;
|
package org.keycloak.storage.ldap.mappers;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.UserModelDelegate;
|
import org.keycloak.models.utils.UserModelDelegate;
|
||||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||||
|
@ -33,39 +32,16 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
|
||||||
|
|
||||||
protected LDAPStorageProvider provider;
|
protected LDAPStorageProvider provider;
|
||||||
protected LDAPObject ldapUser;
|
protected LDAPObject ldapUser;
|
||||||
private final LDAPTransaction transaction;
|
|
||||||
|
|
||||||
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapUser) {
|
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapUser) {
|
||||||
super(delegate);
|
super(delegate);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.ldapUser = ldapUser;
|
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() {
|
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()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.AbstractLDAPStorageMapper;
|
||||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||||
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
|
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
|
||||||
|
import org.keycloak.storage.ldap.mappers.TxAwareLDAPUserModelDelegate;
|
||||||
|
|
||||||
import javax.naming.AuthenticationException;
|
import javax.naming.AuthenticationException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -101,7 +102,7 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
||||||
control.remove(UserAccountControl.ACCOUNTDISABLE);
|
control.remove(UserAccountControl.ACCOUNTDISABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserAccountControl(ldapUser, control);
|
updateUserAccountControl(true, ldapUser, control);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,23 +188,26 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
||||||
return new UserAccountControl(longValue);
|
return new UserAccountControl(longValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user in LDAP
|
// 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(LDAPObject ldapUser, UserAccountControl accountControl) {
|
protected void updateUserAccountControl(boolean updateInLDAP, LDAPObject ldapUser, UserAccountControl accountControl) {
|
||||||
String userAccountControlValue = String.valueOf(accountControl.getValue());
|
String userAccountControlValue = String.valueOf(accountControl.getValue());
|
||||||
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
|
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
|
||||||
|
|
||||||
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
|
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
|
||||||
|
|
||||||
|
if (updateInLDAP) {
|
||||||
ldapProvider.getLdapIdentityStore().update(ldapUser);
|
ldapProvider.getLdapIdentityStore().update(ldapUser);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class MSADUserModelDelegate extends UserModelDelegate {
|
public class MSADUserModelDelegate extends TxAwareLDAPUserModelDelegate {
|
||||||
|
|
||||||
private final LDAPObject ldapUser;
|
private final LDAPObject ldapUser;
|
||||||
|
|
||||||
public MSADUserModelDelegate(UserModel delegate, LDAPObject ldapUser) {
|
public MSADUserModelDelegate(UserModel delegate, LDAPObject ldapUser) {
|
||||||
super(delegate);
|
super(delegate, ldapProvider, ldapUser);
|
||||||
this.ldapUser = ldapUser;
|
this.ldapUser = ldapUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +239,9 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
||||||
control.add(UserAccountControl.ACCOUNTDISABLE);
|
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.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
|
||||||
|
|
||||||
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "0");
|
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.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
|
||||||
|
|
||||||
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
|
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
|
||||||
ldapProvider.getLdapIdentityStore().update(ldapUser);
|
|
||||||
|
ensureTransactionStarted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStor
|
||||||
return ProviderConfigurationBuilder.create()
|
return ProviderConfigurationBuilder.create()
|
||||||
.property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
|
.property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
|
||||||
.label("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.")
|
"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)
|
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||||
.defaultValue("false")
|
.defaultValue("false")
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.federation.storage.ldap;
|
package org.keycloak.testsuite.federation.storage.ldap;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.FixMethodOrder;
|
import org.junit.FixMethodOrder;
|
||||||
|
@ -74,6 +75,8 @@ import static org.junit.Assert.assertEquals;
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class LDAPProvidersIntegrationTest {
|
public class LDAPProvidersIntegrationTest {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(LDAPProvidersIntegrationTest.class);
|
||||||
|
|
||||||
private static LDAPRule ldapRule = new LDAPRule();
|
private static LDAPRule ldapRule = new LDAPRule();
|
||||||
|
|
||||||
private static ComponentModel ldapModel = null;
|
private static ComponentModel ldapModel = null;
|
||||||
|
@ -388,6 +391,10 @@ public class LDAPProvidersIntegrationTest {
|
||||||
Assert.assertNotNull(user);
|
Assert.assertNotNull(user);
|
||||||
Assert.assertNotNull(user.getFederationLink());
|
Assert.assertNotNull(user.getFederationLink());
|
||||||
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
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 {
|
} finally {
|
||||||
keycloakRule.stopSession(session, false);
|
keycloakRule.stopSession(session, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue