Read-Only attributes should be modified if creation is delayed for LDAP

Closes https://github.com/keycloak/keycloak/issues/16848
This commit is contained in:
rmartinc 2023-02-27 18:18:50 +01:00 committed by Marek Posolda
parent 42f66f2c6f
commit 5cdf4d5791
2 changed files with 33 additions and 4 deletions

View file

@ -550,8 +550,8 @@ public class LDAPIdentityStore implements IdentityStore {
// Ignore empty attributes on create (changetype: add) // Ignore empty attributes on create (changetype: add)
!(isCreate && attrValue.isEmpty()) && !(isCreate && attrValue.isEmpty()) &&
// Since we're extracting for saving, skip read-only attributes. ldapObject.getReadOnlyAttributeNames() are lower-cased // Skip read-only attributes for saving if not create. ldapObject.getReadOnlyAttributeNames() are lower-cased
!ldapObject.getReadOnlyAttributeNames().contains(attrNameLowercased) && (isCreate || !ldapObject.getReadOnlyAttributeNames().contains(attrNameLowercased)) &&
// Only extract RDN for create since it can't be changed on update // Only extract RDN for create since it can't be changed on update
(isCreate || !rdnAttrNamesLowerCased.contains(attrNameLowercased)) (isCreate || !rdnAttrNamesLowerCased.contains(attrNameLowercased))

View file

@ -57,6 +57,7 @@ import org.keycloak.storage.UserStorageUtil;
import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPConfig;
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.internal.LDAPQuery;
import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper; import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper;
import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory; import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory;
import org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapper; import org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapper;
@ -83,6 +84,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.naming.directory.SearchControls;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
@ -189,16 +191,33 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
Assert.assertTrue(testRealm().users().search("newuser1").isEmpty()); Assert.assertTrue(testRealm().users().search("newuser1").isEmpty());
} }
private static LDAPObject searchObjectInBase(LDAPStorageProvider ldapProvider, String dn, String... attrs) {
LDAPQuery q = new LDAPQuery(ldapProvider)
.setSearchDn(dn)
.setSearchScope(SearchControls.OBJECT_SCOPE);
if (attrs != null) {
for (String attr: attrs) {
q.addReturningLdapAttribute(attr);
}
}
return q.getFirstResult();
}
@Test @Test
public void testSyncRegistrationEmailRDNNoDefault() { public void testSyncRegistrationEmailRDNNoDefault() {
testingClient.server().run(session -> { testingClient.server().run(session -> {
// configure mail as mandatory but not forcing default // configure mail as mandatory but not forcing default and create a hardcoded attribute for description
LDAPTestContext ctx = LDAPTestContext.init(session); LDAPTestContext ctx = LDAPTestContext.init(session);
ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(ctx.getRealm()); ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(ctx.getRealm());
ComponentModel emailMapper = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ldapModel, "email"); ComponentModel emailMapper = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ldapModel, "email");
emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true");
emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.FORCE_DEFAULT_VALUE, "false"); emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.FORCE_DEFAULT_VALUE, "false");
ctx.getRealm().updateComponent(emailMapper); ctx.getRealm().updateComponent(emailMapper);
ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcodedAttr-description", ctx.getLdapModel().getId(), HardcodedLDAPAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "description",
HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "some-${RANDOM}");
ctx.getRealm().addComponentModel(hardcodedMapperModel);
}); });
try { try {
// test the user cannot be created without email // test the user cannot be created without email
@ -219,7 +238,8 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
newUser1 = testRealm().users().get(userId).toRepresentation(); newUser1 = testRealm().users().get(userId).toRepresentation();
assertFederatedUserLink(newUser1); assertFederatedUserLink(newUser1);
Assert.assertNotNull(newUser1.getAttributes().get(LDAPConstants.LDAP_ID)); Assert.assertNotNull(newUser1.getAttributes().get(LDAPConstants.LDAP_ID));
MatcherAssert.assertThat(newUser1.firstAttribute(LDAPConstants.LDAP_ENTRY_DN), Matchers.containsString("=newuser1,")); final String userDN = newUser1.firstAttribute(LDAPConstants.LDAP_ENTRY_DN);
MatcherAssert.assertThat(userDN, Matchers.containsString("=newuser1,"));
Assert.assertEquals("newuser1@keycloak.org", newUser1.getEmail()); Assert.assertEquals("newuser1@keycloak.org", newUser1.getEmail());
String emailValueInLdap = testingClient.server().fetch(session -> { String emailValueInLdap = testingClient.server().fetch(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session); LDAPTestContext ctx = LDAPTestContext.init(session);
@ -229,6 +249,13 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
return userLdap.getAttributeAsString(emailMapper.get(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE)); return userLdap.getAttributeAsString(emailMapper.get(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE));
}, String.class); }, String.class);
Assert.assertEquals("newuser1@keycloak.org", emailValueInLdap); Assert.assertEquals("newuser1@keycloak.org", emailValueInLdap);
// check description is in ldap assigned
String description = testingClient.server().fetch(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
LDAPObject ldapUser = searchObjectInBase(ctx.getLdapProvider(), userDN, "description");
return ldapUser.getAttributeAsString("description");
}, String.class);
MatcherAssert.assertThat(description, Matchers.startsWith("some-"));
// remove the created user // remove the created user
try (Response resp = testRealm().users().delete(userId)) { try (Response resp = testRealm().users().delete(userId)) {
@ -244,6 +271,8 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false"); emailMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false");
emailMapper.getConfig().remove(UserAttributeLDAPStorageMapper.FORCE_DEFAULT_VALUE); emailMapper.getConfig().remove(UserAttributeLDAPStorageMapper.FORCE_DEFAULT_VALUE);
ctx.getRealm().updateComponent(emailMapper); ctx.getRealm().updateComponent(emailMapper);
ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ctx.getLdapModel(), "hardcodedAttr-description");
ctx.getRealm().removeComponent(hardcodedMapperModel);
}); });
} }
} }