From 3c19ad627f99ff5294b5619e1edc29c4710ae09b Mon Sep 17 00:00:00 2001 From: Joerg Matysiak Date: Wed, 18 May 2022 09:25:28 +0200 Subject: [PATCH] Repsect permissions configured to firstName and lastName when configured in user profile Resolves #12109 --- .../resources/account/AccountRestService.java | 26 ++++-- ...AccountRestServiceWithUserProfileTest.java | 81 +++++++++++++++++- .../app/content/account-page/AccountPage.tsx | 84 ++++++++++--------- 3 files changed, 140 insertions(+), 51 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java index d62bc8f47e..f22fe29727 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java @@ -27,7 +27,9 @@ import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -146,23 +148,35 @@ public class AccountRestService { UserRepresentation rep = new UserRepresentation(); rep.setId(user.getId()); - rep.setUsername(user.getUsername()); - rep.setFirstName(user.getFirstName()); - rep.setLastName(user.getLastName()); - rep.setEmail(user.getEmail()); - rep.setEmailVerified(user.isEmailVerified()); UserProfileProvider provider = session.getProvider(UserProfileProvider.class); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user); rep.setAttributes(profile.getAttributes().getReadable(false)); + addReadableBuiltinAttributes(user, rep, profile.getAttributes().getReadable(true).keySet()); + if(userProfileMetadata == null || userProfileMetadata.booleanValue()) rep.setUserProfileMetadata(createUserProfileMetadata(profile)); return rep; } - + + private void addReadableBuiltinAttributes(UserModel user, UserRepresentation rep, Set readableAttributes) { + setIfReadable(UserModel.USERNAME, readableAttributes, rep::setUsername, user::getUsername); + setIfReadable(UserModel.FIRST_NAME, readableAttributes, rep::setFirstName, user::getFirstName); + setIfReadable(UserModel.LAST_NAME, readableAttributes, rep::setLastName, user::getLastName); + setIfReadable(UserModel.EMAIL, readableAttributes, rep::setEmail, user::getEmail); + // emailVerified is readable when email is readable + setIfReadable(UserModel.EMAIL, readableAttributes, rep::setEmailVerified, user::isEmailVerified); + } + + private void setIfReadable(String attributeName, Set readableAttributes, Consumer setter, Supplier getter) { + if (readableAttributes.contains(attributeName)) { + setter.accept(getter.get()); + } + } + private UserProfileMetadata createUserProfileMetadata(final UserProfile profile) { Map> am = profile.getAttributes().getReadable(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceWithUserProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceWithUserProfileTest.java index 83ed4c17e9..a97593be68 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceWithUserProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceWithUserProfileTest.java @@ -43,7 +43,6 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.forms.VerifyProfileTest; import org.keycloak.userprofile.UserProfileContext; -import org.keycloak.userprofile.EventAuditingAttributeChangeListener; /** * @@ -77,8 +76,23 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes + "{\"name\": \"attr_not_required_due_to_role\"," + PERMISSIONS_ALL + ", \"required\": {\"roles\" : [\"admin\"]}}," + "{\"name\": \"attr_readonly\"," + PERMISSIONS_ADMIN_EDITABLE + "}," + "{\"name\": \"attr_no_permission\"," + PERMISSIONS_ADMIN_ONLY + "}" - + "]}"; - + + "]}"; + + private static String UP_CONFIG_NO_ACCESS_TO_NAME_FIELDS = "{\"attributes\": [" + + "{\"name\": \"firstName\"," + PERMISSIONS_ADMIN_ONLY + ", \"required\": {}, \"displayName\": \"${profile.firstName}\", \"validations\": {\"length\": { \"max\": 255 }}}," + + "{\"name\": \"lastName\"," + PERMISSIONS_ADMIN_ONLY + ", \"required\": {}, \"displayName\": \"Last name\", \"annotations\": {\"formHintKey\" : \"userEmailFormFieldHint\", \"anotherKey\" : 10, \"yetAnotherKey\" : \"some value\"}}," + + "{\"name\": \"attr_readonly\"," + PERMISSIONS_ADMIN_EDITABLE + "}," + + "{\"name\": \"attr_no_permission\"," + PERMISSIONS_ADMIN_ONLY + "}" + + "]}"; + + private static String UP_CONFIG_RO_ACCESS_TO_NAME_FIELDS = "{\"attributes\": [" + + "{\"name\": \"firstName\"," + PERMISSIONS_ADMIN_EDITABLE + ", \"required\": {}, \"displayName\": \"${profile.firstName}\", \"validations\": {\"length\": { \"max\": 255 }}}," + + "{\"name\": \"lastName\"," + PERMISSIONS_ADMIN_EDITABLE + ", \"required\": {}, \"displayName\": \"Last name\", \"annotations\": {\"formHintKey\" : \"userEmailFormFieldHint\", \"anotherKey\" : 10, \"yetAnotherKey\" : \"some value\"}}," + + "{\"name\": \"attr_readonly\"," + PERMISSIONS_ADMIN_EDITABLE + "}," + + "{\"name\": \"attr_no_permission\"," + PERMISSIONS_ADMIN_ONLY + "}" + + "]}"; + + @Test @Override public void testGetUserProfileMetadata_EditUsernameAllowed() throws IOException { @@ -114,7 +128,66 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes assertNull(getUserProfileAttributeMetadata(user, "attr_no_permission")); } - + + @Test + public void testGetUserProfileMetadata_NoAccessToNameFields() throws IOException { + + try { + RealmRepresentation realmRep = adminClient.realm("test").toRepresentation(); + realmRep.setEditUsernameAllowed(false); + adminClient.realm("test").update(realmRep); + + setUserProfileConfiguration(UP_CONFIG_NO_ACCESS_TO_NAME_FIELDS); + + UserRepresentation user = getUser(); + assertNotNull(user.getUserProfileMetadata()); + + assertUserProfileAttributeMetadata(user, "username", "${username}", true, true); + assertUserProfileAttributeMetadata(user, "email", "${email}", true, false); + + assertNull(getUserProfileAttributeMetadata(user, "firstName")); + assertNull(getUserProfileAttributeMetadata(user, "lastName")); + assertUserProfileAttributeMetadata(user, "attr_readonly", "attr_readonly", false, true); + + assertNull(getUserProfileAttributeMetadata(user, "attr_no_permission")); + + } finally { + RealmRepresentation realmRep = testRealm().toRepresentation(); + realmRep.setEditUsernameAllowed(true); + testRealm().update(realmRep); + } + } + + @Test + public void testGetUserProfileMetadata_RoAccessToNameFields() throws IOException { + + try { + RealmRepresentation realmRep = adminClient.realm("test").toRepresentation(); + realmRep.setEditUsernameAllowed(false); + adminClient.realm("test").update(realmRep); + + setUserProfileConfiguration(UP_CONFIG_RO_ACCESS_TO_NAME_FIELDS); + + UserRepresentation user = getUser(); + assertNotNull(user.getUserProfileMetadata()); + + assertUserProfileAttributeMetadata(user, "username", "${username}", true, true); + assertUserProfileAttributeMetadata(user, "email", "${email}", true, false); + + assertUserProfileAttributeMetadata(user, "firstName", "${profile.firstName}", true, true); + assertUserProfileAttributeMetadata(user, "lastName", "Last name", true, true); + assertUserProfileAttributeMetadata(user, "attr_readonly", "attr_readonly", false, true); + + assertNull(getUserProfileAttributeMetadata(user, "attr_no_permission")); + + } finally { + RealmRepresentation realmRep = testRealm().toRepresentation(); + realmRep.setEditUsernameAllowed(true); + testRealm().update(realmRep); + } + } + + @Test @Override public void testGetUserProfileMetadata_EditUsernameDisallowed() throws IOException { diff --git a/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx b/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx index 4552882051..3a41748811 100644 --- a/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx +++ b/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx @@ -197,7 +197,7 @@ export class AccountPage extends React.Component )} - {!this.isUpdateEmailFeatureEnabled && } - - - - - + + + } + {fields.lastName != undefined && - + > + + + } {features.isInternationalizationEnabled && (