Keep same name on update for LDAP attributes
Closes https://github.com/keycloak/keycloak/issues/23888
This commit is contained in:
parent
64836680d7
commit
6963364514
2 changed files with 51 additions and 18 deletions
|
@ -20,6 +20,7 @@ package org.keycloak.storage.ldap.idm.model;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -48,8 +49,8 @@ public class LDAPObject {
|
||||||
|
|
||||||
private final Map<String, Set<String>> attributes = new HashMap<>();
|
private final Map<String, Set<String>> attributes = new HashMap<>();
|
||||||
|
|
||||||
// Copy of "attributes" containing lower-cased keys
|
// Copy of "attributes" containing lower-cased keys and original case-sensitive attribute name
|
||||||
private final Map<String, Set<String>> lowerCasedAttributes = new HashMap<>();
|
private final Map<String, Map.Entry<String, Set<String>>> lowerCasedAttributes = new HashMap<>();
|
||||||
|
|
||||||
// range attributes are always read from 0 to max so just saving the top value
|
// range attributes are always read from 0 to max so just saving the top value
|
||||||
private final Map<String, Integer> rangedAttributes = new HashMap<>();
|
private final Map<String, Integer> rangedAttributes = new HashMap<>();
|
||||||
|
@ -72,8 +73,8 @@ public class LDAPObject {
|
||||||
for (String name : mandatoryAttributeNames) {
|
for (String name : mandatoryAttributeNames) {
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase();
|
||||||
this.mandatoryAttributeNames.add(name);
|
this.mandatoryAttributeNames.add(name);
|
||||||
Set<String> values = lowerCasedAttributes.get(name);
|
Map.Entry<String, Set<String>> entry = lowerCasedAttributes.get(name);
|
||||||
if (values == null || values.isEmpty()) {
|
if (entry == null || entry.getValue().isEmpty()) {
|
||||||
this.mandatoryAttributeNamesRemaining.add(name);
|
this.mandatoryAttributeNamesRemaining.add(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +133,7 @@ public class LDAPObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful when single value will be used as the "RDN" attribute. Which will be most of the cases
|
* Useful when single value will be used as the "RDN" attribute. Which will be most of the cases
|
||||||
|
* @param rdnAttributeName The RDN of the ldap object
|
||||||
*/
|
*/
|
||||||
public void setRdnAttributeName(String rdnAttributeName) {
|
public void setRdnAttributeName(String rdnAttributeName) {
|
||||||
this.rdnAttributeNames.clear();
|
this.rdnAttributeNames.clear();
|
||||||
|
@ -156,14 +158,22 @@ public class LDAPObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAttribute(String attributeName, Set<String> attributeValue) {
|
public void setAttribute(String attributeName, Set<String> attributeValue) {
|
||||||
attributes.put(attributeName, attributeValue);
|
final String attributeNameLowerCase = attributeName.toLowerCase();
|
||||||
attributeName = attributeName.toLowerCase();
|
final Set<String> valueSet = attributeValue == null? Collections.emptySet() : attributeValue;
|
||||||
lowerCasedAttributes.put(attributeName, attributeValue);
|
Map.Entry<String, Set<String>> entry = lowerCasedAttributes.get(attributeNameLowerCase);
|
||||||
|
if (entry == null) {
|
||||||
|
attributes.put(attributeName, valueSet);
|
||||||
|
lowerCasedAttributes.put(attributeNameLowerCase, Map.entry(attributeName, valueSet));
|
||||||
|
} else {
|
||||||
|
// existing entry, maintain previous case for the attribute name
|
||||||
|
attributes.put(entry.getKey(), valueSet);
|
||||||
|
lowerCasedAttributes.put(attributeNameLowerCase, Map.entry(entry.getKey(), valueSet));
|
||||||
|
}
|
||||||
if (consumerOnMandatoryAttributesComplete != null) {
|
if (consumerOnMandatoryAttributesComplete != null) {
|
||||||
if (!attributeValue.isEmpty()) {
|
if (!valueSet.isEmpty()) {
|
||||||
mandatoryAttributeNamesRemaining.remove(attributeName);
|
mandatoryAttributeNamesRemaining.remove(attributeNameLowerCase);
|
||||||
} else if (mandatoryAttributeNames.contains(attributeName)) {
|
} else if (mandatoryAttributeNames.contains(attributeNameLowerCase)) {
|
||||||
mandatoryAttributeNamesRemaining.add(attributeName);
|
mandatoryAttributeNamesRemaining.add(attributeNameLowerCase);
|
||||||
}
|
}
|
||||||
executeConsumerOnMandatoryAttributesComplete();
|
executeConsumerOnMandatoryAttributesComplete();
|
||||||
}
|
}
|
||||||
|
@ -171,20 +181,20 @@ public class LDAPObject {
|
||||||
|
|
||||||
// Case-insensitive
|
// Case-insensitive
|
||||||
public String getAttributeAsString(String name) {
|
public String getAttributeAsString(String name) {
|
||||||
Set<String> attrValue = lowerCasedAttributes.get(name.toLowerCase());
|
Map.Entry<String, Set<String>> entry = lowerCasedAttributes.get(name.toLowerCase());
|
||||||
if (attrValue == null || attrValue.size() == 0) {
|
if (entry == null || entry.getValue().isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
} else if (attrValue.size() > 1) {
|
} else if (entry.getValue().size() > 1) {
|
||||||
logger.warnf("Expected String but attribute '%s' has more values '%s' on object '%s' . Returning just first value", name, attrValue, dn);
|
logger.warnf("Expected String but attribute '%s' has more values '%s' on object '%s' . Returning just first value", name, entry.getValue(), dn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return attrValue.iterator().next();
|
return entry.getValue().iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case-insensitive. Return null if there is not value of attribute with given name or set with all values otherwise
|
// Case-insensitive. Return null if there is not value of attribute with given name or set with all values otherwise
|
||||||
public Set<String> getAttributeAsSet(String name) {
|
public Set<String> getAttributeAsSet(String name) {
|
||||||
Set<String> values = lowerCasedAttributes.get(name.toLowerCase());
|
Map.Entry<String, Set<String>> entry = lowerCasedAttributes.get(name.toLowerCase());
|
||||||
return (values == null) ? null : new LinkedHashSet<>(values);
|
return (entry == null) ? null : new LinkedHashSet<>(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRangeComplete(String name) {
|
public boolean isRangeComplete(String name) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
@ -78,6 +79,7 @@ import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import javax.naming.AuthenticationException;
|
import javax.naming.AuthenticationException;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -702,7 +704,28 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
|
||||||
UserModel user = session.users().getUserByUsername(appRealm, "johnzip");
|
UserModel user = session.users().getUserByUsername(appRealm, "johnzip");
|
||||||
String postalCode = user.getFirstAttribute("postal_code");
|
String postalCode = user.getFirstAttribute("postal_code");
|
||||||
Assert.assertEquals("12398", postalCode);
|
Assert.assertEquals("12398", postalCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// modify postal_code in the user
|
||||||
|
RealmResource realm = testRealm();
|
||||||
|
List<UserRepresentation> users = realm.users().search("johnzip", true);
|
||||||
|
Assert.assertEquals("User not found", 1, users.size());
|
||||||
|
UserRepresentation user = users.iterator().next();
|
||||||
|
Assert.assertEquals("Incorrect postal code", Collections.singletonList("12398"), user.getAttributes().get("postal_code"));
|
||||||
|
UserResource userRes = realm.users().get(user.getId());
|
||||||
|
user.getAttributes().put("postal_code", Collections.singletonList("9876"));
|
||||||
|
userRes.update(user);
|
||||||
|
user = userRes.toRepresentation();
|
||||||
|
Assert.assertEquals("Incorrect postal code", Collections.singletonList("9876"), user.getAttributes().get("postal_code"));
|
||||||
|
|
||||||
|
// ensure the ldap contains the correct value
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
|
RealmModel appRealm = ctx.getRealm();
|
||||||
|
|
||||||
|
LDAPStorageProvider ldapProvider = ctx.getLdapProvider();
|
||||||
|
LDAPObject ldapUser = ldapProvider.loadLDAPUserByUsername(appRealm, "johnzip");
|
||||||
|
Assert.assertEquals("Incorrect postal code", "9876", ldapUser.getAttributeAsString("POstalCode"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue