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.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -146,23 +148,35 @@ public class AccountRestService {
UserRepresentation rep = new UserRepresentation(); UserRepresentation rep = new UserRepresentation();
rep.setId(user.getId()); 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); UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user);
rep.setAttributes(profile.getAttributes().getReadable(false)); rep.setAttributes(profile.getAttributes().getReadable(false));
addReadableBuiltinAttributes(user, rep, profile.getAttributes().getReadable(true).keySet());
if(userProfileMetadata == null || userProfileMetadata.booleanValue()) if(userProfileMetadata == null || userProfileMetadata.booleanValue())
rep.setUserProfileMetadata(createUserProfileMetadata(profile)); rep.setUserProfileMetadata(createUserProfileMetadata(profile));
return rep; 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) { private UserProfileMetadata createUserProfileMetadata(final UserProfile profile) {
Map<String, List<String>> am = profile.getAttributes().getReadable(); 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.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest; import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.userprofile.UserProfileContext; 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_not_required_due_to_role\"," + PERMISSIONS_ALL + ", \"required\": {\"roles\" : [\"admin\"]}},"
+ "{\"name\": \"attr_readonly\"," + PERMISSIONS_ADMIN_EDITABLE + "}," + "{\"name\": \"attr_readonly\"," + PERMISSIONS_ADMIN_EDITABLE + "},"
+ "{\"name\": \"attr_no_permission\"," + PERMISSIONS_ADMIN_ONLY + "}" + "{\"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 @Test
@Override @Override
public void testGetUserProfileMetadata_EditUsernameAllowed() throws IOException { public void testGetUserProfileMetadata_EditUsernameAllowed() throws IOException {
@ -114,7 +128,66 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
assertNull(getUserProfileAttributeMetadata(user, "attr_no_permission")); 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 @Test
@Override @Override
public void testGetUserProfileMetadata_EditUsernameDisallowed() throws IOException { public void testGetUserProfileMetadata_EditUsernameDisallowed() throws IOException {

View file

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