KEYCLOAK-18940 Add support for searching composite roles
This commit is contained in:
parent
64717f650b
commit
da0c945475
17 changed files with 403 additions and 7 deletions
|
@ -27,6 +27,7 @@ import javax.ws.rs.PUT;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -63,6 +64,14 @@ public interface RoleByIdResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Set<RoleRepresentation> getRoleComposites(@PathParam("role-id") String id);
|
Set<RoleRepresentation> getRoleComposites(@PathParam("role-id") String id);
|
||||||
|
|
||||||
|
@Path("{role-id}/composites")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<RoleRepresentation> searchRoleComposites(@PathParam("role-id") String id,
|
||||||
|
@QueryParam("search") String search,
|
||||||
|
@QueryParam("first") Integer first,
|
||||||
|
@QueryParam("max") Integer max);
|
||||||
|
|
||||||
@Path("{role-id}/composites/realm")
|
@Path("{role-id}/composites/realm")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -701,6 +701,11 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||||
return getRoleDelegate().getRealmRolesStream(realm, first, max);
|
return getRoleDelegate().getRealmRolesStream(realm, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
|
||||||
|
return getRoleDelegate().getRolesStream(realm, ids, search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
|
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
|
||||||
return getRoleDelegate().getClientRolesStream(client, first, max);
|
return getRoleDelegate().getClientRolesStream(client, first, max);
|
||||||
|
|
|
@ -143,6 +143,13 @@ public class RoleAdapter implements RoleModel {
|
||||||
return composites.stream();
|
return composites.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getCompositesStream(String search, Integer first, Integer max) {
|
||||||
|
if (isUpdated()) return updated.getCompositesStream(search, first, max);
|
||||||
|
|
||||||
|
return cacheSession.getRoleDelegate().getRolesStream(realm, cached.getComposites().stream(), search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isClientRole() {
|
public boolean isClientRole() {
|
||||||
return cached instanceof CachedClientRole;
|
return cached instanceof CachedClientRole;
|
||||||
|
|
|
@ -302,6 +302,26 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
return getRolesStream(query, realm, first, max);
|
return getRolesStream(query, realm, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
|
||||||
|
if (ids == null) return Stream.empty();
|
||||||
|
|
||||||
|
TypedQuery<String> query;
|
||||||
|
|
||||||
|
if (search == null) {
|
||||||
|
query = em.createNamedQuery("getRoleIdsFromIdList", String.class);
|
||||||
|
} else {
|
||||||
|
query = em.createNamedQuery("getRoleIdsByNameContainingFromIdList", String.class)
|
||||||
|
.setParameter("search", search);
|
||||||
|
}
|
||||||
|
|
||||||
|
query.setParameter("realm", realm.getId())
|
||||||
|
.setParameter("ids", ids.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
return closing(paginateQuery(query, first, max).getResultStream())
|
||||||
|
.map(g -> session.roles().getRoleById(realm, g));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
|
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
|
||||||
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
|
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
|
||||||
|
|
|
@ -113,6 +113,13 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
|
||||||
return composites.filter(Objects::nonNull);
|
return composites.filter(Objects::nonNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getCompositesStream(String search, Integer first, Integer max) {
|
||||||
|
return session.roles().getRolesStream(realm,
|
||||||
|
getEntity().getCompositeRoles().stream().map(RoleEntity::getId),
|
||||||
|
search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasRole(RoleModel role) {
|
public boolean hasRole(RoleModel role) {
|
||||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
|
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
|
||||||
|
|
|
@ -63,6 +63,8 @@ import java.util.Set;
|
||||||
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realmId = :realm"),
|
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realmId = :realm"),
|
||||||
@NamedQuery(name="getRealmRoleIdByName", query="select role.id from RoleEntity role where role.clientRole = false and role.name = :name and role.realmId = :realm"),
|
@NamedQuery(name="getRealmRoleIdByName", query="select role.id from RoleEntity role where role.clientRole = false and role.name = :name and role.realmId = :realm"),
|
||||||
@NamedQuery(name="searchForRealmRoles", query="select role from RoleEntity role where role.clientRole = false and role.realmId = :realm and ( lower(role.name) like :search or lower(role.description) like :search ) order by role.name"),
|
@NamedQuery(name="searchForRealmRoles", query="select role from RoleEntity role where role.clientRole = false and role.realmId = :realm and ( lower(role.name) like :search or lower(role.description) like :search ) order by role.name"),
|
||||||
|
@NamedQuery(name="getRoleIdsFromIdList", query="select role.id from RoleEntity role where role.realmId = :realm and role.id in :ids order by role.name ASC"),
|
||||||
|
@NamedQuery(name="getRoleIdsByNameContainingFromIdList", query="select role.id from RoleEntity role where role.realmId = :realm and lower(role.name) like lower(concat('%',:search,'%')) and role.id in :ids order by role.name ASC"),
|
||||||
})
|
})
|
||||||
|
|
||||||
public class RoleEntity {
|
public class RoleEntity {
|
||||||
|
|
|
@ -431,6 +431,11 @@ public class MapRealmProvider implements RealmProvider {
|
||||||
return session.roles().getRealmRolesStream(realm, first, max);
|
return session.roles().getRealmRolesStream(realm, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
|
||||||
|
return session.roles().getRolesStream(realm, ids, search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean removeRole(RoleModel role) {
|
public boolean removeRole(RoleModel role) {
|
||||||
|
|
|
@ -76,15 +76,21 @@ public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements
|
||||||
.filter(Objects::nonNull);
|
.filter(Objects::nonNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getCompositesStream(String search, Integer first, Integer max) {
|
||||||
|
LOG.tracef("%% (%s).getCompositesStream(%s, %d, %d):%d - %s", this, search, first, max, entity.getCompositeRoles().size(), getShortStackTrace());
|
||||||
|
return session.roles().getRolesStream(realm, entity.getCompositeRoles().stream(), search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCompositeRole(RoleModel role) {
|
public void addCompositeRole(RoleModel role) {
|
||||||
LOG.tracef("%s(%s).addCompositeRole(%s(%s))%s", entity.getName(), entity.getId(), role.getName(), role.getId(), getShortStackTrace());
|
LOG.tracef("(%s).addCompositeRole(%s(%s))%s", this, role.getName(), role.getId(), getShortStackTrace());
|
||||||
entity.addCompositeRole(role.getId());
|
entity.addCompositeRole(role.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeCompositeRole(RoleModel role) {
|
public void removeCompositeRole(RoleModel role) {
|
||||||
LOG.tracef("%s(%s).removeCompositeRole(%s(%s))%s", entity.getName(), entity.getId(), role.getName(), role.getId(), getShortStackTrace());
|
LOG.tracef("(%s).removeCompositeRole(%s(%s))%s", this, role.getName(), role.getId(), getShortStackTrace());
|
||||||
entity.removeCompositeRole(role.getId());
|
entity.removeCompositeRole(role.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +141,7 @@ public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MapRoleAdapter{" + getId() + '}';
|
return String.format("%s@%08x", getName(), System.identityHashCode(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,23 @@ public class MapRoleProvider implements RoleProvider {
|
||||||
.map(entityToAdapterFunc(realm));
|
.map(entityToAdapterFunc(realm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
|
||||||
|
LOG.tracef("getRolesStream(%s, %s, %s, %d, %d)%s", realm, ids, search, first, max, getShortStackTrace());
|
||||||
|
if (ids == null) return Stream.empty();
|
||||||
|
|
||||||
|
ModelCriteriaBuilder<RoleModel> mcb = roleStore.createCriteriaBuilder()
|
||||||
|
.compare(RoleModel.SearchableFields.ID, Operator.IN, ids)
|
||||||
|
.compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||||
|
|
||||||
|
if (search != null) {
|
||||||
|
mcb = mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%" + search + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.read(withCriteria(mcb).pagination(first, max, RoleModel.SearchableFields.NAME))
|
||||||
|
.map(entityToAdapterFunc(realm));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getRealmRolesStream(RealmModel realm) {
|
public Stream<RoleModel> getRealmRolesStream(RealmModel realm) {
|
||||||
ModelCriteriaBuilder<RoleModel> mcb = roleStore.createCriteriaBuilder()
|
ModelCriteriaBuilder<RoleModel> mcb = roleStore.createCriteriaBuilder()
|
||||||
|
|
|
@ -69,7 +69,19 @@ public interface RoleModel {
|
||||||
* Returns all composite roles as a stream.
|
* Returns all composite roles as a stream.
|
||||||
* @return Stream of {@link RoleModel}. Never returns {@code null}.
|
* @return Stream of {@link RoleModel}. Never returns {@code null}.
|
||||||
*/
|
*/
|
||||||
Stream<RoleModel> getCompositesStream();
|
default Stream<RoleModel> getCompositesStream() {
|
||||||
|
return getCompositesStream(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a paginated stream of composite roles of {@code this} role that contain given string in its name.
|
||||||
|
*
|
||||||
|
* @param search Case-insensitive search string
|
||||||
|
* @param first Index of the first result to return. Ignored if negative or {@code null}.
|
||||||
|
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||||
|
* @return A stream of requested roles ordered by the role name
|
||||||
|
*/
|
||||||
|
Stream<RoleModel> getCompositesStream(String search, Integer first, Integer max);
|
||||||
|
|
||||||
boolean isClientRole();
|
boolean isClientRole();
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,18 @@ public interface RoleProvider extends Provider, RoleLookupProvider {
|
||||||
*/
|
*/
|
||||||
Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max);
|
Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a paginated stream of roles with given ids and given search value in role names.
|
||||||
|
*
|
||||||
|
* @param realm Realm. Cannot be {@code null}.
|
||||||
|
* @param ids Stream of ids. Returns empty {@code Stream} when {@code null}.
|
||||||
|
* @param search Case-insensitive string to search by role's name or description. Ignored if {@code null}.
|
||||||
|
* @param first Index of the first result to return. Ignored if negative or {@code null}.
|
||||||
|
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||||
|
* @return Stream of desired roles. Never returns {@code null}.
|
||||||
|
*/
|
||||||
|
Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes given realm role from the given realm.
|
* Removes given realm role from the given realm.
|
||||||
* @param role Role to be removed.
|
* @param role Role to be removed.
|
||||||
|
|
|
@ -42,6 +42,7 @@ import javax.ws.rs.PUT;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -176,14 +177,23 @@ public class RoleByIdResource extends RoleResource {
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Stream<RoleRepresentation> getRoleComposites(final @PathParam("role-id") String id) {
|
public Stream<RoleRepresentation> getRoleComposites(final @PathParam("role-id") String id,
|
||||||
|
final @QueryParam("search") String search,
|
||||||
|
final @QueryParam("first") Integer first,
|
||||||
|
final @QueryParam("max") Integer max
|
||||||
|
) {
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) logger.debug("*** getRoleComposites: '" + id + "'");
|
if (logger.isDebugEnabled()) logger.debug("*** getRoleComposites: '" + id + "'");
|
||||||
RoleModel role = getRoleModel(id);
|
RoleModel role = getRoleModel(id);
|
||||||
auth.roles().requireView(role);
|
auth.roles().requireView(role);
|
||||||
|
|
||||||
|
if (search == null && first == null && max == null) {
|
||||||
return role.getCompositesStream().map(ModelToRepresentation::toBriefRepresentation);
|
return role.getCompositesStream().map(ModelToRepresentation::toBriefRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return role.getCompositesStream(search, first, max).map(ModelToRepresentation::toBriefRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get realm-level roles that are in the role's composite
|
* Get realm-level roles that are in the role's composite
|
||||||
*
|
*
|
||||||
|
|
|
@ -150,6 +150,11 @@ public class RoleStorageManager implements RoleProvider {
|
||||||
return session.roleLocalStorage().getRealmRolesStream(realm, first, max);
|
return session.roleLocalStorage().getRealmRolesStream(realm, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRolesStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
|
||||||
|
return session.roleLocalStorage().getRolesStream(realm, ids, search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtaining roles from an external role storage is time-bounded. In case the external role storage
|
* Obtaining roles from an external role storage is time-bounded. In case the external role storage
|
||||||
* isn't available at least roles from a local storage are returned. For this purpose
|
* isn't available at least roles from a local storage are returned. For this purpose
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class HardcodedRoleStorageProvider implements RoleStorageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getCompositesStream() {
|
public Stream<RoleModel> getCompositesStream(String search, Integer first, Integer max) {
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -146,6 +149,21 @@ public class RoleByIdResourceTest extends AbstractAdminTest {
|
||||||
Set<RoleRepresentation> clientComposites = resource.getClientRoleComposites(ids.get("role-a"), clientUuid);
|
Set<RoleRepresentation> clientComposites = resource.getClientRoleComposites(ids.get("role-a"), clientUuid);
|
||||||
Assert.assertNames(clientComposites, "role-c");
|
Assert.assertNames(clientComposites, "role-c");
|
||||||
|
|
||||||
|
composites = resource.searchRoleComposites(ids.get("role-a"), null, null, null);
|
||||||
|
Assert.assertNames(composites, "role-b", "role-c");
|
||||||
|
|
||||||
|
composites = resource.searchRoleComposites(ids.get("role-a"), "b", null, null);
|
||||||
|
Assert.assertNames(composites, "role-b");
|
||||||
|
|
||||||
|
composites = resource.searchRoleComposites(ids.get("role-a"), null, 0, 0);
|
||||||
|
assertThat(composites, is(empty()));
|
||||||
|
|
||||||
|
composites = resource.searchRoleComposites(ids.get("role-a"), null, 0, 1);
|
||||||
|
Assert.assertNames(composites, "role-b");
|
||||||
|
|
||||||
|
composites = resource.searchRoleComposites(ids.get("role-a"), null, 1, 1);
|
||||||
|
Assert.assertNames(composites, "role-c");
|
||||||
|
|
||||||
resource.deleteComposites(ids.get("role-a"), l);
|
resource.deleteComposites(ids.get("role-a"), l);
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.roleByIdResourceCompositesPath(ids.get("role-a")), l, ResourceType.REALM_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.roleByIdResourceCompositesPath(ids.get("role-a")), l, ResourceType.REALM_ROLE);
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.admin.client.resource.RoleByIdResource;
|
||||||
import org.keycloak.admin.client.resource.RoleResource;
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
import org.keycloak.admin.client.resource.RolesResource;
|
import org.keycloak.admin.client.resource.RolesResource;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
@ -39,8 +40,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
import javax.ws.rs.ClientErrorException;
|
import javax.ws.rs.ClientErrorException;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
@ -156,6 +161,47 @@ public class ClientRolesTest extends AbstractClientTest {
|
||||||
assertEquals(0, rolesRsc.get("role-a").getRoleComposites().size());
|
assertEquals(0, rolesRsc.get("role-a").getRoleComposites().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeRolesSearch() {
|
||||||
|
// Create main-role we will work on
|
||||||
|
RoleRepresentation mainRole = makeRole("main-role");
|
||||||
|
rolesRsc.create(mainRole);
|
||||||
|
|
||||||
|
RoleResource mainRoleRsc = rolesRsc.get("main-role");
|
||||||
|
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientDbId, "main-role"), mainRole, ResourceType.CLIENT_ROLE);
|
||||||
|
|
||||||
|
// Add composites
|
||||||
|
List<RoleRepresentation> createdRoles = IntStream.range(0, 20)
|
||||||
|
.boxed()
|
||||||
|
.map(i -> makeRole("role" + i))
|
||||||
|
.peek(rolesRsc::create)
|
||||||
|
.peek(role -> assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientDbId, role.getName()), role, ResourceType.CLIENT_ROLE))
|
||||||
|
.map(role -> rolesRsc.get(role.getName()).toRepresentation())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
mainRoleRsc.addComposites(createdRoles);
|
||||||
|
mainRole = mainRoleRsc.toRepresentation();
|
||||||
|
RoleByIdResource roleByIdResource = adminClient.realm(getRealmId()).rolesById();
|
||||||
|
|
||||||
|
// Search for all composites
|
||||||
|
Set<RoleRepresentation> foundRoles = roleByIdResource.getRoleComposites(mainRole.getId());
|
||||||
|
assertThat(foundRoles, hasSize(createdRoles.size()));
|
||||||
|
|
||||||
|
// Search paginated composites
|
||||||
|
foundRoles = roleByIdResource.searchRoleComposites(mainRole.getId(), null, 0, 10);
|
||||||
|
assertThat(foundRoles, hasSize(10));
|
||||||
|
|
||||||
|
// Search for composites by string role1 (should be role1, role10-role19) without pagination
|
||||||
|
foundRoles = roleByIdResource.searchRoleComposites(mainRole.getId(), "role1", null, null);
|
||||||
|
assertThat(foundRoles, hasSize(11));
|
||||||
|
|
||||||
|
// Search for role1 with pagination
|
||||||
|
foundRoles.forEach(System.out::println);
|
||||||
|
foundRoles = roleByIdResource.searchRoleComposites(mainRole.getId(), "role1", 5, 5);
|
||||||
|
assertThat(foundRoles, hasSize(5));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void usersInRole() {
|
public void usersInRole() {
|
||||||
String clientID = clientRsc.toRepresentation().getId();
|
String clientID = clientRsc.toRepresentation().getId();
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
package org.keycloak.testsuite.model.role;
|
||||||
|
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientProvider;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.RoleProvider;
|
||||||
|
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||||
|
import org.keycloak.testsuite.model.RequireProvider;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
@RequireProvider(ClientProvider.class)
|
||||||
|
@RequireProvider(RoleProvider.class)
|
||||||
|
public class RoleModelTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
private String mainRoleId;
|
||||||
|
private static List<String> rolesSubset;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("realm");
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
|
||||||
|
createRoles(s, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface GetResult {
|
||||||
|
List<RoleModel> getResult(String search, Integer first, Integer max);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createRoles(KeycloakSession session, RealmModel realm) {
|
||||||
|
RoleModel mainRole = session.roles().addRealmRole(realm, "main-role");
|
||||||
|
mainRoleId = mainRole.getId();
|
||||||
|
|
||||||
|
ClientModel clientModel = session.clients().addClient(realm, "client-with-roles");
|
||||||
|
|
||||||
|
// Create 10 realm roles that are composites of main role
|
||||||
|
rolesSubset = IntStream.range(0, 10)
|
||||||
|
.boxed()
|
||||||
|
.map(i -> session.roles().addRealmRole(realm, "main-role-composite-" + i))
|
||||||
|
.peek(mainRole::addCompositeRole)
|
||||||
|
.map(RoleModel::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Create 10 client roles that are composites of main role
|
||||||
|
rolesSubset.addAll(IntStream.range(10, 20)
|
||||||
|
.boxed()
|
||||||
|
.map(i -> session.roles().addClientRole(clientModel, "main-role-composite-" + i))
|
||||||
|
.peek(mainRole::addCompositeRole)
|
||||||
|
.map(RoleModel::getId)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
// add some additional roles that won't fulfill condition
|
||||||
|
IntStream.range(0, 20)
|
||||||
|
.forEach(i -> session.roles().addRealmRole(realm, "non-returned-role-" + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RoleModel> getResult(String search, Integer first, Integer max) {
|
||||||
|
return withRealm(realmId, (session, realm) -> session.roles().getRolesStream(realm, rolesSubset.stream(), search, first, max).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoleModel getMainRole() {
|
||||||
|
return withRealm(realmId, (session, realm) -> session.roles().getRoleById(realm, mainRoleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RoleModel> getModelResult(String search, Integer first, Integer max) {
|
||||||
|
return withRealm(realmId, ((session, realm) -> session.roles().getRoleById(realm, mainRoleId).getCompositesStream(search, first, max).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRolesWithIdsQueries() {
|
||||||
|
// should return all roles from the subset
|
||||||
|
List<RoleModel> result = getResult(null, null, null);
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// test non-existing role ids
|
||||||
|
result = withRealm(realmId, (session, realm) -> session.roles()
|
||||||
|
.getRolesStream(realm, IntStream.range(0, 10).boxed()
|
||||||
|
.map(i -> UUID.randomUUID().toString()), null, null, null)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
assertThat(result, is(empty()));
|
||||||
|
|
||||||
|
// test mixed non-existing with existing
|
||||||
|
result = withRealm(realmId, (session, realm) -> session.roles()
|
||||||
|
.getRolesStream(realm, Stream.concat(rolesSubset.subList(0, 10).stream(),
|
||||||
|
IntStream.range(0, 10).boxed().map(i -> UUID.randomUUID().toString())), null, null, null)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
assertThat(result, hasSize(10));
|
||||||
|
assertIndexValues(result, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeRoles() {
|
||||||
|
List<RoleModel> result = getModelResult(null, null, null);
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
result = withRealm(realmId, (session, realm) -> session.roles().getRoleById(realm, mainRoleId).getCompositesStream().collect(Collectors.toList()));
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, containsInAnyOrder(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRolesWithIdsSearchQueries() {
|
||||||
|
testRolesWithIdsSearchQueries(this::getResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeRolesSearchQueries() {
|
||||||
|
testRolesWithIdsSearchQueries(this::getModelResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRolesWithIdsSearchQueries(GetResult resultProvider) {
|
||||||
|
// should return all roles from the subset
|
||||||
|
List<RoleModel> result = resultProvider.getResult("", null, null);
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// test string that all contains
|
||||||
|
result = resultProvider.getResult("role-composite", null, null);
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// test string that some contain
|
||||||
|
result = resultProvider.getResult("role-composite-1", null, null);
|
||||||
|
assertThat(result, hasSize(11));
|
||||||
|
assertIndexValues(result, contains(1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
|
||||||
|
|
||||||
|
// test string none contain
|
||||||
|
result = resultProvider.getResult("nonsense-string", null, null);
|
||||||
|
assertThat(result, is(empty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRolesWithIdsPaginationQueries() {
|
||||||
|
testRolesWithIdsPaginationQueries(this::getResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeRolesPaginationQueries() {
|
||||||
|
testRolesWithIdsPaginationQueries(this::getResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRolesWithIdsPaginationQueries(GetResult resultProvider) {
|
||||||
|
// should return all roles from the subset
|
||||||
|
List<RoleModel> result = resultProvider.getResult(null, null, rolesSubset.size());
|
||||||
|
assertThat(result, hasSize(rolesSubset.size()));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// test max parameter
|
||||||
|
result = resultProvider.getResult(null, null, 5);
|
||||||
|
assertThat(result, hasSize(5));
|
||||||
|
assertIndexValues(result, contains(0, 1, 10, 11, 12));
|
||||||
|
|
||||||
|
// test first parameter
|
||||||
|
result = resultProvider.getResult(null, 10, null);
|
||||||
|
assertThat(result, hasSize(rolesSubset.size() - 10));
|
||||||
|
assertIndexValues(result, contains(18, 19, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// test first and max
|
||||||
|
result = resultProvider.getResult(null, 10, 5);
|
||||||
|
assertThat(result, hasSize(5));
|
||||||
|
assertIndexValues(result, contains(18, 19, 2, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRolesWithIdsPaginationSearchQueries() {
|
||||||
|
testRolesWithIdsPaginationSearchQueries(this::getResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeRolesPaginationSearchQueries() {
|
||||||
|
testRolesWithIdsPaginationSearchQueries(this::getModelResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRolesWithIdsPaginationSearchQueries(GetResult resultProvider) {
|
||||||
|
// test all parameters together
|
||||||
|
List<RoleModel> result = resultProvider.getResult("1", 4, 3);
|
||||||
|
assertThat(result, hasSize(3));
|
||||||
|
assertIndexValues(result, contains(13, 14, 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIndexValues(List<RoleModel> roles, Matcher<? super Collection<? extends Integer>> matcher) {
|
||||||
|
assertThat(roles.stream().map(RoleModel::getName).map(s -> s.substring("main-role-composite-".length())).map(Integer::parseInt).collect(Collectors.toList()), matcher);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue