Merge pull request #3637 from mposolda/master
KEYCLOAK-2545 KEYCLOAK-3668 KEYCLOAK-3247 LDAP escaping
This commit is contained in:
commit
efcb749771
17 changed files with 459 additions and 36 deletions
|
@ -73,8 +73,10 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
|
|||
|
||||
@Override
|
||||
public void reset(KeycloakDeployment deployment) {
|
||||
sendRequest(deployment);
|
||||
lastRequestTime = Time.currentTime();
|
||||
synchronized (this) {
|
||||
sendRequest(deployment);
|
||||
lastRequestTime = Time.currentTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.storage.StorageId;
|
|||
import org.keycloak.storage.UserStorageProvider;
|
||||
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;
|
||||
|
@ -312,17 +313,27 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
List<LDAPObject> results = new ArrayList<LDAPObject>();
|
||||
if (attributes.containsKey(UserModel.USERNAME)) {
|
||||
LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(UserModel.USERNAME));
|
||||
if (user != null) {
|
||||
results.add(user);
|
||||
}
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
// Mapper should replace "username" in parameter name with correct LDAP mapped attribute
|
||||
Condition usernameCondition = conditionsBuilder.equal(UserModel.USERNAME, attributes.get(UserModel.USERNAME), EscapeStrategy.NON_ASCII_CHARS_ONLY);
|
||||
ldapQuery.addWhereCondition(usernameCondition);
|
||||
|
||||
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
|
||||
results.addAll(ldapObjects);
|
||||
}
|
||||
|
||||
if (attributes.containsKey(UserModel.EMAIL)) {
|
||||
LDAPObject user = queryByEmail(realm, attributes.get(UserModel.EMAIL));
|
||||
if (user != null) {
|
||||
results.add(user);
|
||||
}
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
||||
Condition emailCondition = conditionsBuilder.equal(UserModel.EMAIL, attributes.get(UserModel.EMAIL), EscapeStrategy.NON_ASCII_CHARS_ONLY);
|
||||
ldapQuery.addWhereCondition(emailCondition);
|
||||
|
||||
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
|
||||
results.addAll(ldapObjects);
|
||||
}
|
||||
|
||||
if (attributes.containsKey(UserModel.FIRST_NAME) || attributes.containsKey(UserModel.LAST_NAME)) {
|
||||
|
@ -331,10 +342,10 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||
if (attributes.containsKey(UserModel.FIRST_NAME)) {
|
||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME)));
|
||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME), EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
||||
}
|
||||
if (attributes.containsKey(UserModel.LAST_NAME)) {
|
||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME)));
|
||||
ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME), EscapeStrategy.NON_ASCII_CHARS_ONLY));
|
||||
}
|
||||
|
||||
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
|
||||
|
@ -405,7 +416,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);
|
||||
Condition emailCondition = conditionsBuilder.equal(UserModel.EMAIL, email, EscapeStrategy.DEFAULT);
|
||||
ldapQuery.addWhereCondition(emailCondition);
|
||||
|
||||
return ldapQuery.getFirstResult();
|
||||
|
@ -624,7 +635,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
||||
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username);
|
||||
Condition usernameCondition = conditionsBuilder.equal(usernameMappedAttribute, username, EscapeStrategy.DEFAULT);
|
||||
ldapQuery.addWhereCondition(usernameCondition);
|
||||
|
||||
LDAPObject ldapUser = ldapQuery.getFirstResult();
|
||||
|
@ -636,7 +647,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
public LDAPStorageMapper getMapper(ComponentModel mapperModel) {
|
||||
LDAPStorageMapper ldapMapper = (LDAPStorageMapper) getSession().getProvider(LDAPStorageMapper.class, mapperModel);
|
||||
LDAPStorageMapper ldapMapper = getSession().getProvider(LDAPStorageMapper.class, mapperModel);
|
||||
if (ldapMapper == null) {
|
||||
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId());
|
||||
}
|
||||
|
|
|
@ -189,9 +189,8 @@ public class LDAPUtils {
|
|||
* @param memberAttrName usually 'member'
|
||||
* @param ldapParent role or group
|
||||
* @param ldapChild usually user (or child group or child role)
|
||||
* @param sendLDAPUpdateRequest if true, the method will send LDAP update request too. Otherwise it will skip it
|
||||
*/
|
||||
public static void deleteMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild, boolean sendLDAPUpdateRequest) {
|
||||
public static void deleteMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild) {
|
||||
Set<String> memberships = getExistingMemberships(memberAttrName, ldapParent);
|
||||
|
||||
String userMembership = getMemberValueOfChildObject(ldapChild, membershipType);
|
||||
|
|
|
@ -88,7 +88,7 @@ public class LDAPDn {
|
|||
*/
|
||||
public String getFirstRdn() {
|
||||
Entry firstEntry = entries.getFirst();
|
||||
return firstEntry.attrName + "=" + firstEntry.attrValue;
|
||||
return firstEntry.attrName + "=" + unescapeValue(firstEntry.attrValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,13 @@ public class LDAPDn {
|
|||
*/
|
||||
public String getFirstRdnAttrValue() {
|
||||
Entry firstEntry = entries.getFirst();
|
||||
return firstEntry.attrValue;
|
||||
String dnEscaped = firstEntry.attrValue;
|
||||
return unescapeValue(dnEscaped);
|
||||
}
|
||||
|
||||
private String unescapeValue(String escaped) {
|
||||
// Something needed to handle non-String types?
|
||||
return Rdn.unescapeValue(escaped).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2016 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;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public enum EscapeStrategy {
|
||||
|
||||
|
||||
// LDAP special characters like * ( ) \ are not escaped. Only non-ASCII characters like é are escaped
|
||||
NON_ASCII_CHARS_ONLY {
|
||||
|
||||
@Override
|
||||
public String escape(String input) {
|
||||
try {
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
for (byte b : input.getBytes("UTF-8")) {
|
||||
appendByte(b, output);
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException(uee);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
// Escaping of LDAP special characters including non-ASCII characters like é
|
||||
DEFAULT {
|
||||
|
||||
|
||||
@Override
|
||||
public String escape(String input) {
|
||||
try {
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
for (byte b : input.getBytes("UTF-8")) {
|
||||
switch (b) {
|
||||
case 0x5c:
|
||||
output.append("\\5c"); // \
|
||||
break;
|
||||
case 0x2a:
|
||||
output.append("\\2a"); // *
|
||||
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();
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException(uee);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
public abstract String escape(String input);
|
||||
|
||||
|
||||
protected void appendByte(byte b, StringBuilder output) {
|
||||
if (b >= 0) {
|
||||
output.append((char) b);
|
||||
} else {
|
||||
int i = -256 ^ b;
|
||||
output.append("\\").append(Integer.toHexString(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.storage.ldap.idm.query.internal;
|
||||
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil;
|
||||
|
||||
import java.util.Date;
|
||||
|
@ -28,16 +29,22 @@ import java.util.Date;
|
|||
public class EqualCondition extends NamedParameterCondition {
|
||||
|
||||
private final Object value;
|
||||
private final EscapeStrategy escapeStrategy;
|
||||
|
||||
public EqualCondition(String name, Object value) {
|
||||
public EqualCondition(String name, Object value, EscapeStrategy escapeStrategy) {
|
||||
super(name);
|
||||
this.value = value;
|
||||
this.escapeStrategy = escapeStrategy;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public EscapeStrategy getEscapeStrategy() {
|
||||
return escapeStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyCondition(StringBuilder filter) {
|
||||
Object parameterValue = value;
|
||||
|
@ -45,7 +52,9 @@ public class EqualCondition extends NamedParameterCondition {
|
|||
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
|
||||
}
|
||||
|
||||
filter.append("(").append(getParameterName()).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
|
||||
String escaped = escapeStrategy.escape(parameterValue.toString());
|
||||
|
||||
filter.append("(").append(getParameterName()).append(LDAPConstants.EQUAL).append(escaped).append(")");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +62,7 @@ public class EqualCondition extends NamedParameterCondition {
|
|||
return "EqualCondition{" +
|
||||
"paramName=" + getParameterName() +
|
||||
", value=" + value +
|
||||
", escapeStrategy=" + escapeStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.storage.ldap.idm.query.internal;
|
|||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -27,7 +28,11 @@ import org.keycloak.storage.ldap.idm.query.Sort;
|
|||
public class LDAPQueryConditionsBuilder {
|
||||
|
||||
public Condition equal(String parameter, Object value) {
|
||||
return new EqualCondition(parameter, value);
|
||||
return new EqualCondition(parameter, value, EscapeStrategy.DEFAULT);
|
||||
}
|
||||
|
||||
public Condition equal(String parameter, Object value, EscapeStrategy escapeStrategy) {
|
||||
return new EqualCondition(parameter, value, escapeStrategy);
|
||||
}
|
||||
|
||||
public Condition greaterThan(String paramName, Object x) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.storage.ldap.LDAPConfig;
|
|||
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.EqualCondition;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.idm.store.IdentityStore;
|
||||
|
@ -408,7 +409,10 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
try {
|
||||
// we need this to retrieve the entry's identifier from the ldap server
|
||||
String uuidAttrName = getConfig().getUuidLDAPAttributeName();
|
||||
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().toString(), "(" + ldapObject.getDn().getFirstRdn() + ")", Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
|
||||
|
||||
String rdn = ldapObject.getDn().getFirstRdn();
|
||||
String filter = "(" + EscapeStrategy.DEFAULT.escape(rdn) + ")";
|
||||
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().toString(), filter, Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
|
||||
Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName());
|
||||
|
||||
if (id == null) {
|
||||
|
|
|
@ -25,6 +25,7 @@ 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;
|
||||
|
||||
|
@ -164,7 +165,10 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
|
|||
} else {
|
||||
return;
|
||||
}
|
||||
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName);
|
||||
|
||||
EscapeStrategy escapeStrategy = firstNameCondition!=null ? firstNameCondition.getEscapeStrategy() : lastNameCondition.getEscapeStrategy();
|
||||
|
||||
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName, escapeStrategy);
|
||||
query.addWhereCondition(fullNameCondition);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.storage.ldap.LDAPUtils;
|
|||
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.mappers.membership.group.GroupLDAPStorageMapper;
|
||||
|
@ -101,7 +102,7 @@ public enum MembershipType {
|
|||
Condition[] orSubconditions = new Condition[dns.size()];
|
||||
int index = 0;
|
||||
for (LDAPDn userDn : dns) {
|
||||
Condition condition = conditionsBuilder.equal(userDn.getFirstRdnAttrName(), userDn.getFirstRdnAttrValue());
|
||||
Condition condition = conditionsBuilder.equal(userDn.getFirstRdnAttrName(), userDn.getFirstRdnAttrValue(), EscapeStrategy.DEFAULT);
|
||||
orSubconditions[index] = condition;
|
||||
index++;
|
||||
}
|
||||
|
|
|
@ -462,7 +462,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
for (LDAPDn toRemoveDN : toRemoveSubgroupsDNs) {
|
||||
LDAPObject fakeGroup = new LDAPObject();
|
||||
fakeGroup.setDn(toRemoveDN);
|
||||
LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, config.getMembershipLdapAttribute(), ldapGroup, fakeGroup, false);
|
||||
LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, config.getMembershipLdapAttribute(), ldapGroup, fakeGroup);
|
||||
}
|
||||
|
||||
// Update group to LDAP
|
||||
|
@ -501,7 +501,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
}
|
||||
|
||||
public void deleteGroupMappingInLDAP(LDAPObject ldapUser, LDAPObject ldapGroup) {
|
||||
LDAPUtils.deleteMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), ldapGroup, ldapUser, true);
|
||||
LDAPUtils.deleteMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), ldapGroup, ldapUser);
|
||||
}
|
||||
|
||||
protected List<LDAPObject> getLDAPGroupMappings(LDAPObject ldapUser) {
|
||||
|
|
|
@ -256,7 +256,7 @@ public class RoleLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
}
|
||||
|
||||
public void deleteRoleMappingInLDAP(LDAPObject ldapUser, LDAPObject ldapRole) {
|
||||
LDAPUtils.deleteMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), ldapRole, ldapUser, true);
|
||||
LDAPUtils.deleteMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), ldapRole, ldapUser);
|
||||
}
|
||||
|
||||
public LDAPObject loadLDAPRoleByName(String roleName) {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2016 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.model;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class EscapeTest {
|
||||
|
||||
@Test
|
||||
public void testNoAsciiOnlyEscaping() throws Exception {
|
||||
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");
|
||||
|
||||
text = "Hi This is a test #çà";
|
||||
Assert.assertEquals(EscapeStrategy.DEFAULT.escape(text), "Hi This is a test #\\c3\\a7\\c3\\a0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscaping() throws Exception {
|
||||
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");
|
||||
|
||||
text = "Hi This is a test #çà";
|
||||
Assert.assertEquals(EscapeStrategy.DEFAULT.escape(text), "Hi This is a test #\\c3\\a7\\c3\\a0");
|
||||
|
||||
}
|
||||
}
|
|
@ -31,9 +31,9 @@ public class LDAPDnTest {
|
|||
dn.addFirst("ou", "People");
|
||||
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.toString());
|
||||
|
||||
dn.addFirst("uid", "Johny,Depp+Pepp");
|
||||
Assert.assertEquals("uid=Johny\\,Depp\\+Pepp,ou=People,dc=keycloak,dc=org", dn.toString());
|
||||
Assert.assertEquals(LDAPDn.fromString("uid=Johny\\,Depp\\+Pepp,ou=People,dc=keycloak,dc=org"), dn);
|
||||
dn.addFirst("uid", "Johny,Depp+Pepp\\Foo");
|
||||
Assert.assertEquals("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org", dn.toString());
|
||||
Assert.assertEquals(LDAPDn.fromString("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org"), dn);
|
||||
|
||||
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
|
||||
|
||||
|
@ -44,6 +44,6 @@ public class LDAPDnTest {
|
|||
Assert.assertFalse(dn.isDescendantOf(dn));
|
||||
|
||||
Assert.assertEquals("uid", dn.getFirstRdnAttrName());
|
||||
Assert.assertEquals("Johny\\,Depp\\+Pepp", dn.getFirstRdnAttrValue());
|
||||
Assert.assertEquals("Johny,Depp+Pepp\\Foo", dn.getFirstRdnAttrValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ public class LDAPGroupMapperSyncTest {
|
|||
Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName));
|
||||
|
||||
// Cleanup - remove recursive mapping in LDAP
|
||||
LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true);
|
||||
LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1);
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
|
|
|
@ -64,7 +64,19 @@ public class LDAPGroupMapperTest {
|
|||
private static ComponentModel ldapModel = null;
|
||||
private static String descriptionAttrName = null;
|
||||
|
||||
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||
|
||||
static class GroupTestKeycloakSetup extends KeycloakRule.KeycloakSetup {
|
||||
|
||||
private final LDAPRule ldapRule;
|
||||
|
||||
ComponentModel ldapModel = null;
|
||||
String descriptionAttrName = null;
|
||||
|
||||
|
||||
public GroupTestKeycloakSetup(LDAPRule ldapRule) {
|
||||
this.ldapRule = ldapRule;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
|
@ -97,12 +109,13 @@ public class LDAPGroupMapperTest {
|
|||
LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
|
||||
LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");
|
||||
LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description");
|
||||
LDAPObject groupSpecialCharacters = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group-spec,ia*l_characžter)s", descriptionAttrName, "group-special-characters");
|
||||
|
||||
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group11, false);
|
||||
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true);
|
||||
|
||||
// Sync LDAP groups to Keycloak DB
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
|
||||
new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm);
|
||||
|
||||
// Delete all LDAP users
|
||||
|
@ -121,8 +134,22 @@ public class LDAPGroupMapperTest {
|
|||
LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
|
||||
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
|
||||
|
||||
LDAPObject james2 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jamees,key*cložak)ppp", "James2", "Brown2", "james2@email.org", null, "89102");
|
||||
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james2, "Password1");
|
||||
|
||||
postSetup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
void postSetup() {
|
||||
LDAPGroupMapperTest.ldapModel = this.ldapModel;
|
||||
LDAPGroupMapperTest.descriptionAttrName = this.descriptionAttrName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static KeycloakRule keycloakRule = new KeycloakRule(new GroupTestKeycloakSetup(ldapRule));
|
||||
|
||||
@ClassRule
|
||||
public static TestRule chain = RuleChain
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright 2016 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.testsuite.federation.storage.ldap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.RegisterPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.LDAPRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MASTER;
|
||||
import static org.keycloak.models.AdminRoles.ADMIN;
|
||||
import static org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPSpecialCharsTest {
|
||||
|
||||
private static LDAPRule ldapRule = new LDAPRule();
|
||||
|
||||
static ComponentModel ldapModel = null;
|
||||
static String descriptionAttrName = null;
|
||||
|
||||
|
||||
private static KeycloakRule keycloakRule = new KeycloakRule(new LDAPGroupMapperTest.GroupTestKeycloakSetup(ldapRule) {
|
||||
|
||||
@Override
|
||||
protected void postSetup() {
|
||||
LDAPSpecialCharsTest.ldapModel = this.ldapModel;
|
||||
LDAPSpecialCharsTest.descriptionAttrName = this.descriptionAttrName;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ClassRule
|
||||
public static TestRule chain = RuleChain
|
||||
.outerRule(ldapRule)
|
||||
.around(keycloakRule);
|
||||
|
||||
|
||||
protected Keycloak adminClient;
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected AppPage appPage;
|
||||
|
||||
@WebResource
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
adminClient = Keycloak.getInstance(AUTH_SERVER_ROOT, MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
adminClient.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test01_userSearch() {
|
||||
List<UserRepresentation> users = adminClient.realm("test").users().search("j*", 0, 10);
|
||||
Assert.assertEquals(3, users.size());
|
||||
|
||||
List<String> usernames = users.stream().map((UserRepresentation user) -> {
|
||||
|
||||
return user.getUsername();
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
Collections.sort(usernames);
|
||||
|
||||
Assert.assertEquals("jamees,key*cložak)ppp", usernames.get(0));
|
||||
Assert.assertEquals("jameskeycloak", usernames.get(1));
|
||||
Assert.assertEquals("johnkeycloak", usernames.get(2));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test02_loginWithSpecialCharacter() {
|
||||
// Fail login with wildcard
|
||||
loginPage.open();
|
||||
loginPage.login("john*", "Password1");
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
// Fail login with wildcard
|
||||
loginPage.login("j*", "Password1");
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
// Success login as username exactly match
|
||||
loginPage.login("jamees,key*cložak)ppp", "Password1");
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test03_specialCharUserJoiningSpecialCharGroup() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
|
||||
LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
|
||||
appRealm.updateComponent(mapperModel);
|
||||
|
||||
UserModel specialUser = session.users().getUserByUsername("jamees,key*cložak)ppp", appRealm);
|
||||
Assert.assertNotNull(specialUser);
|
||||
|
||||
// 1 - Grant some groups in LDAP
|
||||
|
||||
// This group should already exists as it was imported from LDAP
|
||||
GroupModel specialGroup = KeycloakModelUtils.findGroupByPath(appRealm, "/group-spec,ia*l_characžter)s");
|
||||
Assert.assertNotNull(specialGroup);
|
||||
|
||||
specialUser.joinGroup(specialGroup);
|
||||
|
||||
// 3 - Check that group mappings are in LDAP and hence available through federation
|
||||
|
||||
Set<GroupModel> userGroups = specialUser.getGroups();
|
||||
Assert.assertEquals(1, userGroups.size());
|
||||
Assert.assertTrue(userGroups.contains(specialGroup));
|
||||
|
||||
// 4 - Check through userProvider
|
||||
List<UserModel> groupMembers = session.users().getGroupMembers(appRealm, specialGroup, 0, 10);
|
||||
|
||||
Assert.assertEquals(1, groupMembers.size());
|
||||
Assert.assertEquals("jamees,key*cložak)ppp", groupMembers.get(0).getUsername());
|
||||
|
||||
// 4 - Delete some group mappings and check they are deleted
|
||||
|
||||
specialUser.leaveGroup(specialGroup);
|
||||
|
||||
userGroups = specialUser.getGroups();
|
||||
Assert.assertEquals(0, userGroups.size());
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue