Repsect permissions configured to firstName and lastName when configured in user profile
Resolves #12109
This commit is contained in:
parent
c4001ba198
commit
3c19ad627f
3 changed files with 140 additions and 51 deletions
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")}
|
||||||
|
|
Loading…
Reference in a new issue