Do not expose kc.org attribute in user representations

Closes #31143

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-07-09 13:54:30 -03:00 committed by Alexander Schwartz
parent b41f3599ea
commit d475833361
4 changed files with 15 additions and 14 deletions

View file

@ -248,6 +248,7 @@ public class ModelToRepresentation {
} }
if (attributes != null && !copy.isEmpty()) { if (attributes != null && !copy.isEmpty()) {
Map<String, List<String>> attrs = new HashMap<>(copy); Map<String, List<String>> attrs = new HashMap<>(copy);
attrs.remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
rep.setAttributes(attrs); rep.setAttributes(attrs);
} }

View file

@ -49,6 +49,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.ModelIllegalStateException; import org.keycloak.models.ModelIllegalStateException;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
@ -1075,6 +1076,7 @@ public class UserResource {
attributes.remove(UserModel.USERNAME); attributes.remove(UserModel.USERNAME);
attributes.remove(UserModel.EMAIL); attributes.remove(UserModel.EMAIL);
attributes.remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
return attributes.entrySet().stream() return attributes.entrySet().stream()
.filter(entry -> ofNullable(entry.getValue()).orElse(emptyList()).stream().anyMatch(StringUtil::isNotBlank)) .filter(entry -> ofNullable(entry.getValue()).orElse(emptyList()).stream().anyMatch(StringUtil::isNotBlank))

View file

@ -358,13 +358,8 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
metadata.addAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE, -1, metadata.addAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE, -1,
new AttributeValidatorMetadata(OrganizationMemberValidator.ID), new AttributeValidatorMetadata(OrganizationMemberValidator.ID),
new AttributeValidatorMetadata(ImmutableAttributeValidator.ID)) new AttributeValidatorMetadata(ImmutableAttributeValidator.ID))
.addReadCondition(c -> USER_API.equals(c.getContext())) .addReadCondition((c) -> false)
.addWriteCondition(context -> { .addWriteCondition((c) -> false);
// the attribute can only be managed within the scope of the Organization API
// we assume, for now, that if the organization is set as a session attribute, we are operating within the scope if the Organization API
KeycloakSession session = context.getSession();
return session.getAttribute(OrganizationModel.class.getName()) != null;
});
} }
} }

View file

@ -40,12 +40,13 @@ import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import java.io.IOException; import java.io.IOException;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationMemberResource; import org.keycloak.admin.client.resource.OrganizationMemberResource;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile.Feature; import org.keycloak.common.Profile.Feature;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -90,7 +91,6 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
testRealm().users().userProfile().update(upConfig); testRealm().users().userProfile().update(upConfig);
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
UserRepresentation expected = addMember(organization); UserRepresentation expected = addMember(organization);
List<String> expectedOrganizations = expected.getAttributes().get(ORGANIZATION_ATTRIBUTE);
expected.singleAttribute(ORGANIZATION_ATTRIBUTE, "invalid"); expected.singleAttribute(ORGANIZATION_ATTRIBUTE, "invalid");
@ -109,11 +109,14 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
expected.getAttributes().remove(ORGANIZATION_ATTRIBUTE); expected.getAttributes().remove(ORGANIZATION_ATTRIBUTE);
userResource.update(expected); userResource.update(expected);
expected = userResource.toRepresentation(); expected = userResource.toRepresentation();
assertThat(expected.getAttributes().get(ORGANIZATION_ATTRIBUTE), Matchers.containsInAnyOrder(expectedOrganizations.toArray())); assertNull(expected.getAttributes());
getTestingClient().server(TEST_REALM_NAME).run(OrganizationMemberTest::assertMembersHaveOrgAttribute);
}
userResource.update(expected); private static void assertMembersHaveOrgAttribute(KeycloakSession session) {
expected = userResource.toRepresentation(); OrganizationModel organization = session.getProvider(OrganizationProvider.class).getByDomainName("neworg.org");
assertThat(expected.getAttributes().get(ORGANIZATION_ATTRIBUTE), Matchers.containsInAnyOrder(expectedOrganizations.toArray())); assertTrue(session.getProvider(OrganizationProvider.class).getMembersStream(organization, null, false, -1, -1).
anyMatch(userModel -> userModel.getAttributes().getOrDefault(ORGANIZATION_ATTRIBUTE, List.of()).contains(organization.getId())));
} }
@Test @Test
@ -281,7 +284,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
UserRepresentation expected = addMember(organization); UserRepresentation expected = addMember(organization);
assertNotNull(expected.getAttributes()); assertNotNull(expected.getAttributes());
assertTrue(expected.getAttributes().get(ORGANIZATION_ATTRIBUTE).contains(organization.toRepresentation().getId())); assertNull(expected.getAttributes().get(ORGANIZATION_ATTRIBUTE));
OrganizationMemberResource member = organization.members().member(expected.getId()); OrganizationMemberResource member = organization.members().member(expected.getId());
try (Response response = member.delete()) { try (Response response = member.delete()) {