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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.LDAPObject;
|
||||
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.LDAPQueryConditionsBuilder;
|
||||
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)) {
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
Condition attrCondition = conditionsBuilder.equal(attrName, attrValue, EscapeStrategy.DEFAULT);
|
||||
Condition attrCondition = conditionsBuilder.equal(attrName, attrValue);
|
||||
ldapQuery.addWhereCondition(attrCondition);
|
||||
|
||||
ldapObjects = ldapQuery.getResultList();
|
||||
|
@ -476,6 +476,34 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
.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
|
||||
* <ul>
|
||||
|
@ -495,16 +523,16 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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 empty params: all users are returned (pagination applies)
|
||||
|
@ -531,19 +559,21 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
for (String s : search.split("\\s+")) {
|
||||
boolean equals = false;
|
||||
List<Condition> conditions = new LinkedList<>();
|
||||
if (s.startsWith("\"") && s.endsWith("\"")) {
|
||||
// exact search
|
||||
s = s.substring(1, s.length() - 1);
|
||||
equals = true;
|
||||
} else if (!s.endsWith("*")) {
|
||||
// default to prefix search
|
||||
s += "*";
|
||||
}
|
||||
|
||||
conditions.add(conditionsBuilder.equal(UserModel.USERNAME, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||
conditions.add(conditionsBuilder.equal(UserModel.EMAIL, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||
conditions.add(conditionsBuilder.equal(UserModel.FIRST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||
conditions.add(conditionsBuilder.equal(UserModel.LAST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||
conditions.add(createSearchCondition(conditionsBuilder, UserModel.USERNAME, equals, s));
|
||||
conditions.add(createSearchCondition(conditionsBuilder, UserModel.EMAIL, equals, s));
|
||||
conditions.add(createSearchCondition(conditionsBuilder, UserModel.FIRST_NAME, equals, s));
|
||||
conditions.add(createSearchCondition(conditionsBuilder, UserModel.LAST_NAME, equals, s));
|
||||
|
||||
ldapQuery.addWhereCondition(conditionsBuilder.orCondition(conditions.toArray(Condition[]::new)));
|
||||
}
|
||||
|
@ -653,7 +683,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
try (LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm)) {
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
Condition krbPrincipalCondition = conditionsBuilder.equal(kerberosPrincipalAttrName, kerberosPrincipal.toString(), EscapeStrategy.DEFAULT);
|
||||
Condition krbPrincipalCondition = conditionsBuilder.equal(kerberosPrincipalAttrName, kerberosPrincipal.toString());
|
||||
ldapQuery.addWhereCondition(krbPrincipalCondition);
|
||||
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
||||
|
||||
|
@ -936,7 +966,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
||||
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username, EscapeStrategy.DEFAULT);
|
||||
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username);
|
||||
ldapQuery.addWhereCondition(usernameCondition);
|
||||
|
||||
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
||||
|
@ -956,7 +986,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
String uuidLDAPAttributeName = this.ldapIdentityStore.getConfig().getUuidLDAPAttributeName();
|
||||
Condition usernameCondition = conditionsBuilder.equal(uuidLDAPAttributeName, uuid, EscapeStrategy.DEFAULT);
|
||||
Condition usernameCondition = conditionsBuilder.equal(uuidLDAPAttributeName, uuid);
|
||||
ldapQuery.addWhereCondition(usernameCondition);
|
||||
|
||||
return ldapQuery.getFirstResult();
|
||||
|
|
|
@ -36,11 +36,15 @@ public interface Condition {
|
|||
*/
|
||||
void updateParameterName(String modelParamName, String ldapParamName);
|
||||
|
||||
|
||||
void applyCondition(StringBuilder filter);
|
||||
|
||||
void setBinary(boolean binary);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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 é.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class EqualCondition extends NamedParameterCondition {
|
||||
|
||||
private final EscapeStrategy escapeStrategy;
|
||||
private Object value;
|
||||
|
||||
public EqualCondition(String name, Object value, EscapeStrategy escapeStrategy) {
|
||||
public EqualCondition(String name, Object value) {
|
||||
super(name);
|
||||
this.value = value;
|
||||
this.escapeStrategy = escapeStrategy;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
|
@ -42,13 +39,9 @@ public class EqualCondition extends NamedParameterCondition {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
public EscapeStrategy getEscapeStrategy() {
|
||||
return escapeStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
@ -56,7 +49,6 @@ public class EqualCondition extends NamedParameterCondition {
|
|||
return "EqualCondition{" +
|
||||
"paramName=" + getParameterName() +
|
||||
", value=" + value +
|
||||
", escapeStrategy=" + escapeStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
package org.keycloak.storage.ldap.idm.query.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||
import org.keycloak.storage.ldap.idm.query.Sort;
|
||||
|
||||
/**
|
||||
|
@ -28,11 +28,7 @@ import org.keycloak.storage.ldap.idm.query.Sort;
|
|||
public class LDAPQueryConditionsBuilder {
|
||||
|
||||
public Condition equal(String parameter, Object value) {
|
||||
return new EqualCondition(parameter, value, EscapeStrategy.DEFAULT);
|
||||
}
|
||||
|
||||
public Condition equal(String parameter, Object value, EscapeStrategy escapeStrategy) {
|
||||
return new EqualCondition(parameter, value, escapeStrategy);
|
||||
return new EqualCondition(parameter, value);
|
||||
}
|
||||
|
||||
public Condition greaterThan(String paramName, Object x) {
|
||||
|
@ -64,6 +60,13 @@ public class LDAPQueryConditionsBuilder {
|
|||
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) {
|
||||
filter = filter.trim();
|
||||
return new CustomLDAPFilter(filter);
|
||||
|
@ -73,6 +76,23 @@ public class LDAPQueryConditionsBuilder {
|
|||
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) {
|
||||
return new Sort(paramName, true);
|
||||
}
|
||||
|
|
|
@ -63,13 +63,9 @@ public abstract class NamedParameterCondition implements Condition {
|
|||
}
|
||||
|
||||
public String escapeValue(Object value) {
|
||||
return escapeValue(value, EscapeStrategy.DEFAULT);
|
||||
}
|
||||
|
||||
public String escapeValue(Object value, EscapeStrategy strategy) {
|
||||
if (Date.class.isInstance(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.representations.idm.LDAPCapabilityRepresentation;
|
||||
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.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.storage.ldap.idm.store.IdentityStore;
|
||||
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;
|
||||
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
|
||||
search = this.operationManager.searchPaginated(baseDN, filter.toString(), identityQuery);
|
||||
search = this.operationManager.searchPaginated(baseDN, condition, identityQuery);
|
||||
} 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) {
|
||||
|
@ -320,7 +320,9 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
attrs.add("supportedExtension");
|
||||
attrs.add("supportedFeatures");
|
||||
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) {
|
||||
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
|
||||
|
||||
protected StringBuilder createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
||||
StringBuilder filter = new StringBuilder();
|
||||
|
||||
for (Condition condition : identityQuery.getConditions()) {
|
||||
condition.applyCondition(filter);
|
||||
}
|
||||
|
||||
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;
|
||||
protected Condition createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
Set<Condition> conditions = new LinkedHashSet<>(identityQuery.getConditions());
|
||||
addObjectClassesConditions(conditionsBuilder, identityQuery.getObjectClasses(), conditions);
|
||||
return conditionsBuilder.andCondition(conditions.toArray(Condition[]::new));
|
||||
}
|
||||
|
||||
|
||||
private StringBuilder getObjectClassesFilter(Collection<String> objectClasses) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
private Set<Condition> addObjectClassesConditions(LDAPQueryConditionsBuilder conditionsBuilder,
|
||||
Collection<String> objectClasses, Set<Condition> conditions) {
|
||||
if (!objectClasses.isEmpty()) {
|
||||
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 {
|
||||
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
|
||||
String uuidAttrName = getConfig().getUuidLDAPAttributeName();
|
||||
|
||||
String rdn = ldapObject.getDn().getFirstRdn().toString(false);
|
||||
String filter = "(" + EscapeStrategy.DEFAULT.escape(rdn) + ")";
|
||||
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().getLdapName(), filter, Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
|
||||
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().getLdapName(),
|
||||
new LDAPQueryConditionsBuilder().present(LDAPConstants.OBJECT_CLASS),
|
||||
Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
|
||||
Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName());
|
||||
|
||||
if (id == null) {
|
||||
|
|
|
@ -24,8 +24,9 @@ import org.keycloak.models.LDAPConstants;
|
|||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
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.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.extended.PasswordModifyRequest;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
import org.keycloak.truststore.TruststoreProvider;
|
||||
|
@ -245,10 +246,10 @@ public class LDAPOperationManager {
|
|||
return parentDn.getLdapName();
|
||||
}
|
||||
|
||||
|
||||
public List<SearchResult> search(final LdapName baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException {
|
||||
public List<SearchResult> search(final LdapName baseDN, final Condition condition, Collection<String> returningAttributes, int searchScope) throws NamingException {
|
||||
final List<SearchResult> result = new ArrayList<>();
|
||||
final SearchControls cons = getSearchControls(returningAttributes, searchScope);
|
||||
final String filter = condition.toFilter();
|
||||
|
||||
try {
|
||||
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 SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
||||
final String filter = condition.toFilter();
|
||||
|
||||
// Very 1st page. Pagination context is not yet present
|
||||
if (identityQuery.getPaginationContext() == null) {
|
||||
|
@ -370,40 +372,29 @@ public class LDAPOperationManager {
|
|||
return cons;
|
||||
}
|
||||
|
||||
public String getFilterById(String id) {
|
||||
StringBuilder filter = new StringBuilder();
|
||||
filter.insert(0, "(&");
|
||||
public Condition getFilterById(String id) {
|
||||
LDAPQueryConditionsBuilder builder = new LDAPQueryConditionsBuilder();
|
||||
Condition conditionId;
|
||||
|
||||
if (this.config.isObjectGUID()) {
|
||||
byte[] objectGUID = LDAPUtil.encodeObjectGUID(id);
|
||||
filter.append("(objectClass=*)(").append(
|
||||
getUuidAttributeName()).append(LDAPConstants.EQUAL)
|
||||
.append(LDAPUtil.convertObjectGUIDToByteString(
|
||||
objectGUID)).append(")");
|
||||
|
||||
conditionId = builder.equal(getUuidAttributeName(), objectGUID);
|
||||
} else if (this.config.isEdirectoryGUID()) {
|
||||
filter.append("(objectClass=*)(").append(getUuidAttributeName().toUpperCase())
|
||||
.append(LDAPConstants.EQUAL
|
||||
).append(LDAPUtil.convertGUIDToEdirectoryHexString(id)).append(")");
|
||||
byte[] objectGUID = LDAPUtil.encodeObjectEDirectoryGUID(id);
|
||||
conditionId = builder.equal(getUuidAttributeName(), objectGUID);
|
||||
} else {
|
||||
filter.append("(objectClass=*)(").append(getUuidAttributeName()).append(LDAPConstants.EQUAL)
|
||||
.append(EscapeStrategy.DEFAULT.escape(id)).append(")");
|
||||
conditionId = builder.equal(getUuidAttributeName(), id);
|
||||
}
|
||||
|
||||
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) {
|
||||
final String filter = getFilterById(id);
|
||||
final String filter = getFilterById(id).toFilter();
|
||||
|
||||
try {
|
||||
final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.storage.ldap.idm.store.ldap;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
|
@ -131,6 +132,23 @@ public class LDAPUtil {
|
|||
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
|
||||
* Directory.</p>
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.keycloak.storage.UserStorageProvider;
|
|||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
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.LDAPQuery;
|
||||
|
||||
|
@ -229,9 +228,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
|
|||
return;
|
||||
}
|
||||
|
||||
EscapeStrategy escapeStrategy = firstNameCondition != null ? firstNameCondition.getEscapeStrategy() : lastNameCondition.getEscapeStrategy();
|
||||
|
||||
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName, escapeStrategy);
|
||||
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName);
|
||||
query.addWhereCondition(fullNameCondition);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,15 +26,6 @@ import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
|||
*/
|
||||
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
|
||||
public void testEscaping() {
|
||||
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
||||
|
|
|
@ -30,4 +30,12 @@ public class LDAPUtilTest {
|
|||
String decodeObjectGUID = LDAPUtil.decodeObjectGUID(bytes);
|
||||
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