Skip to content

Commit

Permalink
Delay LDAPObject creation until mandatory attributes are set (keycloa…
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartinc authored Sep 16, 2022
1 parent 2252c65 commit cc9326f
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObje

private void checkDNChanged(RealmModel realm, UserModel local, LDAPObject ldapObject) {
String dnFromDB = local.getFirstAttribute(LDAPConstants.LDAP_ENTRY_DN);
String ldapDn = ldapObject.getDn().toString();
if (!ldapDn.equals(dnFromDB)) {
String ldapDn = ldapObject.getDn() == null? null : ldapObject.getDn().toString();
if (ldapDn != null && !ldapDn.equals(dnFromDB)) {
logger.debugf("Updated LDAP DN of user '%s' to '%s'", local.getUsername(), ldapDn);
local.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapDn);

Expand Down Expand Up @@ -284,18 +284,19 @@ public UserModel addUser(RealmModel realm, String username) {
if (!synchronizeRegistrations()) {
return null;
}
UserModel user = null;
final UserModel user;
if (model.isImportEnabled()) {
user = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, username);
user.setFederationLink(model.getId());
} else {
user = new InMemoryUserAdapter(session, realm, new StorageId(model.getId(), username).getId());
user.setUsername(username);
}
LDAPObject ldapUser = LDAPUtils.addUserToLDAP(this, realm, user);
LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getDn().toString());
LDAPObject ldapUser = LDAPUtils.addUserToLDAP(this, realm, user, ldapObject -> {
LDAPUtils.checkUuid(ldapObject, ldapIdentityStore.getConfig());
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
});

// Add the user to the default groups and add default required actions
UserModel proxy = proxy(realm, user, ldapUser, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import javax.naming.directory.SearchControls;
Expand Down Expand Up @@ -56,12 +58,34 @@
public class LDAPUtils {

/**
* @param ldapProvider
* @param realm
* @param user
* @return newly created LDAPObject with all the attributes, uuid and DN properly set
* Method to crate a user in the LDAP. The user will be created when all
* mandatory attributes specified by the mappers are set. The method
* onRegisterUserToLDAP is first called in each mapper to set any default or
* initial value.
*
* @param ldapProvider The ldap provider
* @param realm The realm of the user
* @param user The user model
* @return The LDAPObject created or to be created when mandatory attributes are filled
*/
public static LDAPObject addUserToLDAP(LDAPStorageProvider ldapProvider, RealmModel realm, UserModel user) {
return addUserToLDAP(ldapProvider, realm, user, null);
}

/**
* Method that creates a user in the LDAP when all the attributes marked as
* mandatory by the mappers are set. The method onRegisterUserToLDAP is
* first called in each mapper to set any default or initial value. When
* the user is finally created the passed consumerOnCreated parameter is
* executed (can be null).
*
* @param ldapProvider The ldap provider
* @param realm The realm of the user
* @param user The user model
* @param consumerOnCreated The consumer to execute when the user is created
* @return The LDAPObject created or to be created when mandatory attributes are filled
*/
public static LDAPObject addUserToLDAP(LDAPStorageProvider ldapProvider, RealmModel realm, UserModel user, Consumer<LDAPObject> consumerOnCreated) {
LDAPObject ldapUser = new LDAPObject();

LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
Expand All @@ -70,15 +94,25 @@ public static LDAPObject addUserToLDAP(LDAPStorageProvider ldapProvider, RealmMo
ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());

LDAPMappersComparator ldapMappersComparator = new LDAPMappersComparator(ldapConfig);
realm.getComponentsStream(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName())
Set<String> mandatoryAttrs = realm.getComponentsStream(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName())
.sorted(ldapMappersComparator.sortAsc())
.forEachOrdered(mapperModel -> {
.map(mapperModel -> {
LDAPStorageMapper ldapMapper = ldapProvider.getMapperManager().getMapper(mapperModel);
ldapMapper.onRegisterUserToLDAP(ldapUser, user, realm);
});

LDAPUtils.computeAndSetDn(ldapConfig, ldapUser);
ldapStore.add(ldapUser);
return ldapMapper.mandatoryAttributeNames();
})
.filter(Objects::nonNull)
.flatMap(Set::stream)
.collect(Collectors.toSet());
mandatoryAttrs.add(ldapConfig.getRdnLdapAttribute());

ldapUser.executeOnMandatoryAttributesComplete(mandatoryAttrs, ldapObject -> {
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
ldapStore.add(ldapObject);
if (consumerOnCreated != null) {
consumerOnCreated.accept(ldapObject);
}
});
return ldapUser;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

import org.jboss.logging.Logger;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
Expand Down Expand Up @@ -54,6 +54,41 @@ public class LDAPObject {
// range attributes are always read from 0 to max so just saving the top value
private final Map<String, Integer> rangedAttributes = new HashMap<>();

// consumer to be executed when mandatory attributes are set
private Consumer<LDAPObject> consumerOnMandatoryAttributesComplete;

// mandatory attributes defined for the entry
private Set<String> mandatoryAttributeNames;

// mandatory attributes that remain not set
private Set<String> mandatoryAttributeNamesRemaining;

public void executeOnMandatoryAttributesComplete(Set<String> mandatoryAttributeNames, Consumer<LDAPObject> consumer) {
this.consumerOnMandatoryAttributesComplete = consumer;
this.mandatoryAttributeNames = new LinkedHashSet<>();
this.mandatoryAttributeNamesRemaining = new LinkedHashSet<>();
// initializes mandatory attributes
if (mandatoryAttributeNames != null) {
for (String name : mandatoryAttributeNames) {
name = name.toLowerCase();
this.mandatoryAttributeNames.add(name);
Set<String> values = lowerCasedAttributes.get(name);
if (values == null || values.isEmpty()) {
this.mandatoryAttributeNamesRemaining.add(name);
}
}
}
executeConsumerOnMandatoryAttributesComplete();
}

public boolean isWaitingForExecutionOnMandatoryAttributesComplete() {
return consumerOnMandatoryAttributesComplete != null;
}

public Set<String> getMandatoryAttributeNamesRemaining() {
return mandatoryAttributeNamesRemaining;
}

public String getUuid() {
return uuid;
}
Expand Down Expand Up @@ -114,13 +149,24 @@ public void addRdnAttributeName(String rdnAttributeName) {

public void setSingleAttribute(String attributeName, String attributeValue) {
Set<String> asSet = new LinkedHashSet<>();
asSet.add(attributeValue);
if (attributeValue != null) {
asSet.add(attributeValue);
}
setAttribute(attributeName, asSet);
}

public void setAttribute(String attributeName, Set<String> attributeValue) {
attributes.put(attributeName, attributeValue);
lowerCasedAttributes.put(attributeName.toLowerCase(), attributeValue);
attributeName = attributeName.toLowerCase();
lowerCasedAttributes.put(attributeName, attributeValue);
if (consumerOnMandatoryAttributesComplete != null) {
if (!attributeValue.isEmpty()) {
mandatoryAttributeNamesRemaining.remove(attributeName);
} else if (mandatoryAttributeNames.contains(attributeName)) {
mandatoryAttributeNamesRemaining.add(attributeName);
}
executeConsumerOnMandatoryAttributesComplete();
}
}

// Case-insensitive
Expand Down Expand Up @@ -203,4 +249,14 @@ public String toString() {
return "LDAP Object [ dn: " + dn + " , uuid: " + uuid + ", attributes: " + attributes +
", readOnly attribute names: " + readOnlyAttributeNames + ", ranges: " + rangedAttributes + " ]";
}

private void executeConsumerOnMandatoryAttributesComplete() {
if (mandatoryAttributeNamesRemaining.isEmpty()) {
final Consumer<LDAPObject> consumer = consumerOnMandatoryAttributesComplete;
consumerOnMandatoryAttributesComplete = null;
mandatoryAttributeNames = null;
mandatoryAttributeNamesRemaining = null;
consumer.accept(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import javax.naming.AuthenticationException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.keycloak.models.RoleModel;

/**
Expand Down Expand Up @@ -73,6 +74,10 @@ public boolean onAuthenticationFailure(LDAPObject ldapUser, UserModel user, Auth
return false;
}

@Override
public Set<String> mandatoryAttributeNames() {
return null;
}

public static boolean parseBooleanParameter(ComponentModel mapperModel, String paramName) {
String paramm = mapperModel.getConfig().getFirst(paramName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import javax.naming.AuthenticationException;
import java.util.List;
import java.util.Set;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;

Expand Down Expand Up @@ -85,6 +86,13 @@ public interface LDAPStorageMapper extends Provider {
*/
void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm);

/**
* Method that returns the mandatory attributes that this mapper imposes
* on the entry.
*
* @return The list of mandatory attributes or null
*/
Set<String> mandatoryAttributeNames();

/**
* Called when invoke proxy on LDAP federation provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.jboss.logging.Logger;
import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.ModelException;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject;

Expand All @@ -47,16 +48,22 @@ public LDAPTransaction(LDAPStorageProvider ldapProvider, LDAPObject ldapUser) {
@Override
protected void commitImpl() {
if (logger.isTraceEnabled()) {
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn() + ", attributes: " + ldapUser.getAttributes());
}
if (ldapUser.isWaitingForExecutionOnMandatoryAttributesComplete()) {
throw new ModelException("LDAPObject cannot be commited because some mandatory attributes are not set: "
+ ldapUser.getMandatoryAttributeNamesRemaining());
}

ldapProvider.getLdapIdentityStore().update(ldapUser);
if (!updatedAttributes.isEmpty()) {
ldapProvider.getLdapIdentityStore().update(ldapUser);
}
}


@Override
protected void rollbackImpl() {
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn());
}

/**
Expand All @@ -65,7 +72,10 @@ protected void rollbackImpl() {
* @param attributeName model attribute name (For example "firstName", "lastName", "street")
*/
public void addUpdatedAttribute(String attributeName) {
updatedAttributes.add(attributeName);
if (ldapUser.getDn() != null) {
// only add the attribute if the ldapObject is created, it has a DN
updatedAttributes.add(attributeName);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected void ensureTransactionStarted() {
LDAPTransaction transaction = provider.getUserManager().getTransaction(getId());
if (transaction.getState() == LDAPTransaction.TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) {
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn());
}

this.provider.getSession().getTransactionManager().enlistAfterCompletion(transaction);
Expand Down
Loading

0 comments on commit cc9326f

Please sign in to comment.