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:
parent
a2dd0f31c5
commit
d10ccc7245
10 changed files with 206 additions and 246 deletions
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue