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 @@ -40,6 +40,7 @@
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;

import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.client.clienttype.ClientTypeManager;
Expand Down Expand Up @@ -75,6 +76,7 @@
import org.keycloak.models.jpa.entities.ClientScopeEntity;
import org.keycloak.models.jpa.entities.GroupAttributeEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.GroupRoleMappingEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RealmLocalizationTextsEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
Expand Down Expand Up @@ -750,14 +752,30 @@ public Long getGroupsCountByNameContaining(RealmModel realm, String search) {

@Override
public Stream<GroupModel> getGroupsByRoleStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
TypedQuery<GroupEntity> query = em.createNamedQuery("groupsInRole", GroupEntity.class);
query.setParameter("roleId", role.getId());
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);

queryBuilder.select(root.get("id"));
Comment thread
vramik marked this conversation as resolved.

Subquery<String> roleMappingSubquery = queryBuilder.subquery(String.class);
Root<GroupRoleMappingEntity> roleMappingRoot = roleMappingSubquery.from(GroupRoleMappingEntity.class);
roleMappingSubquery.select(roleMappingRoot.get("group").get("id"));
roleMappingSubquery.where(builder.equal(roleMappingRoot.get("roleId"), role.getId()));

List<Predicate> predicates = new ArrayList<>();

Stream<GroupEntity> results = paginateQuery(query, firstResult, maxResults).getResultStream();
predicates.add(root.get("id").in(roleMappingSubquery));
predicates.add(builder.equal(root.get("realm"), realm.getId()));
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));

queryBuilder.where(predicates.toArray(new Predicate[0]));
queryBuilder.orderBy(builder.asc(root.get("name")));

return closing(results
.map(g -> (GroupModel) new GroupAdapter(session, realm, em, g))
.sorted(GroupModel.COMPARE_BY_NAME));
return closing(paginateQuery(em.createQuery(queryBuilder), firstResult, maxResults).getResultStream()
.map(g -> session.groups().getGroupById(realm, g))
.filter(Objects::nonNull));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="groupsInRole", query="select g from GroupRoleMappingEntity m, GroupEntity g where m.roleId=:roleId and g=m.group"),
@NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
@NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
@NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,8 @@ public Stream<GroupRepresentation> getGroupsInRole(final @Parameter(description
@Parameter(description = "If false, return a full representation of the {@code GroupRepresentation} objects.") @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {

auth.roles().requireView(roleContainer);
auth.groups().requireList();

firstResult = firstResult != null ? firstResult : 0;
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
Expand Down Expand Up @@ -480,4 +483,70 @@ public void testManageClientWithAuthorizationSettings() {
myResourceServer.setName("somethingNew");
clientResource.update(myResourceServer);
}

@Test
public void testRoleGroupMembersFilteredByGroupPermissions() {
ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
String myclientId = myclient.getId();

// create a client role
Comment thread
vramik marked this conversation as resolved.
RoleRepresentation role = new RoleRepresentation();
role.setName("visible-role");
role.setClientRole(true);
realm.admin().clients().get(myclientId).roles().create(role);
role = realm.admin().clients().get(myclientId).roles().get("visible-role").toRepresentation();

// create two groups and assign the role to both
GroupRepresentation visibleGroup = createGroup("visible-group");
GroupRepresentation hiddenGroup = createGroup("hidden-group");

realm.admin().groups().group(visibleGroup.getId()).roles().clientLevel(myclientId).add(List.of(role));
realm.admin().groups().group(hiddenGroup.getId()).roles().clientLevel(myclientId).add(List.of(role));

// create a realm role and assign it to both groups
RoleRepresentation realmRole = new RoleRepresentation();
realmRole.setName("visible-realm-role");
realm.admin().roles().create(realmRole);
realmRole = realm.admin().roles().get("visible-realm-role").toRepresentation();

realm.admin().groups().group(visibleGroup.getId()).roles().realmLevel().add(List.of(realmRole));
realm.admin().groups().group(hiddenGroup.getId()).roles().realmLevel().add(List.of(realmRole));

// grant limited-admin view permission on the client only
UserPolicyRepresentation policy = createUserPolicy(realm, adminPermissionsClient, "Only My Admin User Policy", myadmin.getId());
createPermission(adminPermissionsClient, myclientId, clientsType, Set.of(VIEW), policy);

// grant view permission on visible-group only
createGroupPermission(visibleGroup, Set.of(AdminPermissionsSchema.VIEW), policy);

// grant view-realm role so the limited admin can access realm-level roles endpoint
String realmMgmtClientId = realm.admin().clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0).getId();
RoleRepresentation viewRealmRole = realm.admin().clients().get(realmMgmtClientId).roles().get(AdminRoles.VIEW_REALM).toRepresentation();
realm.admin().users().get(myadmin.getId()).roles().clientLevel(realmMgmtClientId).add(List.of(viewRealmRole));
realm.cleanup().add(r -> r.users().get(myadmin.getId()).roles().clientLevel(realmMgmtClientId).remove(List.of(viewRealmRole)));

// limited admin can view the client and its role
realmAdminClient.realm(realm.getName()).clients().get(myclientId).toRepresentation();
realmAdminClient.realm(realm.getName()).clients().get(myclientId).roles().get("visible-role").toRepresentation();

// limited admin cannot directly access hidden-group
try {
realmAdminClient.realm(realm.getName()).groups().group(hiddenGroup.getId()).toRepresentation();
fail("Should not be able to access hidden group directly");
} catch (ForbiddenException expected) {
}

// client role group members should only return the visible group, not the hidden one
Set<GroupRepresentation> roleGroups = realmAdminClient.realm(realm.getName()).clients().get(myclientId)
.roles().get("visible-role").getRoleGroupMembers();
assertThat(roleGroups, hasSize(1));
assertEquals("visible-group", roleGroups.iterator().next().getName());

// realm role group members should also be filtered by group permissions
Set<GroupRepresentation> realmRoleGroups = realmAdminClient.realm(realm.getName()).roles()
.get("visible-realm-role").getRoleGroupMembers();
assertThat(realmRoleGroups, hasSize(1));
assertEquals("visible-group", realmRoleGroups.iterator().next().getName());
}
}
Loading