Allow setting locale when edit mode is READ_ONLY

Closes #38981

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-04-16 03:55:30 -03:00 committed by GitHub
parent b9d38d0fe9
commit ab41366757
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 5 deletions

View file

@ -120,6 +120,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
UserProfileDecorator {
private static final Logger logger = Logger.getLogger(LDAPStorageProvider.class);
private static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1;
public static final List<String> INTERNAL_ATTRIBUTES = List.of(UserModel.LOCALE);
protected LDAPStorageProviderFactory factory;
protected KeycloakSession session;
@ -1201,6 +1202,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
// 3 - make all attributes read-only for LDAP users in case that LDAP itself is read-only
if (getEditMode() == EditMode.READ_ONLY) {
Stream.concat(metadata.getAttributes().stream(), metadatas.stream())
.filter((m) -> !INTERNAL_ATTRIBUTES.contains(m.getName()))
.forEach(attrMetadata -> attrMetadata.addWriteCondition(AttributeMetadata.ALWAYS_FALSE));
}

View file

@ -17,7 +17,8 @@
package org.keycloak.storage.ldap;
import java.util.Collections;
import static org.keycloak.storage.ldap.LDAPStorageProvider.INTERNAL_ATTRIBUTES;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -69,13 +70,15 @@ public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate {
@Override
public void setSingleAttribute(String name, String value) {
if (!Objects.equals(getAttributeStream(name).collect(Collectors.toList()), Collections.singletonList(value))) {
throw new ReadOnlyException("Federated storage is not writable");
}
setAttribute(name, List.of(value));
}
@Override
public void setAttribute(String name, List<String> values) {
if (INTERNAL_ATTRIBUTES.contains(name)) {
super.setAttribute(name, values);
return;
}
if (!Objects.equals(getAttributeStream(name).collect(Collectors.toList()), values)) {
throw new ReadOnlyException("Federated storage is not writable");
}
@ -83,6 +86,10 @@ public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate {
@Override
public void removeAttribute(String name) {
if (INTERNAL_ATTRIBUTES.contains(name)) {
super.removeAttribute(name);
return;
}
if (getAttributeStream(name).count() > 0) {
throw new ReadOnlyException("Federated storage is not writable");
}

View file

@ -18,6 +18,7 @@
package org.keycloak.testsuite.federation.ldap;
import java.util.List;
import java.util.Map;
import org.hamcrest.MatcherAssert;
import org.jboss.arquillian.graphene.page.Page;
@ -26,7 +27,6 @@ import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.AuthenticationExecutionModel;
@ -56,7 +56,10 @@ import jakarta.ws.rs.ClientErrorException;
import java.util.Collections;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@ -164,6 +167,26 @@ public class LDAPReadOnlyTest extends AbstractLDAPTest {
user.update(userRepresentation);
}
@Test
public void testUpdateLocale() {
RealmRepresentation realm = testRealm().toRepresentation();
realm.setInternationalizationEnabled(true);
testRealm().update(realm);
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "johnkeycloak");
UserRepresentation userRepresentation = user.toRepresentation();
String language = "pt_BR";
userRepresentation.setAttributes(Map.of(UserModel.LOCALE, List.of(language)));
user.update(userRepresentation);
userRepresentation = user.toRepresentation();
assertEquals(language, userRepresentation.getAttributes().get(UserModel.LOCALE).get(0));
userRepresentation.getAttributes().remove(UserModel.LOCALE);
user.update(userRepresentation);
assertNull(userRepresentation.getAttributes().get(UserModel.LOCALE));
}
// KEYCLOAK-3365
@Test(expected = ClientErrorException.class)
public void testReadOnlyUserThrowsIfChanged() {