Repsect permissions configured to firstName and lastName when configured in user profile

Resolves #12109
This commit is contained in:
Joerg Matysiak 2022-05-18 09:25:28 +02:00 committed by Pedro Igor
parent c4001ba198
commit 3c19ad627f
3 changed files with 140 additions and 51 deletions

View file

@ -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<String> 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 <T> void setIfReadable(String attributeName, Set<String> readableAttributes, Consumer<T> setter, Supplier<T> getter) {
if (readableAttributes.contains(attributeName)) {
setter.accept(getter.get());
}
}
private UserProfileMetadata createUserProfileMetadata(final UserProfile profile) {
Map<String, List<String>> am = profile.getAttributes().getReadable();

View file

@ -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 {

View file

@ -197,7 +197,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
)}
</FormGroup>
)}
{!this.isUpdateEmailFeatureEnabled && <FormGroup
{!this.isUpdateEmailFeatureEnabled && fields.email != undefined && <FormGroup
label={Msg.localize('email')}
fieldId="email-address"
helperTextInvalid={this.state.errors.email}
@ -250,56 +250,58 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
}
</InputGroup>
</FormGroup> }
<FormGroup
label={Msg.localize("firstName")}
fieldId="first-name"
helperTextInvalid={this.state.errors.firstName}
validated={
this.state.errors.firstName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
>
<TextInput
isRequired
type="text"
id="first-name"
name="firstName"
maxLength={254}
value={fields.firstName}
onChange={this.handleChange}
{ fields.firstName != undefined && <FormGroup
label={Msg.localize("firstName")}
fieldId="first-name"
helperTextInvalid={this.state.errors.firstName}
validated={
this.state.errors.firstName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
></TextInput>
</FormGroup>
<FormGroup
label={Msg.localize("lastName")}
fieldId="last-name"
helperTextInvalid={this.state.errors.lastName}
validated={
this.state.errors.lastName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
>
<TextInput
isRequired
type="text"
id="last-name"
name="lastName"
maxLength={254}
value={fields.lastName}
onChange={this.handleChange}
>
<TextInput
isRequired
type="text"
id="first-name"
name="firstName"
maxLength={254}
value={fields.firstName}
onChange={this.handleChange}
validated={
this.state.errors.firstName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
></TextInput>
</FormGroup>
}
{fields.lastName != undefined && <FormGroup
label={Msg.localize("lastName")}
fieldId="last-name"
helperTextInvalid={this.state.errors.lastName}
validated={
this.state.errors.lastName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
></TextInput>
</FormGroup>
>
<TextInput
isRequired
type="text"
id="last-name"
name="lastName"
maxLength={254}
value={fields.lastName}
onChange={this.handleChange}
validated={
this.state.errors.lastName !== ""
? ValidatedOptions.error
: ValidatedOptions.default
}
></TextInput>
</FormGroup>
}
{features.isInternationalizationEnabled && (
<FormGroup
label={Msg.localize("selectLocale")}