parent
a29c30ccd5
commit
512e30b210
4 changed files with 40 additions and 17 deletions
|
@ -431,16 +431,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.NON_ASCII_CHARS_ONLY));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.USERNAME, attributes.get(UserModel.USERNAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
}
|
}
|
||||||
if (attributes.containsKey(UserModel.EMAIL)) {
|
if (attributes.containsKey(UserModel.EMAIL)) {
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.EMAIL, attributes.get(UserModel.EMAIL), EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.EMAIL, attributes.get(UserModel.EMAIL), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
}
|
}
|
||||||
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.NON_ASCII_CHARS_ONLY));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
}
|
}
|
||||||
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.NON_ASCII_CHARS_ONLY));
|
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
}
|
}
|
||||||
// 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)
|
||||||
|
@ -476,10 +476,10 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
s += "*";
|
s += "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.USERNAME, s.trim().toLowerCase(), EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
conditions.add(conditionsBuilder.equal(UserModel.USERNAME, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.EMAIL, s.trim().toLowerCase(), EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
conditions.add(conditionsBuilder.equal(UserModel.EMAIL, s.trim().toLowerCase(), EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.FIRST_NAME, s, EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
conditions.add(conditionsBuilder.equal(UserModel.FIRST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
conditions.add(conditionsBuilder.equal(UserModel.LAST_NAME, s, EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
conditions.add(conditionsBuilder.equal(UserModel.LAST_NAME, s, EscapeStrategy.DEFAULT_EXCEPT_ASTERISK));
|
||||||
|
|
||||||
ldapQuery.addWhereCondition(conditionsBuilder.orCondition(conditions.toArray(Condition[]::new)));
|
ldapQuery.addWhereCondition(conditionsBuilder.orCondition(conditions.toArray(Condition[]::new)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,17 +24,35 @@ import java.nio.charset.StandardCharsets;
|
||||||
*/
|
*/
|
||||||
public enum EscapeStrategy {
|
public enum EscapeStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
// LDAP special characters like * ( ) \ are not escaped. Only non-ASCII characters like é are escaped
|
* LDAP special character * is not escaped, other special characters are escaped. Non-ASCII characters like é are escaped.
|
||||||
NON_ASCII_CHARS_ONLY {
|
* Use it for searches where wildcards are allowed.
|
||||||
|
*/
|
||||||
|
DEFAULT_EXCEPT_ASTERISK {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String escape(String input) {
|
public String escape(String input) {
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
for (byte b : input.getBytes(StandardCharsets.UTF_8)) {
|
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);
|
appendByte(b, output);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return output.toString();
|
return output.toString();
|
||||||
}
|
}
|
||||||
|
@ -42,7 +60,9 @@ public enum EscapeStrategy {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Escaping of LDAP special characters including non-ASCII characters like é
|
/**
|
||||||
|
* Escaping of LDAP special characters including non-ASCII characters like é.
|
||||||
|
*/
|
||||||
DEFAULT {
|
DEFAULT {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,16 +27,16 @@ import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||||
public class EscapeTest {
|
public class EscapeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoAsciiOnlyEscaping() throws Exception {
|
public void testEscapingExceptAsterisk() {
|
||||||
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
||||||
Assert.assertEquals(EscapeStrategy.NON_ASCII_CHARS_ONLY.escape(text), "V\\c3\\a9ronique* 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 #çà";
|
text = "Hi This is a test #çà";
|
||||||
Assert.assertEquals(EscapeStrategy.DEFAULT.escape(text), "Hi This is a test #\\c3\\a7\\c3\\a0");
|
Assert.assertEquals(EscapeStrategy.DEFAULT_EXCEPT_ASTERISK.escape(text), "Hi This is a test #\\c3\\a7\\c3\\a0");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEscaping() throws Exception {
|
public void testEscaping() {
|
||||||
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
String text = "Véronique* Martin(john)second\\fff//eee\u0000";
|
||||||
Assert.assertEquals(EscapeStrategy.DEFAULT.escape(text), "V\\c3\\a9ronique\\2a Martin\\28john\\29second\\5cfff//eee\\00");
|
Assert.assertEquals(EscapeStrategy.DEFAULT.escape(text), "V\\c3\\a9ronique\\2a Martin\\28john\\29second\\5cfff//eee\\00");
|
||||||
|
|
||||||
|
|
|
@ -1023,6 +1023,9 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
|
||||||
// search by a string that matches multiple fields. Should still return the one entity it matches.
|
// search by a string that matches multiple fields. Should still return the one entity it matches.
|
||||||
Assert.assertEquals(1, session.users().searchForUserStream(appRealm, "*11*").count());
|
Assert.assertEquals(1, session.users().searchForUserStream(appRealm, "*11*").count());
|
||||||
LDAPTestAsserts.assertUserImported(UserStoragePrivateUtil.userLocalStorage(session), appRealm, "username11", "John11", "Doel11", "user11@email.org", "124");
|
LDAPTestAsserts.assertUserImported(UserStoragePrivateUtil.userLocalStorage(session), appRealm, "username11", "John11", "Doel11", "user11@email.org", "124");
|
||||||
|
|
||||||
|
// search by a string that has special characters. Should succeed with an empty set, but no exceptions.
|
||||||
|
Assert.assertEquals(0, session.users().searchForUserStream(appRealm, "John)").count());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue