Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ protected Object getAttributeValue(UserModel model, String name) {

if (permissions.hasPermission(model, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW)) {
return model.getGroupsStream()
.filter(group -> !isOrganizationGroup(group))
.filter(this::canViewGroup)
.toList();
}
Expand Down Expand Up @@ -122,4 +123,11 @@ protected UserProfile getUserProfile() {
protected boolean canViewGroup(GroupModel group) {
return session.getContext().getPermissions().hasPermission(group, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);
}

/**
* Organization groups are only accessible through the Organization API and must not be exposed through SCIM.
*/
protected static boolean isOrganizationGroup(GroupModel group) {
return GroupModel.Type.ORGANIZATION.equals(group.getType()) && group.getOrganization() != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ private static void checkUserMembershipPermission(Permissions permissions, UserM
}

private static void checkGroupMembershipPermission(Permissions permissions, GroupModel group) {
if (GroupModel.Type.ORGANIZATION.equals(group.getType()) && group.getOrganization() != null) {
if (isOrganizationGroup(group)) {
throw new ModelValidationException("Cannot access organization related group via non Organization API.");
}
if (permissions.isAdminGroup(group)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.http.simple.SimpleHttp;
Expand All @@ -21,6 +22,8 @@
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
Expand Down Expand Up @@ -1325,6 +1328,50 @@ public void testPatchCustomAttribute() {
}
}

@Test
public void testOrganizationGroupsNotExposedOnUser() {
realm.updateWithCleanup(realm -> realm.organizationsEnabled(true));

OrganizationRepresentation orgRep = new OrganizationRepresentation();
String orgName = KeycloakModelUtils.generateId();
orgRep.setName(orgName);
orgRep.setAlias(orgName);
orgRep.addDomain(new OrganizationDomainRepresentation(orgName + ".org"));
try (Response response = realm.admin().organizations().create(orgRep)) {
orgRep.setId(ApiUtil.getCreatedId(response));
}
realm.cleanup().add(realm -> realm.organizations().get(orgRep.getId()).delete().close());

OrganizationResource orgResource = realm.admin().organizations().get(orgRep.getId());

GroupRepresentation orgGroup = new GroupRepresentation();
orgGroup.setName(KeycloakModelUtils.generateId());
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
orgGroup.setId(ApiUtil.getCreatedId(response));
}

GroupRepresentation realmGroup = createGroup(KeycloakModelUtils.generateId());

User user = createUser();
user.addGroup(realmGroup.getId());
User expected = client.users().create(user);

// organization membership is itself a membership in the organization's internal group
try (Response response = orgResource.members().addMember(expected.getId())) {
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
}
orgResource.groups().group(orgGroup.getId()).addMember(expected.getId());

User actual = client.users().get(expected.getId(), List.of("groups"));
List<GroupMembership> groups = actual.getGroups();

assertNotNull(groups);
assertTrue(groups.stream().anyMatch(g -> realmGroup.getId().equals(g.getValue())));
assertTrue(groups.stream().noneMatch(g -> orgGroup.getId().equals(g.getValue())));
// neither the organization group nor the organization's internal group are exposed
assertEquals(1, groups.size());
}

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());
Expand Down
Loading