Use jdk LdapName and Rdn to parse inside LDAPDn and RDN and avoid string conversions

Closes: https://github.com/keycloak/keycloak/issues/21797
Closes: https://github.com/keycloak/keycloak/issues/21818
This commit is contained in:
rmartinc 2023-09-27 16:49:30 +02:00 committed by Marek Posolda
parent a2dd0f31c5
commit d10ccc7245
10 changed files with 206 additions and 246 deletions

View file

@ -76,6 +76,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>jakarta.transaction</groupId> <groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId> <artifactId>jakarta.transaction-api</artifactId>

View file

@ -231,7 +231,7 @@ public class LDAPUtils {
*/ */
public static void addMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, String memberChildAttrName, LDAPObject ldapParent, LDAPObject ldapChild) { public static void addMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, String memberChildAttrName, LDAPObject ldapParent, LDAPObject ldapChild) {
String membership = getMemberValueOfChildObject(ldapChild, membershipType, memberChildAttrName); String membership = getMemberValueOfChildObject(ldapChild, membershipType, memberChildAttrName);
ldapProvider.getLdapIdentityStore().addMemberToGroup(ldapParent.getDn().toString(), memberAttrName, membership); ldapProvider.getLdapIdentityStore().addMemberToGroup(ldapParent.getDn().getLdapName(), memberAttrName, membership);
} }
/** /**
@ -246,7 +246,7 @@ public class LDAPUtils {
*/ */
public static void deleteMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, String memberChildAttrName, LDAPObject ldapParent, LDAPObject ldapChild) { public static void deleteMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, String memberChildAttrName, LDAPObject ldapParent, LDAPObject ldapChild) {
String userMembership = getMemberValueOfChildObject(ldapChild, membershipType, memberChildAttrName); String userMembership = getMemberValueOfChildObject(ldapChild, membershipType, memberChildAttrName);
ldapProvider.getLdapIdentityStore().removeMemberFromGroup(ldapParent.getDn().toString(), memberAttrName, userMembership); ldapProvider.getLdapIdentityStore().removeMemberFromGroup(ldapParent.getDn().getLdapName(), memberAttrName, userMembership);
} }
/** /**
@ -333,7 +333,7 @@ public class LDAPUtils {
private static LDAPQuery createLdapQueryForRangeAttribute(LDAPStorageProvider ldapProvider, LDAPObject ldapObject, String name) { private static LDAPQuery createLdapQueryForRangeAttribute(LDAPStorageProvider ldapProvider, LDAPObject ldapObject, String name) {
LDAPQuery q = new LDAPQuery(ldapProvider); LDAPQuery q = new LDAPQuery(ldapProvider);
q.setSearchDn(ldapObject.getDn().toString()); q.setSearchDn(ldapObject.getDn().getLdapName());
q.setSearchScope(SearchControls.OBJECT_SCOPE); q.setSearchScope(SearchControls.OBJECT_SCOPE);
q.addReturningLdapAttribute(name + ";range=" + (ldapObject.getCurrentRange(name) + 1) + "-*"); q.addReturningLdapAttribute(name + ";range=" + (ldapObject.getCurrentRange(name) + 1) + "-*");
return q; return q;

View file

@ -17,72 +17,53 @@
package org.keycloak.storage.ldap.idm.model; package org.keycloak.storage.ldap.idm.model;
import javax.naming.ldap.Rdn; import java.util.ArrayList;
import java.util.Collection; import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import javax.naming.NamingEnumeration;
import java.util.stream.Collectors; import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class LDAPDn { public class LDAPDn {
private static final Pattern DN_PATTERN = Pattern.compile("(?<!\\\\),");
private static final Pattern ENTRY_PATTERN = Pattern.compile("(?<!\\\\)\\+");
private static final Pattern SUB_ENTRY_PATTERN = Pattern.compile("(?<!\\\\)=");
private final Deque<RDN> entries; private final LdapName ldapName;
private LDAPDn() { private LDAPDn() {
this.entries = new LinkedList<>(); this.ldapName = new LdapName(Collections.emptyList());
} }
private LDAPDn(Deque<RDN> entries) { private LDAPDn(LdapName ldapName) {
this.entries = entries; this.ldapName = ldapName;
}
public static LDAPDn fromLdapName(LdapName ldapName) {
return new LDAPDn(ldapName);
} }
public static LDAPDn fromString(String dnString) { public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
// In certain OpenLDAP implementations the uniqueMember attribute is mandatory // In certain OpenLDAP implementations the uniqueMember attribute is mandatory
// Thus, if a new group is created, it will contain an empty uniqueMember attribute // Thus, if a new group is created, it will contain an empty uniqueMember attribute
// Later on, when adding members, this empty attribute will be kept // Later on, when adding members, this empty attribute will be kept
// Keycloak must be able to process it, properly, w/o throwing an ArrayIndexOutOfBoundsException // Keycloak must be able to process it, properly, w/o throwing an ArrayIndexOutOfBoundsException
if(dnString.trim().isEmpty()) if(dnString.trim().isEmpty()) {
return dn; return new LDAPDn();
String[] rdns = DN_PATTERN.split(dnString);
for (String entryStr : rdns) {
if (entryStr.indexOf('+') == -1) {
// This is 99.9% of cases where RDN consists of single key-value pair
SubEntry subEntry = parseSingleSubEntry(dn, entryStr);
dn.addLast(new RDN(subEntry));
} else {
// This is 0.1% of cases where RDN consists of more key-value pairs like "uid=foo+cn=bar"
String[] subEntries = ENTRY_PATTERN.split(entryStr);
RDN entry = new RDN();
for (String subEntryStr : subEntries) {
SubEntry subEntry = parseSingleSubEntry(dn, subEntryStr);
entry.addSubEntry(subEntry);
}
dn.addLast(entry);
}
} }
return dn; try {
return new LDAPDn(new LdapName(dnString));
} catch (NamingException e) {
throw new IllegalArgumentException("Invalid DN:" + dnString, e);
}
} }
// parse single sub-entry and add it to the "dn" . Assumption is that subentry is something like "uid=bar" and does not contain + character public LdapName getLdapName() {
private static SubEntry parseSingleSubEntry(LDAPDn dn, String subEntryStr) { return ldapName;
String[] rdn = SUB_ENTRY_PATTERN.split(subEntryStr);
if (rdn.length >1) {
return new SubEntry(rdn[0].trim(), rdn[1].trim());
} else {
return new SubEntry(rdn[0].trim(), "");
}
} }
@Override @Override
@ -91,50 +72,27 @@ public class LDAPDn {
return false; return false;
} }
return toString().equals(obj.toString()); return ldapName.equals(((LDAPDn) obj).ldapName);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return toString().hashCode(); return ldapName.hashCode();
} }
@Override @Override
public String toString() { public String toString() {
return toString(entries); return ldapName.toString();
}
private static String toString(Collection<RDN> entries) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (RDN rdn : entries) {
if (first) {
first = false;
} else {
builder.append(",");
}
builder.append(rdn.toString());
}
return builder.toString();
} }
/** /**
* @return first entry. Usually entry corresponding to something like "uid=joe" from the DN like "uid=joe,dc=something,dc=org" * @return first entry. Usually entry corresponding to something like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/ */
public RDN getFirstRdn() { public RDN getFirstRdn() {
return entries.getFirst(); if (ldapName.size() > 0) {
} return new RDN(ldapName.getRdn(ldapName.size() - 1));
}
private static String unescapeValue(String escaped) { return null;
// Something needed to handle non-String types?
return Rdn.unescapeValue(escaped).toString();
}
private static String escapeValue(String unescaped) {
// Something needed to handle non-String types?
return Rdn.escapeValue(unescaped);
} }
/** /**
@ -144,37 +102,31 @@ public class LDAPDn {
* *
*/ */
public LDAPDn getParentDn() { public LDAPDn getParentDn() {
LinkedList<RDN> parentDnEntries = new LinkedList<>(entries); if (ldapName.size() > 0) {
parentDnEntries.remove(); LdapName parent = (LdapName) ldapName.getPrefix(ldapName.size() - 1);
return new LDAPDn(parentDnEntries); return new LDAPDn(parent);
}
return null;
} }
public boolean isDescendantOf(LDAPDn expectedParentDn) { public boolean isDescendantOf(LDAPDn expectedParentDn) {
int parentEntriesCount = expectedParentDn.entries.size(); LDAPDn parent = getParentDn();
if (parent == null) {
Deque<RDN> myEntries = new LinkedList<>(this.entries); return false;
boolean someRemoved = false;
while (myEntries.size() > parentEntriesCount) {
myEntries.removeFirst();
someRemoved = true;
} }
return parent.ldapName.startsWith(expectedParentDn.ldapName);
String myEntriesParentStr = toString(myEntries).toLowerCase();
String expectedParentDnStr = expectedParentDn.toString().toLowerCase();
return someRemoved && myEntriesParentStr.equals(expectedParentDnStr);
} }
public void addFirst(String rdnName, String rdnValue) { public void addFirst(String rdnName, String rdnValue) {
rdnValue = escapeValue(rdnValue); try {
entries.addFirst(new RDN(new SubEntry(rdnName, rdnValue))); ldapName.add(rdnName + "=" + Rdn.escapeValue(rdnValue));
} catch (NamingException e) {
throw new IllegalArgumentException("Invalid RDN name=" + rdnName + " value=" + rdnValue, e);
}
} }
public void addFirst(RDN entry) { public void addFirst(RDN entry) {
entries.addFirst(entry); ldapName.add(entry.rdn);
}
private void addLast(RDN entry) {
entries.addLast(entry);
} }
/** /**
@ -183,24 +135,27 @@ public class LDAPDn {
*/ */
public static class RDN { public static class RDN {
private List<SubEntry> subs = new LinkedList<>(); private Rdn rdn;
private RDN() { private RDN(Rdn rdn) {
} this.rdn = rdn;
private RDN(SubEntry subEntry) {
subs.add(subEntry);
}
private void addSubEntry(SubEntry subEntry) {
subs.add(subEntry);
} }
/** /**
* @return Keys in the RDN. Returned list is the copy, which is not linked to the original RDN * @return Keys in the RDN. Returned list is the copy, which is not linked to the original RDN
*/ */
public List<String> getAllKeys() { public List<String> getAllKeys() {
return subs.stream().map(SubEntry::getAttrName).collect(Collectors.toList()); try {
Attributes attrs = rdn.toAttributes();
List<String> result = new ArrayList<>(attrs.size());
NamingEnumeration<? extends Attribute> ne = attrs.getAll();
while (ne.hasMore()) {
result.add(ne.next().getID());
}
return result;
} catch (NamingException e) {
throw new IllegalStateException(e);
}
} }
/** /**
@ -213,44 +168,52 @@ public class LDAPDn {
* @return * @return
*/ */
public String getAttrValue(String attrName) { public String getAttrValue(String attrName) {
for (SubEntry sub : subs) { try {
if (attrName.equalsIgnoreCase(sub.attrName)) { Attribute attr = rdn.toAttributes().get(attrName);
return LDAPDn.unescapeValue(sub.attrValue); if (attr != null) {
Object value = attr.get();
if (value != null) {
return value.toString();
}
} }
return null;
} catch (NamingException e) {
throw new IllegalStateException(e);
} }
return null;
} }
public void setAttrValue(String attrName, String newAttrValue) { public void setAttrValue(String attrName, String newAttrValue) {
for (SubEntry sub : subs) { try {
if (attrName.equalsIgnoreCase(sub.attrName)) { Attributes attrs = rdn.toAttributes();
sub.attrValue = escapeValue(newAttrValue); Attribute attr = attrs.get(attrName);
return; if (attr != null) {
attr.clear();
attr.add(newAttrValue);
} else {
attrs.put(attrName, newAttrValue);
} }
rdn = new Rdn(attrs);
} catch (NamingException e) {
throw new IllegalStateException(e);
} }
addSubEntry(new SubEntry(attrName, escapeValue(newAttrValue)));
} }
public boolean removeAttrValue(String attrName) { public boolean removeAttrValue(String attrName) {
SubEntry toRemove = null; try {
for (SubEntry sub : subs) { Attributes attrs = rdn.toAttributes();
if (attrName.equalsIgnoreCase(sub.attrName)) { if (attrs.remove(attrName) != null) {
toRemove = sub; rdn = new Rdn(attrs);
continue; return true;
} }
}
if (toRemove != null) {
subs.remove(toRemove);
return true;
} else {
return false; return false;
} catch (NamingException e) {
throw new IllegalStateException(e);
} }
} }
@Override @Override
public String toString() { public String toString() {
return toString(true); return rdn.toString();
} }
/** /**
@ -259,43 +222,26 @@ public class LDAPDn {
* @return * @return
*/ */
public String toString(boolean escaped) { public String toString(boolean escaped) {
StringBuilder builder = new StringBuilder(); if (escaped) {
return toString();
}
boolean first = true; StringBuilder builder = new StringBuilder();
for (SubEntry subEntry : subs) { try {
if (first) { NamingEnumeration<? extends Attribute> attrs = rdn.toAttributes().getAll();
first = false; while (attrs.hasMore()) {
} else { Attribute attr = attrs.next();
builder.append('+'); NamingEnumeration<?> values = attr.getAll();
while (values.hasMore()) {
builder.append(attr.getID()).append("=").append(values.next().toString()).append("+");
}
} }
builder.append(subEntry.toString(escaped)); builder.setLength(builder.length() - 1);
} catch (NamingException e) {
throw new IllegalStateException(e);
} }
return builder.toString(); return builder.toString();
} }
} }
private static class SubEntry {
private final String attrName;
private String attrValue;
private SubEntry(String attrName, String attrValue) {
this.attrName = attrName;
this.attrValue = attrValue;
}
private String getAttrName() {
return attrName;
}
@Override
public String toString() {
return toString(true);
}
private String toString(boolean escaped) {
String val = escaped ? attrValue : unescapeValue(attrValue);
return attrName + '=' + val;
}
}
} }

View file

@ -22,6 +22,7 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPDn;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.idm.query.Condition; import org.keycloak.storage.ldap.idm.query.Condition;
import org.keycloak.storage.ldap.idm.query.Sort; import org.keycloak.storage.ldap.idm.query.Sort;
@ -32,6 +33,7 @@ import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.naming.directory.SearchControls; import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import java.util.*; import java.util.*;
@ -54,7 +56,7 @@ public class LDAPQuery implements AutoCloseable {
private int limit; private int limit;
private PaginationContext paginationContext; private PaginationContext paginationContext;
private LDAPContextManager ldapContextManager; private LDAPContextManager ldapContextManager;
private String searchDn; private LdapName searchDn;
private final Set<Condition> conditions = new LinkedHashSet<>(); private final Set<Condition> conditions = new LinkedHashSet<>();
private final Set<Sort> ordering = new LinkedHashSet<>(); private final Set<Sort> ordering = new LinkedHashSet<>();
@ -84,6 +86,11 @@ public class LDAPQuery implements AutoCloseable {
} }
public LDAPQuery setSearchDn(String searchDn) { public LDAPQuery setSearchDn(String searchDn) {
this.searchDn = LDAPDn.fromString(searchDn).getLdapName();
return this;
}
public LDAPQuery setSearchDn(LdapName searchDn) {
this.searchDn = searchDn; this.searchDn = searchDn;
return this; return this;
} }
@ -117,7 +124,7 @@ public class LDAPQuery implements AutoCloseable {
return unmodifiableSet(this.ordering); return unmodifiableSet(this.ordering);
} }
public String getSearchDn() { public LdapName getSearchDn() {
return this.searchDn; return this.searchDn;
} }

View file

@ -17,7 +17,10 @@
package org.keycloak.storage.ldap.idm.store; package org.keycloak.storage.ldap.idm.store;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.naming.AuthenticationException;
import javax.naming.ldap.LdapName;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;
@ -25,9 +28,6 @@ import org.keycloak.representations.idm.LDAPCapabilityRepresentation;
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator; import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
import javax.naming.AuthenticationException;
import java.util.List;
/** /**
* IdentityStore representation providing minimal SPI * IdentityStore representation providing minimal SPI
* *
@ -74,7 +74,7 @@ public interface IdentityStore {
* @param memberAttrName The member attribute name * @param memberAttrName The member attribute name
* @param value The value (it can be uid or dn depending the group type) * @param value The value (it can be uid or dn depending the group type)
*/ */
public void addMemberToGroup(String groupDn, String memberAttrName, String value); public void addMemberToGroup(LdapName groupDn, String memberAttrName, String value);
/** /**
* Removes a member from a group. * Removes a member from a group.
@ -82,7 +82,7 @@ public interface IdentityStore {
* @param memberAttrName The member attribute name * @param memberAttrName The member attribute name
* @param value The value (it can be uid or dn depending the group type) * @param value The value (it can be uid or dn depending the group type)
*/ */
public void removeMemberFromGroup(String groupDn, String memberAttrName, String value); public void removeMemberFromGroup(LdapName groupDn, String memberAttrName, String value);
// Identity query // Identity query

View file

@ -64,6 +64,7 @@ import java.util.stream.Collectors;
import javax.naming.directory.AttributeInUseException; import javax.naming.directory.AttributeInUseException;
import javax.naming.directory.NoSuchAttributeException; import javax.naming.directory.NoSuchAttributeException;
import javax.naming.directory.SchemaViolationException; import javax.naming.directory.SchemaViolationException;
import javax.naming.ldap.LdapName;
/** /**
* An IdentityStore implementation backed by an LDAP directory * An IdentityStore implementation backed by an LDAP directory
@ -97,18 +98,17 @@ public class LDAPIdentityStore implements IdentityStore {
throw new ModelException("Can't add object with already assigned uuid"); throw new ModelException("Can't add object with already assigned uuid");
} }
String entryDN = ldapObject.getDn().toString();
BasicAttributes ldapAttributes = extractAttributesForSaving(ldapObject, true); BasicAttributes ldapAttributes = extractAttributesForSaving(ldapObject, true);
this.operationManager.createSubContext(entryDN, ldapAttributes); this.operationManager.createSubContext(ldapObject.getDn().getLdapName(), ldapAttributes);
ldapObject.setUuid(getEntryIdentifier(ldapObject)); ldapObject.setUuid(getEntryIdentifier(ldapObject));
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debugf("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), entryDN); logger.debugf("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), ldapObject.getDn());
} }
} }
@Override @Override
public void addMemberToGroup(String groupDn, String memberAttrName, String value) { public void addMemberToGroup(LdapName groupDn, String memberAttrName, String value) {
// do not check EMPTY_MEMBER_ATTRIBUTE_VALUE, we save one useless query // do not check EMPTY_MEMBER_ATTRIBUTE_VALUE, we save one useless query
// the value will be there forever for objectclasses that enforces the attribute as MUST // the value will be there forever for objectclasses that enforces the attribute as MUST
BasicAttribute attr = new BasicAttribute(memberAttrName, value); BasicAttribute attr = new BasicAttribute(memberAttrName, value);
@ -123,7 +123,7 @@ public class LDAPIdentityStore implements IdentityStore {
} }
@Override @Override
public void removeMemberFromGroup(String groupDn, String memberAttrName, String value) { public void removeMemberFromGroup(LdapName groupDn, String memberAttrName, String value) {
BasicAttribute attr = new BasicAttribute(memberAttrName, value); BasicAttribute attr = new BasicAttribute(memberAttrName, value);
ModificationItem item = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); ModificationItem item = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr);
try { try {
@ -152,17 +152,16 @@ public class LDAPIdentityStore implements IdentityStore {
BasicAttributes updatedAttributes = extractAttributesForSaving(ldapObject, false); BasicAttributes updatedAttributes = extractAttributesForSaving(ldapObject, false);
NamingEnumeration<Attribute> attributes = updatedAttributes.getAll(); NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
String entryDn = ldapObject.getDn().toString(); this.operationManager.modifyAttributes(ldapObject.getDn().getLdapName(), attributes);
this.operationManager.modifyAttributes(entryDn, attributes);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debugf("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), entryDn); logger.debugf("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), ldapObject.getDn());
} }
} }
protected void checkRename(LDAPObject ldapObject) { protected void checkRename(LDAPObject ldapObject) {
LDAPDn.RDN firstRdn = ldapObject.getDn().getFirstRdn(); LDAPDn.RDN firstRdn = ldapObject.getDn().getFirstRdn();
String oldDn = ldapObject.getDn().toString(); LdapName oldDn = ldapObject.getDn().getLdapName();
// Detect which keys will need to be updated in RDN, which are new keys to be added, and which are to be removed // Detect which keys will need to be updated in RDN, which are new keys to be added, and which are to be removed
List<String> toUpdateKeys = firstRdn.getAllKeys(); List<String> toUpdateKeys = firstRdn.getAllKeys();
@ -218,20 +217,20 @@ public class LDAPIdentityStore implements IdentityStore {
LDAPDn newLdapDn = ldapObject.getDn().getParentDn(); LDAPDn newLdapDn = ldapObject.getDn().getParentDn();
newLdapDn.addFirst(firstRdn); newLdapDn.addFirst(firstRdn);
String newDn = newLdapDn.toString(); LdapName newDn = newLdapDn.getLdapName();
logger.debugf("Renaming LDAP Object. Old DN: [%s], New DN: [%s]", oldDn, newDn); logger.debugf("Renaming LDAP Object. Old DN: [%s], New DN: [%s]", oldDn, newDn);
// In case, that there is conflict (For example already existing "CN=John Anthony"), the different DN is returned // In case, that there is conflict (For example already existing "CN=John Anthony"), the different DN is returned
newDn = this.operationManager.renameEntry(oldDn, newDn, true); newDn = this.operationManager.renameEntry(oldDn, newDn, true);
ldapObject.setDn(LDAPDn.fromString(newDn)); ldapObject.setDn(LDAPDn.fromLdapName(newDn));
} }
} }
@Override @Override
public void remove(LDAPObject ldapObject) { public void remove(LDAPObject ldapObject) {
this.operationManager.removeEntry(ldapObject.getDn().toString()); this.operationManager.removeEntry(ldapObject.getDn().getLdapName());
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debugf("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString()); logger.debugf("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString());
@ -248,7 +247,7 @@ public class LDAPIdentityStore implements IdentityStore {
List<LDAPObject> results = new ArrayList<>(); List<LDAPObject> results = new ArrayList<>();
try { try {
String baseDN = identityQuery.getSearchDn(); LdapName baseDN = identityQuery.getSearchDn();
for (Condition condition : identityQuery.getConditions()) { for (Condition condition : identityQuery.getConditions()) {
@ -281,7 +280,7 @@ public class LDAPIdentityStore implements IdentityStore {
for (SearchResult result : search) { for (SearchResult result : search) {
// don't add the branch in subtree search // don't add the branch in subtree search
if (identityQuery.getSearchScope() != SearchControls.SUBTREE_SCOPE || !result.getNameInNamespace().equalsIgnoreCase(baseDN)) { if (identityQuery.getSearchScope() != SearchControls.SUBTREE_SCOPE || !baseDN.equals(new LdapName(result.getNameInNamespace()))) {
results.add(populateAttributedType(result, identityQuery)); results.add(populateAttributedType(result, identityQuery));
} }
} }
@ -314,7 +313,7 @@ public class LDAPIdentityStore implements IdentityStore {
attrs.add("supportedExtension"); attrs.add("supportedExtension");
attrs.add("supportedFeatures"); attrs.add("supportedFeatures");
List<SearchResult> searchResults = operationManager List<SearchResult> searchResults = operationManager
.search("", "(objectClass=*)", Collections.unmodifiableCollection(attrs), SearchControls.OBJECT_SCOPE); .search(new LdapName(Collections.emptyList()), "(objectClass=*)", Collections.unmodifiableCollection(attrs), SearchControls.OBJECT_SCOPE);
if (searchResults.size() != 1) { if (searchResults.size() != 1) {
throw new ModelException("Could not query root DSE: unexpected result size"); throw new ModelException("Could not query root DSE: unexpected result size");
} }
@ -343,36 +342,32 @@ public class LDAPIdentityStore implements IdentityStore {
@Override @Override
public void validatePassword(LDAPObject user, String password) throws AuthenticationException { public void validatePassword(LDAPObject user, String password) throws AuthenticationException {
String userDN = user.getDn().toString();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Using DN [%s] for authentication of user", userDN); logger.tracef("Using DN [%s] for authentication of user", user.getDn());
} }
operationManager.authenticate(userDN, password); operationManager.authenticate(user.getDn().getLdapName(), password);
} }
@Override @Override
public void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator) { public void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator) {
String userDN = user.getDn().toString();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debugf("Using DN [%s] for updating LDAP password of user", userDN); logger.debugf("Using DN [%s] for updating LDAP password of user", user.getDn());
} }
if (getConfig().isActiveDirectory()) { if (getConfig().isActiveDirectory()) {
updateADPassword(userDN, password, passwordUpdateDecorator); updateADPassword(user.getDn().getLdapName(), password, passwordUpdateDecorator);
return; return;
} }
try { try {
if (config.useExtendedPasswordModifyOp()) { if (config.useExtendedPasswordModifyOp()) {
operationManager.passwordModifyExtended(userDN, password, passwordUpdateDecorator); operationManager.passwordModifyExtended(user.getDn().getLdapName(), password, passwordUpdateDecorator);
} else { } else {
ModificationItem[] mods = new ModificationItem[1]; ModificationItem[] mods = new ModificationItem[1];
BasicAttribute mod0 = new BasicAttribute(LDAPConstants.USER_PASSWORD_ATTRIBUTE, password); BasicAttribute mod0 = new BasicAttribute(LDAPConstants.USER_PASSWORD_ATTRIBUTE, password);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0); mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
operationManager.modifyAttributes(userDN, mods, passwordUpdateDecorator); operationManager.modifyAttributes(user.getDn().getLdapName(), mods, passwordUpdateDecorator);
} }
} catch (ModelException me) { } catch (ModelException me) {
throw me; throw me;
@ -381,7 +376,7 @@ public class LDAPIdentityStore implements IdentityStore {
} }
} }
private void updateADPassword(String userDN, String password, LDAPOperationDecorator passwordUpdateDecorator) { private void updateADPassword(LdapName userDN, String password, LDAPOperationDecorator passwordUpdateDecorator) {
try { try {
// Replace the "unicdodePwd" attribute with a new value // Replace the "unicdodePwd" attribute with a new value
// Password must be both Unicode and a quoted string // Password must be both Unicode and a quoted string
@ -617,7 +612,7 @@ public class LDAPIdentityStore implements IdentityStore {
String rdn = ldapObject.getDn().getFirstRdn().toString(false); String rdn = ldapObject.getDn().getFirstRdn().toString(false);
String filter = "(" + EscapeStrategy.DEFAULT.escape(rdn) + ")"; String filter = "(" + EscapeStrategy.DEFAULT.escape(rdn) + ")";
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().toString(), filter, Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE); List<SearchResult> search = this.operationManager.search(ldapObject.getDn().getLdapName(), filter, Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName()); Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName());
if (id == null) { if (id == null) {

View file

@ -87,7 +87,7 @@ public class LDAPOperationManager {
* @param dn * @param dn
* @param attribute * @param attribute
*/ */
public void modifyAttribute(String dn, Attribute attribute) { public void modifyAttribute(LdapName dn, Attribute attribute) {
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)}; ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)};
modifyAttributes(dn, mods, null); modifyAttributes(dn, mods, null);
} }
@ -101,7 +101,7 @@ public class LDAPOperationManager {
* @param dn * @param dn
* @param attributes * @param attributes
*/ */
public void modifyAttributes(String dn, NamingEnumeration<Attribute> attributes) { public void modifyAttributes(LdapName dn, NamingEnumeration<Attribute> attributes) {
try { try {
List<ModificationItem> modItems = new ArrayList<>(); List<ModificationItem> modItems = new ArrayList<>();
while (attributes.hasMore()) { while (attributes.hasMore()) {
@ -125,7 +125,7 @@ public class LDAPOperationManager {
* @param dn * @param dn
* @param attribute * @param attribute
*/ */
public void removeAttribute(String dn, Attribute attribute) { public void removeAttribute(LdapName dn, Attribute attribute) {
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)}; ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)};
modifyAttributes(dn, mods, null); modifyAttributes(dn, mods, null);
} }
@ -138,7 +138,7 @@ public class LDAPOperationManager {
* @param dn * @param dn
* @param attribute * @param attribute
*/ */
public void addAttribute(String dn, Attribute attribute) { public void addAttribute(LdapName dn, Attribute attribute) {
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)}; ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)};
modifyAttributes(dn, mods, null); modifyAttributes(dn, mods, null);
} }
@ -148,7 +148,7 @@ public class LDAPOperationManager {
* Removes the object from the LDAP tree * Removes the object from the LDAP tree
* </p> * </p>
*/ */
public void removeEntry(final String entryDn) { public void removeEntry(final LdapName entryDn) {
try { try {
execute(new LdapOperation<SearchResult>() { execute(new LdapOperation<SearchResult>() {
@ -185,25 +185,25 @@ public class LDAPOperationManager {
* attempt to rename to "CN=John Doe", but there is already existing "CN=John Doe", we will try "CN=John Doe0" * attempt to rename to "CN=John Doe", but there is already existing "CN=John Doe", we will try "CN=John Doe0"
* @return the non-conflicting DN, which was used in the end * @return the non-conflicting DN, which was used in the end
*/ */
public String renameEntry(String oldDn, String newDn, boolean fallback) { public LdapName renameEntry(LdapName oldDn, LdapName newDn, boolean fallback) {
try { try {
String newNonConflictingDn = execute(new LdapOperation<String>() { LdapName newNonConflictingDn = execute(new LdapOperation<LdapName>() {
@Override @Override
public String execute(LdapContext context) throws NamingException { public LdapName execute(LdapContext context) throws NamingException {
String dn = newDn; LdapName dn = newDn;
// Max 5 attempts for now // Max 5 attempts for now
int max = 5; int max = 5;
for (int i=0 ; i<max ; i++) { for (int i=0 ; i<max ; i++) {
try { try {
context.rename(new LdapName(oldDn), new LdapName(dn)); context.rename(oldDn, dn);
return dn; return dn;
} catch (NameAlreadyBoundException ex) { } catch (NameAlreadyBoundException ex) {
if (!fallback) { if (!fallback) {
throw ex; throw ex;
} else { } else {
String failedDn = dn; LdapName failedDn = dn;
if (i<max) { if (i<max) {
dn = findNextDNForFallback(newDn, i); dn = findNextDNForFallback(newDn, i);
logger.warnf("Failed to rename DN [%s] to [%s]. Will try to fallback to DN [%s]", oldDn, failedDn, dn); logger.warnf("Failed to rename DN [%s] to [%s]. Will try to fallback to DN [%s]", oldDn, failedDn, dn);
@ -234,18 +234,18 @@ public class LDAPOperationManager {
} }
} }
private String findNextDNForFallback(String newDn, int counter) { private LdapName findNextDNForFallback(LdapName newDn, int counter) {
LDAPDn dn = LDAPDn.fromString(newDn); LDAPDn dn = LDAPDn.fromLdapName(newDn);
LDAPDn.RDN firstRdn = dn.getFirstRdn(); LDAPDn.RDN firstRdn = dn.getFirstRdn();
String rdnAttrName = firstRdn.getAllKeys().get(0); String rdnAttrName = firstRdn.getAllKeys().get(0);
String rdnAttrVal = firstRdn.getAttrValue(rdnAttrName); String rdnAttrVal = firstRdn.getAttrValue(rdnAttrName);
LDAPDn parentDn = dn.getParentDn(); LDAPDn parentDn = dn.getParentDn();
parentDn.addFirst(rdnAttrName, rdnAttrVal + counter); parentDn.addFirst(rdnAttrName, rdnAttrVal + counter);
return parentDn.toString(); return parentDn.getLdapName();
} }
public List<SearchResult> search(final String baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException { public List<SearchResult> search(final LdapName baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException {
final List<SearchResult> result = new ArrayList<>(); final List<SearchResult> result = new ArrayList<>();
final SearchControls cons = getSearchControls(returningAttributes, searchScope); final SearchControls cons = getSearchControls(returningAttributes, searchScope);
@ -253,7 +253,7 @@ public class LDAPOperationManager {
return execute(new LdapOperation<List<SearchResult>>() { return execute(new LdapOperation<List<SearchResult>>() {
@Override @Override
public List<SearchResult> execute(LdapContext context) throws NamingException { public List<SearchResult> execute(LdapContext context) throws NamingException {
NamingEnumeration<SearchResult> search = context.search(new LdapName(baseDN), filter, cons); NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
while (search.hasMoreElements()) { while (search.hasMoreElements()) {
result.add(search.nextElement()); result.add(search.nextElement());
@ -284,7 +284,7 @@ public class LDAPOperationManager {
} }
} }
public List<SearchResult> searchPaginated(final String baseDN, final String filter, final LDAPQuery identityQuery) throws NamingException { public List<SearchResult> searchPaginated(final LdapName baseDN, final String filter, final LDAPQuery identityQuery) throws NamingException {
final List<SearchResult> result = new ArrayList<>(); final List<SearchResult> result = new ArrayList<>();
final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope()); final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
@ -303,7 +303,7 @@ public class LDAPOperationManager {
PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL); PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
context.setRequestControls(new Control[] { pagedControls }); context.setRequestControls(new Control[] { pagedControls });
NamingEnumeration<SearchResult> search = context.search(new LdapName(baseDN), filter, cons); NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
while (search.hasMoreElements()) { while (search.hasMoreElements()) {
result.add(search.nextElement()); result.add(search.nextElement());
@ -401,7 +401,7 @@ public class LDAPOperationManager {
return ldapIdFilter; return ldapIdFilter;
} }
public SearchResult lookupById(final String baseDN, final String id, final Collection<String> returningAttributes) { public SearchResult lookupById(final LdapName baseDN, final String id, final Collection<String> returningAttributes) {
final String filter = getFilterById(id); final String filter = getFilterById(id);
try { try {
@ -411,7 +411,7 @@ public class LDAPOperationManager {
@Override @Override
public SearchResult execute(LdapContext context) throws NamingException { public SearchResult execute(LdapContext context) throws NamingException {
NamingEnumeration<SearchResult> search = context.search(new LdapName(baseDN), filter, cons); NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
try { try {
if (search.hasMoreElements()) { if (search.hasMoreElements()) {
@ -450,21 +450,21 @@ public class LDAPOperationManager {
* *
* @param dn * @param dn
*/ */
private void destroySubcontext(LdapContext context, final String dn) { private void destroySubcontext(LdapContext context, final LdapName dn) {
try { try {
NamingEnumeration<Binding> enumeration = null; NamingEnumeration<Binding> enumeration = null;
try { try {
enumeration = context.listBindings(new LdapName(dn)); enumeration = context.listBindings(dn);
while (enumeration.hasMore()) { while (enumeration.hasMore()) {
Binding binding = enumeration.next(); Binding binding = enumeration.next();
String name = binding.getNameInNamespace(); String name = binding.getNameInNamespace();
destroySubcontext(context, name); destroySubcontext(context, new LdapName(name));
} }
context.unbind(new LdapName(dn)); context.unbind(dn);
} finally { } finally {
try { try {
enumeration.close(); enumeration.close();
@ -486,7 +486,7 @@ public class LDAPOperationManager {
* @throws AuthenticationException if authentication is not successful * @throws AuthenticationException if authentication is not successful
* *
*/ */
public void authenticate(String dn, String password) throws AuthenticationException { public void authenticate(LdapName dn, String password) throws AuthenticationException {
if (password == null || password.isEmpty()) { if (password == null || password.isEmpty()) {
throw new AuthenticationException("Empty password used"); throw new AuthenticationException("Empty password used");
@ -503,7 +503,7 @@ public class LDAPOperationManager {
if(!this.config.isStartTls()) { if(!this.config.isStartTls()) {
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, dn); env.put(Context.SECURITY_PRINCIPAL, dn.toString());
env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_CREDENTIALS, password);
} }
@ -515,7 +515,7 @@ public class LDAPOperationManager {
sslSocketFactory = provider.getSSLSocketFactory(); sslSocketFactory = provider.getSSLSocketFactory();
} }
tlsResponse = LDAPContextManager.startTLS(authCtx, "simple", dn, password.toCharArray(), sslSocketFactory); tlsResponse = LDAPContextManager.startTLS(authCtx, "simple", dn.toString(), password.toCharArray(), sslSocketFactory);
// Exception should be already thrown by LDAPContextManager.startTLS if "startTLS" could not be established, but rather do some additional check // Exception should be already thrown by LDAPContextManager.startTLS if "startTLS" could not be established, but rather do some additional check
if (tlsResponse == null) { if (tlsResponse == null) {
@ -557,7 +557,7 @@ public class LDAPOperationManager {
} }
} }
public void modifyAttributesNaming(final String dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) throws NamingException { public void modifyAttributesNaming(final LdapName dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) throws NamingException {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Modifying attributes for entry [%s]: [", dn); logger.tracef("Modifying attributes for entry [%s]: [", dn);
@ -585,7 +585,7 @@ public class LDAPOperationManager {
@Override @Override
public Void execute(LdapContext context) throws NamingException { public Void execute(LdapContext context) throws NamingException {
context.modifyAttributes(new LdapName(dn), mods); context.modifyAttributes(dn, mods);
return null; return null;
} }
@ -600,7 +600,7 @@ public class LDAPOperationManager {
}, decorator); }, decorator);
} }
public void modifyAttributes(final String dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) { public void modifyAttributes(final LdapName dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) {
try { try {
modifyAttributesNaming(dn, mods, decorator); modifyAttributesNaming(dn, mods, decorator);
} catch (NamingException e) { } catch (NamingException e) {
@ -608,7 +608,7 @@ public class LDAPOperationManager {
} }
} }
public void createSubContext(final String name, final Attributes attributes) { public void createSubContext(final LdapName name, final Attributes attributes) {
try { try {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Creating entry [%s] with attributes: [", name); logger.tracef("Creating entry [%s] with attributes: [", name);
@ -633,7 +633,7 @@ public class LDAPOperationManager {
execute(new LdapOperation<Void>() { execute(new LdapOperation<Void>() {
@Override @Override
public Void execute(LdapContext context) throws NamingException { public Void execute(LdapContext context) throws NamingException {
DirContext subcontext = context.createSubcontext(new LdapName(name), attributes); DirContext subcontext = context.createSubcontext(name, attributes);
subcontext.close(); subcontext.close();
@ -659,7 +659,7 @@ public class LDAPOperationManager {
return this.config.getUuidLDAPAttributeName(); return this.config.getUuidLDAPAttributeName();
} }
public Attributes getAttributes(final String entryUUID, final String baseDN, Set<String> returningAttributes) { public Attributes getAttributes(final String entryUUID, final LdapName baseDN, Set<String> returningAttributes) {
SearchResult search = lookupById(baseDN, entryUUID, returningAttributes); SearchResult search = lookupById(baseDN, entryUUID, returningAttributes);
if (search == null) { if (search == null) {
@ -689,10 +689,10 @@ public class LDAPOperationManager {
* @param decorator A decorator to apply to the ldap operation. * @param decorator A decorator to apply to the ldap operation.
*/ */
public void passwordModifyExtended(String dn, String password, LDAPOperationDecorator decorator) { public void passwordModifyExtended(LdapName dn, String password, LDAPOperationDecorator decorator) {
try { try {
execute(context -> { execute(context -> {
PasswordModifyRequest modifyRequest = new PasswordModifyRequest(dn, null, password); PasswordModifyRequest modifyRequest = new PasswordModifyRequest(dn.toString(), null, password);
return context.extendedOperation(modifyRequest); return context.extendedOperation(modifyRequest);
}, decorator); }, decorator);
} catch (NamingException e) { } catch (NamingException e) {

View file

@ -17,8 +17,8 @@
package org.keycloak.storage.ldap.idm.model; package org.keycloak.storage.ldap.idm.model;
import java.util.List; import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -45,7 +45,7 @@ public class LDAPDnTest {
Assert.assertFalse(dn.isDescendantOf(LDAPDn.fromString("dc=keycloakk, dc=org"))); Assert.assertFalse(dn.isDescendantOf(LDAPDn.fromString("dc=keycloakk, dc=org")));
Assert.assertFalse(dn.isDescendantOf(dn)); Assert.assertFalse(dn.isDescendantOf(dn));
Assert.assertEquals("uid", dn.getFirstRdn().getAllKeys().get(0)); MatcherAssert.assertThat(dn.getFirstRdn().getAllKeys(), Matchers.containsInAnyOrder("uid"));
Assert.assertEquals("uid=Johny\\,Depp\\+Pepp\\\\Foo", dn.getFirstRdn().toString()); Assert.assertEquals("uid=Johny\\,Depp\\+Pepp\\\\Foo", dn.getFirstRdn().toString());
Assert.assertEquals("uid=Johny,Depp+Pepp\\Foo", dn.getFirstRdn().toString(false)); Assert.assertEquals("uid=Johny,Depp+Pepp\\Foo", dn.getFirstRdn().toString(false));
Assert.assertEquals("Johny,Depp+Pepp\\Foo", dn.getFirstRdn().getAttrValue("uid")); Assert.assertEquals("Johny,Depp+Pepp\\Foo", dn.getFirstRdn().getAttrValue("uid"));
@ -56,7 +56,7 @@ public class LDAPDnTest {
LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org"); LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org");
dn.addFirst("ou", ""); dn.addFirst("ou", "");
Assert.assertEquals("ou", dn.getFirstRdn().getAllKeys().get(0)); MatcherAssert.assertThat(dn.getFirstRdn().getAllKeys(), Matchers.containsInAnyOrder("ou"));
Assert.assertEquals("", dn.getFirstRdn().getAttrValue("ou")); Assert.assertEquals("", dn.getFirstRdn().getAttrValue("ou"));
Assert.assertEquals("ou=,dc=keycloak,dc=org", dn.toString()); Assert.assertEquals("ou=,dc=keycloak,dc=org", dn.toString());
@ -86,25 +86,32 @@ public class LDAPDnTest {
dn.addFirst("cn", "Johny,Džýa "); dn.addFirst("cn", "Johny,Džýa ");
Assert.assertEquals("cn=Johny\\,Džýa\\ ,dc=keycloak,dc=org", dn.toString()); Assert.assertEquals("cn=Johny\\,Džýa\\ ,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals("Johny,Džýa ", dn.getFirstRdn().getAttrValue("cn")); Assert.assertEquals("Johny,Džýa ", dn.getFirstRdn().getAttrValue("cn"));
dn = LDAPDn.fromString("CN=Test User\\\\,OU=Users,DC=example,DC=com");
Assert.assertEquals("CN=Test User\\\\,OU=Users,DC=example,DC=com", dn.toString());
Assert.assertEquals("Test User\\", dn.getFirstRdn().getAttrValue("CN"));
dn = LDAPDn.fromString("CN=Test User\\ ,OU=Users,DC=example,DC=com");
Assert.assertEquals("CN=Test User\\ ,OU=Users,DC=example,DC=com", dn.toString());
Assert.assertEquals("Test User ", dn.getFirstRdn().getAttrValue("CN"));
} }
@Test @Test
public void testDNWithMultivaluedRDN() throws Exception { public void testDNWithMultivaluedRDN() throws Exception {
LDAPDn dn = LDAPDn.fromString("uid=john+cn=John Do\\+eř,dc=keycloak+ou=foo, dc=org"); LDAPDn dn = LDAPDn.fromString("cn=John Do\\+eř+uid=john,dc=keycloak+ou=foo, dc=org");
Assert.assertEquals("uid=john+cn=John Do\\+eř", dn.getFirstRdn().toString()); Assert.assertEquals("cn=John Do\\+eř+uid=john", dn.getFirstRdn().toString());
List<String> keys = dn.getFirstRdn().getAllKeys(); MatcherAssert.assertThat(dn.getFirstRdn().getAllKeys(), Matchers.containsInAnyOrder("uid", "cn"));
Assert.assertEquals("uid", keys.get(0));
Assert.assertEquals("cn", keys.get(1));
Assert.assertEquals("john", dn.getFirstRdn().getAttrValue("UiD")); Assert.assertEquals("john", dn.getFirstRdn().getAttrValue("UiD"));
Assert.assertEquals("John Do+eř", dn.getFirstRdn().getAttrValue("CN")); Assert.assertEquals("John Do+eř", dn.getFirstRdn().getAttrValue("CN"));
Assert.assertEquals("dc=keycloak+ou=foo,dc=org", dn.getParentDn().toString()); Assert.assertEquals("dc=keycloak+ou=foo,dc=org", dn.getParentDn().toString());
dn.getFirstRdn().setAttrValue("UID", "john2"); LDAPDn.RDN rdn = dn.getFirstRdn();
Assert.assertEquals("uid=john2+cn=John Do\\+eř", dn.getFirstRdn().toString()); rdn.setAttrValue("UID", "john2");
Assert.assertEquals("cn=John Do\\+eř+uid=john2", rdn.toString());
dn.getFirstRdn().setAttrValue("some", "somet+hing"); rdn.setAttrValue("some", "somet+hing");
Assert.assertEquals("uid=john2+cn=John Do\\+eř+some=somet\\+hing", dn.getFirstRdn().toString()); Assert.assertEquals("cn=John Do\\+eř+some=somet\\+hing+uid=john2", rdn.toString());
} }
} }

View file

@ -158,7 +158,7 @@ public class LDAPNoMSADTest extends AbstractLDAPTest {
// Assert DN was changed // Assert DN was changed
String rdnAttrName = ldapProvider.getLdapIdentityStore().getConfig().getRdnLdapAttribute(); String rdnAttrName = ldapProvider.getLdapIdentityStore().getConfig().getRdnLdapAttribute();
Assert.assertEquals(rdnAttrName + "=johnkeycloak3+sn=Doe3", john2.getDn().getFirstRdn().toString()); Assert.assertEquals("sn=Doe3+" + rdnAttrName + "=johnkeycloak3", john2.getDn().getFirstRdn().toString());
}); });
// Update some user attributes not mapped to DN. DN won't be changed // Update some user attributes not mapped to DN. DN won't be changed

View file

@ -190,7 +190,7 @@ public class UserSyncTest extends KeycloakModelTest {
LDAPObject user1LdapObject = ldapFedProvider.loadLDAPUserByUsername(realm, "user1"); LDAPObject user1LdapObject = ldapFedProvider.loadLDAPUserByUsername(realm, "user1");
LDAPOperationManager ldapOperationManager = new LDAPOperationManager(session, ldapFedProvider.getLdapIdentityStore().getConfig()); LDAPOperationManager ldapOperationManager = new LDAPOperationManager(session, ldapFedProvider.getLdapIdentityStore().getConfig());
ldapOperationManager.removeAttribute(user1LdapObject.getDn().toString(), new BasicAttribute(LDAPConstants.STREET)); ldapOperationManager.removeAttribute(user1LdapObject.getDn().getLdapName(), new BasicAttribute(LDAPConstants.STREET));
return null; return null;
}); });