Use conditions instead of String for filters and just use default escape strategy
Closes https://github.com/keycloak/keycloak/issues/24767 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
c8009b4627
commit
4e7bd76954
15 changed files with 319 additions and 140 deletions
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.storage.ldap;
|
package org.keycloak.storage.ldap;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -78,7 +79,6 @@ import org.keycloak.storage.adapter.UpdateOnlyChangeUserModelDelegate;
|
||||||
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
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.EscapeStrategy;
|
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
|
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
|
@ -289,7 +289,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
try (LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm)) {
|
try (LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm)) {
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
Condition attrCondition = conditionsBuilder.equal(attrName, attrValue, EscapeStrategy.DEFAULT);
|
Condition attrCondition = conditionsBuilder.equal(attrName, attrValue);
|
||||||
ldapQuery.addWhereCondition(attrCondition);
|
ldapQuery.addWhereCondition(attrCondition);
|
||||||
|
|
||||||
ldapObjects = ldapQuery.getResultList();
|
ldapObjects = ldapQuery.getResultList();
|
||||||
|
@ -476,6 +476,34 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
.map(ldapUser -> importUserFromLDAP(session, realm, ldapUser));
|
.map(ldapUser -> importUserFromLDAP(session, realm, ldapUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Condition createSearchCondition(LDAPQueryConditionsBuilder conditionsBuilder, String name, boolean equals, String value) {
|
||||||
|
if (equals) {
|
||||||
|
return conditionsBuilder.equal(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform a substring search based on *
|
||||||
|
String[] values = value.split("\\Q*\\E+", -1); // split by *
|
||||||
|
String start = null, end = null;
|
||||||
|
String[] middle = null;
|
||||||
|
if (!values[0].isEmpty()) {
|
||||||
|
start = values[0];
|
||||||
|
}
|
||||||
|
if (values.length > 1 && !values[values.length -1].isEmpty()) {
|
||||||
|
end = values[values.length - 1];
|
||||||
|
}
|
||||||
|
if (values.length > 2) {
|
||||||
|
middle = Arrays.copyOfRange(values, 1, values.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == null && middle == null && end == null) {
|
||||||
|
// just searching using empty string or *
|
||||||
|
return conditionsBuilder.present(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return proper substring search
|
||||||
|
return conditionsBuilder.substring(name, start, middle, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches LDAP using logical conjunction of params. It supports
|
* Searches LDAP using logical conjunction of params. It supports
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -495,16 +523,16 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
|
|
||||||
// Mapper should replace parameter with correct LDAP mapped attributes
|
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||||
if (attributes.containsKey(UserModel.USERNAME)) {
|
if (attributes.containsKey(UserModel.USERNAME)) {
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.USERNAME, attributes.get(UserModel.USERNAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.USERNAME, attributes.get(UserModel.USERNAME)));
|
||||||
}
|
}
|
||||||
if (attributes.containsKey(UserModel.EMAIL)) {
|
if (attributes.containsKey(UserModel.EMAIL)) {
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.EMAIL, attributes.get(UserModel.EMAIL), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.EMAIL, attributes.get(UserModel.EMAIL)));
|
||||||
}
|
}
|
||||||
if (attributes.containsKey(UserModel.FIRST_NAME)) {
|
if (attributes.containsKey(UserModel.FIRST_NAME)) {
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME)));
|
||||||
}
|
}
|
||||||
if (attributes.containsKey(UserModel.LAST_NAME)) {
|
if (attributes.containsKey(UserModel.LAST_NAME)) {
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME)));
|
||||||
}
|
}
|
||||||
// for all other searchable fields: Ignoring is the fallback option, since it may overestimate the results but does not ignore matches.
|
// for all other searchable fields: Ignoring is the fallback option, since it may overestimate the results but does not ignore matches.
|
||||||
// for empty params: all users are returned (pagination applies)
|
// for empty params: all users are returned (pagination applies)
|
||||||
|
@ -531,19 +559,21 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
for (String s : search.split("\\s+")) {
|
for (String s : search.split("\\s+")) {
|
||||||
|
boolean equals = false;
|
||||||
List<Condition> conditions = new LinkedList<>();
|
List<Condition> conditions = new LinkedList<>();
|
||||||
if (s.startsWith("\"") && s.endsWith("\"")) {
|
if (s.startsWith("\"") && s.endsWith("\"")) {
|
||||||
// exact search
|
// exact search
|
||||||
s = s.substring(1, s.length() - 1);
|
s = s.substring(1, s.length() - 1);
|
||||||
|
equals = true;
|
||||||
} else if (!s.endsWith("*")) {
|
} else if (!s.endsWith("*")) {
|
||||||
// default to prefix search
|
// default to prefix search
|
||||||
s += "*";
|
s += "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.USERNAME, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
conditions.add(createSearchCondition(conditionsBuilder, UserModel.USERNAME, equals, s));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.EMAIL, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
conditions.add(createSearchCondition(conditionsBuilder, UserModel.EMAIL, equals, s));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.FIRST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
conditions.add(createSearchCondition(conditionsBuilder, UserModel.FIRST_NAME, equals, s));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.LAST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
conditions.add(createSearchCondition(conditionsBuilder, UserModel.LAST_NAME, equals, s));
|
||||||
|
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.orCondition(conditions.toArray(Condition[]::new)));
|
ldapQuery.addWhereCondition(conditionsBuilder.orCondition(conditions.toArray(Condition[]::new)));
|
||||||
}
|
}
|
||||||
|
@ -653,7 +683,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
||||||
Condition emailCondition = conditionsBuilder.equal(UserModel.EMAIL, email, EscapeStrategy.DEFAULT);
|
Condition emailCondition = conditionsBuilder.equal(UserModel.EMAIL, email);
|
||||||
ldapQuery.addWhereCondition(emailCondition);
|
ldapQuery.addWhereCondition(emailCondition);
|
||||||
|
|
||||||
return ldapQuery.getFirstResult();
|
return ldapQuery.getFirstResult();
|
||||||
|
@ -913,7 +943,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
logger.debugf("Trying to find kerberos authenticated user [%s] in LDAP. Kerberos principal attribute is [%s]", kerberosPrincipal.toString(), kerberosPrincipalAttrName);
|
logger.debugf("Trying to find kerberos authenticated user [%s] in LDAP. Kerberos principal attribute is [%s]", kerberosPrincipal.toString(), kerberosPrincipalAttrName);
|
||||||
try (LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm)) {
|
try (LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm)) {
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
Condition krbPrincipalCondition = conditionsBuilder.equal(kerberosPrincipalAttrName, kerberosPrincipal.toString(), EscapeStrategy.DEFAULT);
|
Condition krbPrincipalCondition = conditionsBuilder.equal(kerberosPrincipalAttrName, kerberosPrincipal.toString());
|
||||||
ldapQuery.addWhereCondition(krbPrincipalCondition);
|
ldapQuery.addWhereCondition(krbPrincipalCondition);
|
||||||
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
||||||
|
|
||||||
|
@ -936,7 +966,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
||||||
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username, EscapeStrategy.DEFAULT);
|
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username);
|
||||||
ldapQuery.addWhereCondition(usernameCondition);
|
ldapQuery.addWhereCondition(usernameCondition);
|
||||||
|
|
||||||
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
||||||
|
@ -956,7 +986,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
String uuidLDAPAttributeName = this.ldapIdentityStore.getConfig().getUuidLDAPAttributeName();
|
String uuidLDAPAttributeName = this.ldapIdentityStore.getConfig().getUuidLDAPAttributeName();
|
||||||
Condition usernameCondition = conditionsBuilder.equal(uuidLDAPAttributeName, uuid, EscapeStrategy.DEFAULT);
|
Condition usernameCondition = conditionsBuilder.equal(uuidLDAPAttributeName, uuid);
|
||||||
ldapQuery.addWhereCondition(usernameCondition);
|
ldapQuery.addWhereCondition(usernameCondition);
|
||||||
|
|
||||||
return ldapQuery.getFirstResult();
|
return ldapQuery.getFirstResult();
|
||||||
|
|
|
@ -36,11 +36,15 @@ public interface Condition {
|
||||||
*/
|
*/
|
||||||
void updateParameterName(String modelParamName, String ldapParamName);
|
void updateParameterName(String modelParamName, String ldapParamName);
|
||||||
|
|
||||||
|
|
||||||
void applyCondition(StringBuilder filter);
|
void applyCondition(StringBuilder filter);
|
||||||
|
|
||||||
void setBinary(boolean binary);
|
void setBinary(boolean binary);
|
||||||
|
|
||||||
boolean isBinary();
|
boolean isBinary();
|
||||||
|
|
||||||
|
default String toFilter() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
applyCondition(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -24,42 +24,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
*/
|
*/
|
||||||
public enum EscapeStrategy {
|
public enum EscapeStrategy {
|
||||||
|
|
||||||
/**
|
|
||||||
* LDAP special character * is not escaped, other special characters are escaped. Non-ASCII characters like é are escaped.
|
|
||||||
* Use it for searches where wildcards are allowed.
|
|
||||||
*/
|
|
||||||
DEFAULT_EXCEPT_ASTERISK {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String escape(String input) {
|
|
||||||
StringBuilder output = new StringBuilder();
|
|
||||||
|
|
||||||
for (byte b : input.getBytes(StandardCharsets.UTF_8)) {
|
|
||||||
switch (b) {
|
|
||||||
case 0x5c:
|
|
||||||
output.append("\\5c"); // \
|
|
||||||
break;
|
|
||||||
case 0x28:
|
|
||||||
output.append("\\28"); // (
|
|
||||||
break;
|
|
||||||
case 0x29:
|
|
||||||
output.append("\\29"); // )
|
|
||||||
break;
|
|
||||||
case 0x00:
|
|
||||||
output.append("\\00"); // \u0000
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
appendByte(b, output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escaping of LDAP special characters including non-ASCII characters like é.
|
* Escaping of LDAP special characters including non-ASCII characters like é.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.storage.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>And condition for filters.</p>
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class AndCondition implements Condition {
|
||||||
|
|
||||||
|
private final Condition[] innerConditions;
|
||||||
|
|
||||||
|
public AndCondition(Condition... innerConditions) {
|
||||||
|
this.innerConditions = innerConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParameterName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParameterName(String parameterName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateParameterName(String modelParamName, String ldapParamName) {
|
||||||
|
for (Condition innerCondition : innerConditions) {
|
||||||
|
innerCondition.updateParameterName(modelParamName, ldapParamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyCondition(StringBuilder filter) {
|
||||||
|
filter.append("(&");
|
||||||
|
|
||||||
|
for (Condition innerCondition : innerConditions) {
|
||||||
|
innerCondition.applyCondition(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinary(boolean binary) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBinary() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,20 +18,17 @@
|
||||||
package org.keycloak.storage.ldap.idm.query.internal;
|
package org.keycloak.storage.ldap.idm.query.internal;
|
||||||
|
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
public class EqualCondition extends NamedParameterCondition {
|
public class EqualCondition extends NamedParameterCondition {
|
||||||
|
|
||||||
private final EscapeStrategy escapeStrategy;
|
|
||||||
private Object value;
|
private Object value;
|
||||||
|
|
||||||
public EqualCondition(String name, Object value, EscapeStrategy escapeStrategy) {
|
public EqualCondition(String name, Object value) {
|
||||||
super(name);
|
super(name);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.escapeStrategy = escapeStrategy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getValue() {
|
public Object getValue() {
|
||||||
|
@ -42,13 +39,9 @@ public class EqualCondition extends NamedParameterCondition {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EscapeStrategy getEscapeStrategy() {
|
|
||||||
return escapeStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyCondition(StringBuilder filter) {
|
public void applyCondition(StringBuilder filter) {
|
||||||
filter.append("(").append(getParameterName()).append(LDAPConstants.EQUAL).append(escapeValue(value, escapeStrategy)).append(")");
|
filter.append("(").append(getParameterName()).append(LDAPConstants.EQUAL).append(escapeValue(value)).append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,7 +49,6 @@ public class EqualCondition extends NamedParameterCondition {
|
||||||
return "EqualCondition{" +
|
return "EqualCondition{" +
|
||||||
"paramName=" + getParameterName() +
|
"paramName=" + getParameterName() +
|
||||||
", value=" + value +
|
", value=" + value +
|
||||||
", escapeStrategy=" + escapeStrategy +
|
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.storage.ldap.idm.query.internal;
|
package org.keycloak.storage.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.storage.ldap.idm.query.Condition;
|
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
|
||||||
import org.keycloak.storage.ldap.idm.query.Sort;
|
import org.keycloak.storage.ldap.idm.query.Sort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,11 +28,7 @@ import org.keycloak.storage.ldap.idm.query.Sort;
|
||||||
public class LDAPQueryConditionsBuilder {
|
public class LDAPQueryConditionsBuilder {
|
||||||
|
|
||||||
public Condition equal(String parameter, Object value) {
|
public Condition equal(String parameter, Object value) {
|
||||||
return new EqualCondition(parameter, value, EscapeStrategy.DEFAULT);
|
return new EqualCondition(parameter, value);
|
||||||
}
|
|
||||||
|
|
||||||
public Condition equal(String parameter, Object value, EscapeStrategy escapeStrategy) {
|
|
||||||
return new EqualCondition(parameter, value, escapeStrategy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Condition greaterThan(String paramName, Object x) {
|
public Condition greaterThan(String paramName, Object x) {
|
||||||
|
@ -64,6 +60,13 @@ public class LDAPQueryConditionsBuilder {
|
||||||
return new OrCondition(conditions);
|
return new OrCondition(conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Condition andCondition(Condition... conditions) {
|
||||||
|
if (conditions == null || conditions.length == 0) {
|
||||||
|
throw new ModelException("At least one condition should be provided to AND query");
|
||||||
|
}
|
||||||
|
return new AndCondition(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
public Condition addCustomLDAPFilter(String filter) {
|
public Condition addCustomLDAPFilter(String filter) {
|
||||||
filter = filter.trim();
|
filter = filter.trim();
|
||||||
return new CustomLDAPFilter(filter);
|
return new CustomLDAPFilter(filter);
|
||||||
|
@ -73,6 +76,23 @@ public class LDAPQueryConditionsBuilder {
|
||||||
return new InCondition(paramName, x);
|
return new InCondition(paramName, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Condition present(String paramName) {
|
||||||
|
return new PresentCondition(paramName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition substring(String paramName, String start, String[] middle, String end) {
|
||||||
|
if ((start == null || start.isEmpty())
|
||||||
|
&& (end == null || end.isEmpty())
|
||||||
|
&& (middle == null || middle.length == 0)) {
|
||||||
|
throw new ModelException("Invalid substring filter with no start, middle or end");
|
||||||
|
}
|
||||||
|
if (middle != null && middle.length > 0 && Arrays.stream(middle).filter(s -> s == null || s.isEmpty()).findAny().isPresent()) {
|
||||||
|
throw new ModelException("Invalid substring filter with an empty string in the middle array");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubstringCondition(paramName, start, middle, end);
|
||||||
|
}
|
||||||
|
|
||||||
public Sort asc(String paramName) {
|
public Sort asc(String paramName) {
|
||||||
return new Sort(paramName, true);
|
return new Sort(paramName, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,13 +63,9 @@ public abstract class NamedParameterCondition implements Condition {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String escapeValue(Object value) {
|
public String escapeValue(Object value) {
|
||||||
return escapeValue(value, EscapeStrategy.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String escapeValue(Object value, EscapeStrategy strategy) {
|
|
||||||
if (Date.class.isInstance(value)) {
|
if (Date.class.isInstance(value)) {
|
||||||
value = LDAPUtil.formatDate((Date) value);
|
value = LDAPUtil.formatDate((Date) value);
|
||||||
}
|
}
|
||||||
return new OctetStringEncoder(strategy).encode(value, isBinary());
|
return new OctetStringEncoder(EscapeStrategy.DEFAULT).encode(value, isBinary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.storage.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Present LDAP condition <em>attrname=*</em> for filters</p>
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class PresentCondition extends NamedParameterCondition {
|
||||||
|
|
||||||
|
public PresentCondition(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyCondition(StringBuilder filter) {
|
||||||
|
filter.append("(").append(getParameterName()).append("=*)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PresentCondition{"
|
||||||
|
+ "paramName=" + getParameterName()
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.storage.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Substring condition for ldap filters, <em>attrname=*some*thing*</em> for
|
||||||
|
* example. The filter is created <em>attrname=[start]*[middle1]*[middle2]*[middleN]*[end]</em>.
|
||||||
|
* At least one property (start, middle or end) should contain a non-empty
|
||||||
|
* string. The middle array should not contain any null or empty string.</p>
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class SubstringCondition extends NamedParameterCondition {
|
||||||
|
|
||||||
|
private final String start;
|
||||||
|
private final String[] middle;
|
||||||
|
private final String end;
|
||||||
|
|
||||||
|
public SubstringCondition(String name, String start, String[] middle, String end) {
|
||||||
|
super(name);
|
||||||
|
this.start = start;
|
||||||
|
this.middle = middle;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyCondition(StringBuilder filter) {
|
||||||
|
filter.append("(").append(getParameterName()).append("=");
|
||||||
|
if (start != null && !start.isEmpty()) {
|
||||||
|
filter.append(escapeValue(start));
|
||||||
|
}
|
||||||
|
filter.append("*");
|
||||||
|
if (middle != null && middle.length > 0) {
|
||||||
|
Arrays.stream(middle).forEach(s -> filter.append(escapeValue(s)).append("*"));
|
||||||
|
}
|
||||||
|
if (end != null && !end.isEmpty()) {
|
||||||
|
filter.append(escapeValue(end));
|
||||||
|
}
|
||||||
|
filter.append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PresentCondition{"
|
||||||
|
+ "paramName=" + getParameterName()
|
||||||
|
+ ", start=" + start
|
||||||
|
+ ", middle=" + (middle == null? "null" : Arrays.asList(middle))
|
||||||
|
+ ", end=" + end
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,9 +28,9 @@ 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.representations.idm.LDAPCapabilityRepresentation;
|
import org.keycloak.representations.idm.LDAPCapabilityRepresentation;
|
||||||
import org.keycloak.storage.ldap.idm.query.Condition;
|
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
|
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.storage.ldap.idm.store.IdentityStore;
|
import org.keycloak.storage.ldap.idm.store.IdentityStore;
|
||||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||||
|
|
||||||
|
@ -270,13 +270,13 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringBuilder filter = createIdentityTypeSearchFilter(identityQuery);
|
Condition condition = createIdentityTypeSearchFilter(identityQuery);
|
||||||
|
|
||||||
List<SearchResult> search;
|
List<SearchResult> search;
|
||||||
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
|
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
|
||||||
search = this.operationManager.searchPaginated(baseDN, filter.toString(), identityQuery);
|
search = this.operationManager.searchPaginated(baseDN, condition, identityQuery);
|
||||||
} else {
|
} else {
|
||||||
search = this.operationManager.search(baseDN, filter.toString(), identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
search = this.operationManager.search(baseDN, condition, identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SearchResult result : search) {
|
for (SearchResult result : search) {
|
||||||
|
@ -320,7 +320,9 @@ 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(new LdapName(Collections.emptyList()), "(objectClass=*)", Collections.unmodifiableCollection(attrs), SearchControls.OBJECT_SCOPE);
|
.search(new LdapName(Collections.emptyList()),
|
||||||
|
new LDAPQueryConditionsBuilder().present(LDAPConstants.OBJECT_CLASS),
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
|
@ -405,36 +407,25 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
protected StringBuilder createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
protected Condition createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
||||||
StringBuilder filter = new StringBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
Set<Condition> conditions = new LinkedHashSet<>(identityQuery.getConditions());
|
||||||
for (Condition condition : identityQuery.getConditions()) {
|
addObjectClassesConditions(conditionsBuilder, identityQuery.getObjectClasses(), conditions);
|
||||||
condition.applyCondition(filter);
|
return conditionsBuilder.andCondition(conditions.toArray(Condition[]::new));
|
||||||
}
|
|
||||||
|
|
||||||
filter.insert(0, "(&");
|
|
||||||
filter.append(getObjectClassesFilter(identityQuery.getObjectClasses()));
|
|
||||||
filter.append(")");
|
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.tracef("Using filter for LDAP search: %s . Searching in DN: %s", filter, identityQuery.getSearchDn());
|
|
||||||
}
|
|
||||||
return filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private StringBuilder getObjectClassesFilter(Collection<String> objectClasses) {
|
private Set<Condition> addObjectClassesConditions(LDAPQueryConditionsBuilder conditionsBuilder,
|
||||||
StringBuilder builder = new StringBuilder();
|
Collection<String> objectClasses, Set<Condition> conditions) {
|
||||||
|
|
||||||
if (!objectClasses.isEmpty()) {
|
if (!objectClasses.isEmpty()) {
|
||||||
for (String objectClass : objectClasses) {
|
for (String objectClass : objectClasses) {
|
||||||
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
|
conditions.add(conditionsBuilder.equal(LDAPConstants.OBJECT_CLASS, objectClass));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append("*").append(")");
|
conditions.add(conditionsBuilder.present(LDAPConstants.OBJECT_CLASS));
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return conditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -617,9 +608,9 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
// we need this to retrieve the entry's identifier from the ldap server
|
// we need this to retrieve the entry's identifier from the ldap server
|
||||||
String uuidAttrName = getConfig().getUuidLDAPAttributeName();
|
String uuidAttrName = getConfig().getUuidLDAPAttributeName();
|
||||||
|
|
||||||
String rdn = ldapObject.getDn().getFirstRdn().toString(false);
|
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().getLdapName(),
|
||||||
String filter = "(" + EscapeStrategy.DEFAULT.escape(rdn) + ")";
|
new LDAPQueryConditionsBuilder().present(LDAPConstants.OBJECT_CLASS),
|
||||||
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().getLdapName(), filter, Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
|
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) {
|
||||||
|
|
|
@ -24,8 +24,9 @@ import org.keycloak.models.LDAPConstants;
|
||||||
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.LDAPDn;
|
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
||||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.storage.ldap.idm.store.ldap.extended.PasswordModifyRequest;
|
import org.keycloak.storage.ldap.idm.store.ldap.extended.PasswordModifyRequest;
|
||||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||||
import org.keycloak.truststore.TruststoreProvider;
|
import org.keycloak.truststore.TruststoreProvider;
|
||||||
|
@ -245,10 +246,10 @@ public class LDAPOperationManager {
|
||||||
return parentDn.getLdapName();
|
return parentDn.getLdapName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<SearchResult> search(final LdapName baseDN, final Condition condition, 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);
|
||||||
|
final String filter = condition.toFilter();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return execute(new LdapOperation<List<SearchResult>>() {
|
return execute(new LdapOperation<List<SearchResult>>() {
|
||||||
|
@ -285,9 +286,10 @@ public class LDAPOperationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SearchResult> searchPaginated(final LdapName baseDN, final String filter, final LDAPQuery identityQuery) throws NamingException {
|
public List<SearchResult> searchPaginated(final LdapName baseDN, final Condition condition, 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());
|
||||||
|
final String filter = condition.toFilter();
|
||||||
|
|
||||||
// Very 1st page. Pagination context is not yet present
|
// Very 1st page. Pagination context is not yet present
|
||||||
if (identityQuery.getPaginationContext() == null) {
|
if (identityQuery.getPaginationContext() == null) {
|
||||||
|
@ -370,40 +372,29 @@ public class LDAPOperationManager {
|
||||||
return cons;
|
return cons;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilterById(String id) {
|
public Condition getFilterById(String id) {
|
||||||
StringBuilder filter = new StringBuilder();
|
LDAPQueryConditionsBuilder builder = new LDAPQueryConditionsBuilder();
|
||||||
filter.insert(0, "(&");
|
Condition conditionId;
|
||||||
|
|
||||||
if (this.config.isObjectGUID()) {
|
if (this.config.isObjectGUID()) {
|
||||||
byte[] objectGUID = LDAPUtil.encodeObjectGUID(id);
|
byte[] objectGUID = LDAPUtil.encodeObjectGUID(id);
|
||||||
filter.append("(objectClass=*)(").append(
|
conditionId = builder.equal(getUuidAttributeName(), objectGUID);
|
||||||
getUuidAttributeName()).append(LDAPConstants.EQUAL)
|
|
||||||
.append(LDAPUtil.convertObjectGUIDToByteString(
|
|
||||||
objectGUID)).append(")");
|
|
||||||
|
|
||||||
} else if (this.config.isEdirectoryGUID()) {
|
} else if (this.config.isEdirectoryGUID()) {
|
||||||
filter.append("(objectClass=*)(").append(getUuidAttributeName().toUpperCase())
|
byte[] objectGUID = LDAPUtil.encodeObjectEDirectoryGUID(id);
|
||||||
.append(LDAPConstants.EQUAL
|
conditionId = builder.equal(getUuidAttributeName(), objectGUID);
|
||||||
).append(LDAPUtil.convertGUIDToEdirectoryHexString(id)).append(")");
|
|
||||||
} else {
|
} else {
|
||||||
filter.append("(objectClass=*)(").append(getUuidAttributeName()).append(LDAPConstants.EQUAL)
|
conditionId = builder.equal(getUuidAttributeName(), id);
|
||||||
.append(EscapeStrategy.DEFAULT.escape(id)).append(")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getCustomUserSearchFilter() != null) {
|
if (config.getCustomUserSearchFilter() != null) {
|
||||||
filter.append(config.getCustomUserSearchFilter());
|
return builder.andCondition(new Condition[]{conditionId, builder.addCustomLDAPFilter(config.getCustomUserSearchFilter())});
|
||||||
|
} else {
|
||||||
|
return conditionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.append(")");
|
|
||||||
String ldapIdFilter = filter.toString();
|
|
||||||
|
|
||||||
logger.tracef("Using filter for lookup user by LDAP ID: %s", ldapIdFilter);
|
|
||||||
|
|
||||||
return ldapIdFilter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchResult lookupById(final LdapName 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).toFilter();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
|
final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.storage.ldap.idm.store.ldap;
|
package org.keycloak.storage.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.storage.ldap.LDAPConfig;
|
import org.keycloak.storage.ldap.LDAPConfig;
|
||||||
|
@ -131,6 +132,23 @@ public class LDAPUtil {
|
||||||
return result.toString().toUpperCase();
|
return result.toString().toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the EDirectory GUID string into the byte array.
|
||||||
|
* @param guid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] encodeObjectEDirectoryGUID(String guid) {
|
||||||
|
String withoutDash = guid.replace("-", "");
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
for (int i = 0; i < withoutDash.length(); i++) {
|
||||||
|
String byteStr = new StringBuilder().append(withoutDash.charAt(i)).append(withoutDash.charAt(++i)).toString();
|
||||||
|
result.write(Integer.parseInt(byteStr, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Encode a string representing the display value of the <code>objectGUID</code> attribute retrieved from Active
|
* <p>Encode a string representing the display value of the <code>objectGUID</code> attribute retrieved from Active
|
||||||
* Directory.</p>
|
* Directory.</p>
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||||
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.EscapeStrategy;
|
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
|
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
|
|
||||||
|
@ -229,9 +228,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EscapeStrategy escapeStrategy = firstNameCondition != null ? firstNameCondition.getEscapeStrategy() : lastNameCondition.getEscapeStrategy();
|
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName);
|
||||||
|
|
||||||
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName, escapeStrategy);
|
|
||||||
query.addWhereCondition(fullNameCondition);
|
query.addWhereCondition(fullNameCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,15 +26,6 @@ import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||||
*/
|
*/
|
||||||
public class EscapeTest {
|
public class EscapeTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEscapingExceptAsterisk() {
|
|
||||||
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
|
||||||
Assert.assertEquals(EscapeStrategy.DEFAULT_EXCEPT_ASTERISK.escape(text), "V\\c3\\a9ronique* Martin\\28john\\29second\\5cfff//eee\\00");
|
|
||||||
|
|
||||||
text = "Hi This is a test #çà";
|
|
||||||
Assert.assertEquals(EscapeStrategy.DEFAULT_EXCEPT_ASTERISK.escape(text), "Hi This is a test #\\c3\\a7\\c3\\a0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEscaping() {
|
public void testEscaping() {
|
||||||
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
||||||
|
|
|
@ -30,4 +30,12 @@ public class LDAPUtilTest {
|
||||||
String decodeObjectGUID = LDAPUtil.decodeObjectGUID(bytes);
|
String decodeObjectGUID = LDAPUtil.decodeObjectGUID(bytes);
|
||||||
Assert.assertEquals(displayGUID, decodeObjectGUID);
|
Assert.assertEquals(displayGUID, decodeObjectGUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeEDirectoryGUID() {
|
||||||
|
String guid = "bcdf4a91-ccb1-ae49-a18f-bcdf4a91ccff";
|
||||||
|
byte[] bytes = LDAPUtil.encodeObjectEDirectoryGUID(guid);
|
||||||
|
String decodeObjectGUID = LDAPUtil.decodeGuid(bytes);
|
||||||
|
Assert.assertEquals(guid, decodeObjectGUID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue