mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Change locale of user profile validation message to be resolved from authenticated user instead of validated user
Closes #19707
This commit is contained in:
parent
feb20de2ef
commit
5554c62bea
@ -92,7 +92,6 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
@ -186,7 +185,7 @@ public class UserResource {
|
||||
|
||||
UserProfile profile = session.getProvider(UserProfileProvider.class).create(USER_API, attributes, user);
|
||||
|
||||
Response response = validateUserProfile(profile, user, session);
|
||||
Response response = validateUserProfile(profile, session, auth.adminAuth());
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
@ -215,9 +214,9 @@ public class UserResource {
|
||||
logger.warn("Could not update user!", me);
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw ErrorResponse.error("Could not update user!", Status.BAD_REQUEST);
|
||||
} catch (ForbiddenException fe) {
|
||||
} catch (ForbiddenException | ErrorResponseException e) {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw fe;
|
||||
throw e;
|
||||
} catch (Exception me) { // JPA
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
logger.warn("Could not update user!", me);// may be committed by JTA which can't
|
||||
@ -225,14 +224,15 @@ public class UserResource {
|
||||
}
|
||||
}
|
||||
|
||||
public static Response validateUserProfile(UserProfile profile, UserModel user, KeycloakSession session) {
|
||||
public static Response validateUserProfile(UserProfile profile, KeycloakSession session, AdminAuth adminAuth) {
|
||||
try {
|
||||
profile.validate();
|
||||
} catch (ValidationException pve) {
|
||||
List<ErrorRepresentation> errors = new ArrayList<>();
|
||||
AdminMessageFormatter adminMessageFormatter = createAdminMessageFormatter(session, adminAuth);
|
||||
|
||||
for (ValidationException.Error error : pve.getErrors()) {
|
||||
errors.add(new ErrorRepresentation(error.getFormattedMessage(new AdminMessageFormatter(session, user))));
|
||||
errors.add(new ErrorRepresentation(error.getFormattedMessage(adminMessageFormatter)));
|
||||
}
|
||||
|
||||
throw ErrorResponse.errors(errors, Status.BAD_REQUEST);
|
||||
@ -241,6 +241,13 @@ public class UserResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AdminMessageFormatter createAdminMessageFormatter(KeycloakSession session, AdminAuth adminAuth) {
|
||||
// the authenticated user is used to resolve the locale for the messages. It can be null.
|
||||
UserModel authenticatedUser = adminAuth == null ? null : adminAuth.getUser();
|
||||
|
||||
return new AdminMessageFormatter(session, authenticatedUser);
|
||||
}
|
||||
|
||||
public static void updateUserFromRep(UserProfile profile, UserModel user, UserRepresentation rep, KeycloakSession session, boolean isUpdateExistingUser) {
|
||||
boolean removeMissingRequiredActions = isUpdateExistingUser;
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ public class UsersResource {
|
||||
UserProfile profile = profileProvider.create(USER_API, rep.toAttributes());
|
||||
|
||||
try {
|
||||
Response response = UserResource.validateUserProfile(profile, null, session);
|
||||
Response response = UserResource.validateUserProfile(profile, session, auth.adminAuth());
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
package org.keycloak.testsuite.admin;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@ -7,28 +13,38 @@ import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
@ -36,12 +52,20 @@ import org.keycloak.userprofile.UserProfileProvider;
|
||||
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
|
||||
public class DeclarativeUserTest extends AbstractAdminTest {
|
||||
|
||||
private static final String LOCALE_ATTR_KEY = "locale";
|
||||
private static final String TEST_REALM_USER_MANAGER_NAME = "test-realm-user-manager";
|
||||
private static final String REQUIRED_ATTR_KEY = "required-attr";
|
||||
|
||||
private Keycloak testRealmUserManagerClient;
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
RealmRepresentation realmRep = this.realm.toRepresentation();
|
||||
public void onBefore() throws Exception {
|
||||
RealmRepresentation realmRep = realm.toRepresentation();
|
||||
realmRep.setInternationalizationEnabled(true);
|
||||
realmRep.setSupportedLocales(new HashSet<>(Arrays.asList("en", "de")));
|
||||
enableDynamicUserProfile(realmRep);
|
||||
this.realm.update(realmRep);
|
||||
setUserProfileConfiguration(this.realm, "{\"attributes\": ["
|
||||
realm.update(realmRep);
|
||||
setUserProfileConfiguration(realm, "{\"attributes\": ["
|
||||
+ "{\"name\": \"username\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"firstName\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"email\", " + PERMISSIONS_ALL + "},"
|
||||
@ -51,6 +75,50 @@ public class DeclarativeUserTest extends AbstractAdminTest {
|
||||
+ "{\"name\": \"custom-hidden\"},"
|
||||
+ "{\"name\": \"attr1\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"attr2\", " + PERMISSIONS_ALL + "}]}");
|
||||
|
||||
UserRepresentation testRealmUserManager = UserBuilder.create().username(TEST_REALM_USER_MANAGER_NAME)
|
||||
.password(TEST_REALM_USER_MANAGER_NAME).build();
|
||||
String createdUserId = null;
|
||||
try (Response response = realm.users().create(testRealmUserManager)) {
|
||||
createdUserId = ApiUtil.getCreatedId(response);
|
||||
} catch (WebApplicationException e) {
|
||||
// it's ok when the user has already been created for a previous test
|
||||
assertThat(e.getResponse().getStatus(), equalTo(409));
|
||||
}
|
||||
|
||||
if (createdUserId != null) {
|
||||
List<ClientRepresentation> foundClients = realm.clients().findByClientId("realm-management");
|
||||
assertThat(foundClients, hasSize(1));
|
||||
ClientRepresentation realmManagementClient = foundClients.get(0);
|
||||
|
||||
RoleRepresentation manageUsersRole =
|
||||
realm.clients().get(realmManagementClient.getId()).roles().get("manage-users").toRepresentation();
|
||||
assertThat(manageUsersRole, notNullValue());
|
||||
|
||||
realm.users().get(createdUserId).roles().clientLevel(realmManagementClient.getId())
|
||||
.add(Collections.singletonList(manageUsersRole));
|
||||
}
|
||||
|
||||
ClientRepresentation testApp = new ClientRepresentation();
|
||||
testApp.setClientId("test-app");
|
||||
testApp.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
testApp.setSecret("secret");
|
||||
try (Response response = realm.clients().create(testApp)) {
|
||||
ApiUtil.getCreatedId(response);
|
||||
} catch (WebApplicationException e) {
|
||||
// it's ok when the client has already been created for a previous test
|
||||
assertThat(e.getResponse().getStatus(), equalTo(409));
|
||||
}
|
||||
|
||||
testRealmUserManagerClient = AdminClientUtil.createAdminClient(true, realmRep.getRealm(),
|
||||
TEST_REALM_USER_MANAGER_NAME, TEST_REALM_USER_MANAGER_NAME, testApp.getClientId(), testApp.getSecret());
|
||||
}
|
||||
|
||||
@After
|
||||
public void closeClient() {
|
||||
if (testRealmUserManagerClient != null) {
|
||||
testRealmUserManagerClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -148,12 +216,12 @@ public class DeclarativeUserTest extends AbstractAdminTest {
|
||||
+ "{\"name\": \"firstName\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"email\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"lastName\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"attr1\", \"required\": {}, " + PERMISSIONS_ALL + "}]}");
|
||||
+ "{\"name\": \"" + REQUIRED_ATTR_KEY + "\", \"required\": {}, " + PERMISSIONS_ALL + "}]}");
|
||||
|
||||
UserRepresentation user1 = new UserRepresentation();
|
||||
user1.setUsername("user1");
|
||||
// set an attribute to later remove it from the configuration
|
||||
user1.singleAttribute("attr1", "some-value");
|
||||
user1.singleAttribute(REQUIRED_ATTR_KEY, "some-value");
|
||||
String user1Id = createUser(user1);
|
||||
|
||||
UserResource userResource = realm.users().get(user1Id);
|
||||
@ -161,23 +229,106 @@ public class DeclarativeUserTest extends AbstractAdminTest {
|
||||
user1.setFirstName("changed");
|
||||
user1.setAttributes(null);
|
||||
|
||||
// do not validate attr1 because the attribute list is not provided and the user has the attribute
|
||||
// do not validate REQUIRED_ATTR_KEY because the attribute list is not provided and the user has the attribute
|
||||
userResource.update(user1);
|
||||
user1 = userResource.toRepresentation();
|
||||
assertEquals("changed", user1.getFirstName());
|
||||
|
||||
try {
|
||||
user1.setAttributes(Collections.emptyMap());
|
||||
userResource.update(user1);
|
||||
fail("Should fail because the attribute attr1 is required");
|
||||
} catch (BadRequestException ignore) {
|
||||
user1.setAttributes(Collections.emptyMap());
|
||||
String expectedErrorMessage = String.format("Please specify attribute %s.", REQUIRED_ATTR_KEY);
|
||||
verifyUserUpdateFails(realm.users(), user1Id, user1, expectedErrorMessage);
|
||||
}
|
||||
|
||||
private void verifyUserUpdateFails(UsersResource usersResource, String userId, UserRepresentation user,
|
||||
String expectedErrorMessage) {
|
||||
UserResource userResource = usersResource.get(userId);
|
||||
try {
|
||||
userResource.update(user);
|
||||
fail("Should fail with errorMessage: " + expectedErrorMessage);
|
||||
} catch (BadRequestException badRequest) {
|
||||
try (Response response = badRequest.getResponse()) {
|
||||
assertThat(response.getStatus(), equalTo(400));
|
||||
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
|
||||
assertThat(error.getErrorMessage(), equalTo(expectedErrorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validationErrorMessagesCanBeConfiguredWithRealmLocalization() {
|
||||
try {
|
||||
setUserProfileConfiguration(this.realm, "{\"attributes\": ["
|
||||
+ "{\"name\": \"username\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"firstName\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"email\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"lastName\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"" + LOCALE_ATTR_KEY + "\", " + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"" + REQUIRED_ATTR_KEY + "\", \"required\": {}, " + PERMISSIONS_ALL + "}]}");
|
||||
|
||||
realm.localization().saveRealmLocalizationText("en", "error-user-attribute-required",
|
||||
"required-error en: {0}");
|
||||
getCleanup().addLocalization("en");
|
||||
realm.localization().saveRealmLocalizationText("de", "error-user-attribute-required",
|
||||
"required-error de: {0}");
|
||||
getCleanup().addLocalization("de");
|
||||
|
||||
UsersResource testRealmUserManagerClientUsersResource =
|
||||
testRealmUserManagerClient.realm(REALM_NAME).users();
|
||||
|
||||
// start with locale en
|
||||
changeTestRealmUserManagerLocale("en");
|
||||
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername("user-realm-localization");
|
||||
user.singleAttribute(REQUIRED_ATTR_KEY, "some-value");
|
||||
String userId = createUser(user);
|
||||
|
||||
user.setAttributes(new HashMap<>());
|
||||
verifyUserUpdateFails(testRealmUserManagerClientUsersResource, userId, user,
|
||||
"required-error en: " + REQUIRED_ATTR_KEY);
|
||||
|
||||
// switch to locale de
|
||||
changeTestRealmUserManagerLocale("de");
|
||||
|
||||
user.singleAttribute(REQUIRED_ATTR_KEY, "some-value");
|
||||
realm.users().get(userId).update(user);
|
||||
|
||||
user.setAttributes(new HashMap<>());
|
||||
verifyUserUpdateFails(testRealmUserManagerClientUsersResource, userId, user,
|
||||
"required-error de: " + REQUIRED_ATTR_KEY);
|
||||
} finally {
|
||||
changeTestRealmUserManagerLocale(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeTestRealmUserManagerLocale(String locale) {
|
||||
UsersResource testRealmUserManagerUsersResource = testRealmUserManagerClient.realm(REALM_NAME).users();
|
||||
|
||||
List<UserRepresentation> foundUsers =
|
||||
testRealmUserManagerUsersResource.search(TEST_REALM_USER_MANAGER_NAME, true);
|
||||
assertThat(foundUsers, hasSize(1));
|
||||
UserRepresentation user = foundUsers.iterator().next();
|
||||
|
||||
if (locale == null) {
|
||||
Map<String, List<String>> attributes = user.getAttributes();
|
||||
if (attributes != null) {
|
||||
attributes.remove(LOCALE_ATTR_KEY);
|
||||
}
|
||||
} else {
|
||||
user.singleAttribute(LOCALE_ATTR_KEY, locale);
|
||||
}
|
||||
|
||||
// also set REQUIRED_ATTR_KEY, when not already set, otherwise the change will be rejected
|
||||
if (StringUtil.isBlank(user.firstAttribute(REQUIRED_ATTR_KEY))) {
|
||||
user.singleAttribute(REQUIRED_ATTR_KEY, "arbitrary-value");
|
||||
}
|
||||
|
||||
testRealmUserManagerUsersResource.get(user.getId()).update(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultUserProfileProviderIsActive() {
|
||||
getTestingClient().server(TEST_REALM_NAME).run(session -> {
|
||||
getTestingClient().server(REALM_NAME).run(session -> {
|
||||
Set<UserProfileProvider> providers = session.getAllProviders(UserProfileProvider.class);
|
||||
assertThat(providers, notNullValue());
|
||||
assertThat(providers.isEmpty(), is(false));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user