mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Request and exclude attributes based on the parent or schema extension
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
@@ -73,8 +73,8 @@ public abstract class AbstractModelSchema<M extends Model, R extends ResourceTyp
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(R resource, M model, List<String> attributes, List<String> excludedAttributes) {
|
||||
populateResourceType(resource, model, attributes, excludedAttributes);
|
||||
public void populate(R resource, M model, List<String> requestedAttributes, List<String> excludedAttributes) {
|
||||
populateResourceType(resource, model, requestedAttributes, excludedAttributes);
|
||||
resource.setId(model.getId());
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ public abstract class AbstractModelSchema<M extends Model, R extends ResourceTyp
|
||||
for (String name : getModelAttributeNames()) {
|
||||
Attribute<M, R> attribute = getAttributeMapperByModelAttribute(name);
|
||||
|
||||
if (attribute != null && !shouldSkipAttribute(attribute, requestedAttributes, excludedAttributes)) {
|
||||
if (attribute != null && !attribute.isExcluded(this, requestedAttributes, excludedAttributes)) {
|
||||
Object value = getAttributeValue(model, name);
|
||||
attribute.set(resource, value);
|
||||
resource.addSchema(this.id);
|
||||
@@ -236,41 +236,6 @@ public abstract class AbstractModelSchema<M extends Model, R extends ResourceTyp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given attribute should be skipped during population based on
|
||||
* the {@code returned} characteristic and the requested attribute filters.
|
||||
*/
|
||||
private boolean shouldSkipAttribute(Attribute<M, R> attribute, List<String> requestedAttributes, List<String> excludedAttributes) {
|
||||
String returned = attribute.getReturned();
|
||||
|
||||
// returned: always - never skip
|
||||
if (Attribute.RETURNED_ALWAYS.equals(returned)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returned: never - always skip
|
||||
if (Attribute.RETURNED_NEVER.equals(returned)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If attributes parameter is specified (inclusion filter)
|
||||
if (requestedAttributes != null && !requestedAttributes.isEmpty()) {
|
||||
if (requestedAttributes.stream().map(this::getAttributeByPath).noneMatch(attribute::equals)) {
|
||||
return true;
|
||||
}
|
||||
} else if (Attribute.RETURNED_REQUEST.equals(returned)) {
|
||||
// No attributes parameter specified - returned: request attributes are not returned by default
|
||||
return true;
|
||||
}
|
||||
|
||||
// If excludedAttributes parameter is specified (exclusion filter)
|
||||
if (excludedAttributes != null && !excludedAttributes.isEmpty()) {
|
||||
return excludedAttributes.stream().map(this::getAttributeByPath).anyMatch(attribute::equals);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Attribute<M, R> getAttributeMapperByModelAttribute(String name) {
|
||||
String scimName = getAttributeSchemaName(name);
|
||||
|
||||
|
||||
+60
-12
@@ -14,6 +14,8 @@ import org.keycloak.scim.resource.schema.ModelSchema;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
/**
|
||||
* Represents an attribute from a {@link ModelSchema}, its metadata and the mapper
|
||||
* that is used to map the attribute from a {@link ResourceTypeRepresentation} to a {@link Model} and vice versa.
|
||||
@@ -27,14 +29,6 @@ public class Attribute<M extends Model, R extends ResourceTypeRepresentation> {
|
||||
public static final String RETURNED_REQUEST = "request";
|
||||
public static final String RETURNED_NEVER = "never";
|
||||
|
||||
private final String alias;
|
||||
private Function<Attribute<M, R>, String> modelAttributeResolver;
|
||||
private String type;
|
||||
private String mutability;
|
||||
private String returned = RETURNED_DEFAULT;
|
||||
private boolean multivalued;
|
||||
private Class<?> complexType;
|
||||
|
||||
/**
|
||||
* Creates a simple attribute with the given {@code name}.
|
||||
*
|
||||
@@ -63,10 +57,13 @@ public class Attribute<M extends Model, R extends ResourceTypeRepresentation> {
|
||||
private final String name;
|
||||
private final AttributeMapper<M, R> mapper;
|
||||
private final String parentName;
|
||||
|
||||
private Attribute(String name, AttributeMapper<M, R> mapper, String parentName) {
|
||||
this(name, mapper, parentName, null);
|
||||
}
|
||||
private final String alias;
|
||||
private Function<Attribute<M, R>, String> modelAttributeResolver;
|
||||
private String type;
|
||||
private String mutability;
|
||||
private String returned = RETURNED_DEFAULT;
|
||||
private boolean multivalued;
|
||||
private Class<?> complexType;
|
||||
|
||||
private Attribute(String name, AttributeMapper<M, R> mapper, String parentName, String alias) {
|
||||
this.name = name;
|
||||
@@ -189,6 +186,57 @@ public class Attribute<M extends Model, R extends ResourceTypeRepresentation> {
|
||||
mapper.removeValue(model, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given attribute should be skipped during population based on
|
||||
* the {@code returned} characteristic and the requested attribute filters.
|
||||
*/
|
||||
public boolean isExcluded(ModelSchema<M, R> schema, List<String> requestedAttributes, List<String> excludedAttributes) {
|
||||
String returned = getReturned();
|
||||
|
||||
// returned: always - never skip
|
||||
if (Attribute.RETURNED_ALWAYS.equals(returned)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returned: never - always skip
|
||||
if (Attribute.RETURNED_NEVER.equals(returned)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If attributes parameter is specified (inclusion filter)
|
||||
if (requestedAttributes != null && !requestedAttributes.isEmpty()) {
|
||||
if (!isPresent(schema, requestedAttributes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Attribute.RETURNED_REQUEST.equals(returned)) {
|
||||
// No attributes parameter specified - returned: request attributes are not returned by default
|
||||
return true;
|
||||
}
|
||||
|
||||
return isPresent(schema, excludedAttributes);
|
||||
}
|
||||
|
||||
private boolean isPresent(ModelSchema<M, R> schema, List<String> names) {
|
||||
return ofNullable(names).orElse(List.of()).stream()
|
||||
.map(path -> {
|
||||
String parentName = getParentName();
|
||||
|
||||
// fallback to check if the attribute is a child of a requested attribute
|
||||
if (path.equalsIgnoreCase(parentName)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// fallback to check if the path is the scheme itself
|
||||
if (path.equalsIgnoreCase(schema.getId())) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return schema.getAttributeByPath(path);
|
||||
}).anyMatch(this::equals);
|
||||
}
|
||||
|
||||
public static class Builder<M extends Model, R extends ResourceTypeRepresentation> {
|
||||
|
||||
private final Class<?> complexType;
|
||||
|
||||
@@ -84,8 +84,8 @@ public final class GroupCoreModelSchema extends AbstractModelSchema<GroupModel,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(Group resource, GroupModel model, List<String> attributes, List<String> excludedAttributes) {
|
||||
super.populate(resource, model, attributes, excludedAttributes);
|
||||
public void populate(Group resource, GroupModel model, List<String> requestedAttributes, List<String> excludedAttributes) {
|
||||
super.populate(resource, model, requestedAttributes, excludedAttributes);
|
||||
setTimestamps(resource, model);
|
||||
}
|
||||
|
||||
|
||||
@@ -215,8 +215,8 @@ public final class UserCoreModelSchema extends AbstractUserModelSchema {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(User resource, UserModel model, List<String> attributes, List<String> excludedAttributes) {
|
||||
super.populate(resource, model, attributes, excludedAttributes);
|
||||
public void populate(User resource, UserModel model, List<String> requestedAttributes, List<String> excludedAttributes) {
|
||||
super.populate(resource, model, requestedAttributes, excludedAttributes);
|
||||
setTimestamps(resource, model);
|
||||
}
|
||||
|
||||
|
||||
@@ -198,23 +198,7 @@ public class UserTest extends AbstractScimTest {
|
||||
|
||||
@Test
|
||||
public void testCreateEnterpriseUser() {
|
||||
UPConfig configuration = realm.admin().users().userProfile().getConfiguration();
|
||||
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("department", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".department")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("division", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".division")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("costCenter", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".costCenter")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("employeeNumber", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".employeeNumber")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("organization", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".organization")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("manager", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".manager.value")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("managerName", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".manager.displayName")));
|
||||
realm.admin().users().userProfile().update(configuration);
|
||||
addEnterpriseUserUserProfileAttributes();
|
||||
|
||||
User expected = createUser();
|
||||
EnterpriseUser enterpriseUser = new EnterpriseUser();
|
||||
@@ -1048,6 +1032,7 @@ public class UserTest extends AbstractScimTest {
|
||||
|
||||
@Test
|
||||
public void testGetWithExtensionUrnAttribute() {
|
||||
addEnterpriseUserUserProfileAttributes();
|
||||
User expected = createUser();
|
||||
EnterpriseUser enterpriseUser = new EnterpriseUser();
|
||||
enterpriseUser.setEmployeeNumber("12345");
|
||||
@@ -1067,6 +1052,24 @@ public class UserTest extends AbstractScimTest {
|
||||
assertNull(actual.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWithExcludedExtensionUrnAttribute() {
|
||||
addEnterpriseUserUserProfileAttributes();
|
||||
User expected = createUser();
|
||||
EnterpriseUser enterpriseUser = new EnterpriseUser();
|
||||
enterpriseUser.setEmployeeNumber("12345");
|
||||
enterpriseUser.setDepartment("Engineering");
|
||||
expected.setEnterpriseUser(enterpriseUser);
|
||||
expected = client.users().create(expected);
|
||||
|
||||
// Requesting the extension URN should return all extension attributes
|
||||
User actual = client.users().get(expected.getId(),
|
||||
null, List.of("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"));
|
||||
assertNotNull(actual);
|
||||
assertNotNull(actual.getId());
|
||||
assertNull(actual.getEnterpriseUser());
|
||||
}
|
||||
|
||||
private static void assertGroup(List<GroupMembership> groups, GroupRepresentation group, String type) {
|
||||
assertTrue(groups.stream().anyMatch(membership -> {
|
||||
boolean found = group.getId().equals(membership.getValue()) && group.getName().equals(membership.getDisplay());
|
||||
@@ -1184,4 +1187,24 @@ public class UserTest extends AbstractScimTest {
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private void addEnterpriseUserUserProfileAttributes() {
|
||||
UPConfig configuration = realm.admin().users().userProfile().getConfiguration();
|
||||
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("department", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".department")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("division", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".division")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("costCenter", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".costCenter")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("employeeNumber", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".employeeNumber")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("organization", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".organization")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("manager", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".manager.value")));
|
||||
configuration.addOrReplaceAttribute(new UPAttribute("managerName", Map.of(
|
||||
ANNOTATION_SCIM_SCHEMA_ATTRIBUTE, ENTERPRISE_USER_SCHEMA + ".manager.displayName")));
|
||||
realm.admin().users().userProfile().update(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user