Keycloak 2035
This PR adds: * an endpoint to Role that lists users with the Role * a tab "Users in Role" in Admin console Role page * it is applicable to Realm and Client Roles * Extends UserQueryProvider with default methods (throwing Runtime Exception if not overriden) * Testing in base testsuite and Console
This commit is contained in:
parent
15ddb2009d
commit
e78bf5f876
21 changed files with 494 additions and 3 deletions
|
@ -299,6 +299,16 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.storage.StorageId;
|
import org.keycloak.storage.StorageId;
|
||||||
|
@ -191,6 +192,19 @@ public class PropertyFileUserStorageProvider implements
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
// Not supported in federated storage
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
|
||||||
|
// Not supported in federated storage
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||||
// runtime automatically handles querying UserFederatedStorage
|
// runtime automatically handles querying UserFederatedStorage
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
|
@ -72,4 +73,9 @@ public interface RoleResource {
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
void deleteComposites(List<RoleRepresentation> rolesToRemove);
|
void deleteComposites(List<RoleRepresentation> rolesToRemove);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("users")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<UserRepresentation> getRoleUserMembers();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,6 +511,17 @@ public class UserCacheSession implements UserCache {
|
||||||
return getDelegate().getGroupMembers(realm, group);
|
return getDelegate().getGroupMembers(realm, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
return getDelegate().getRoleMembers(realm, role, firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
|
||||||
|
return getDelegate().getRoleMembers(realm, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getServiceAccount(ClientModel client) {
|
public UserModel getServiceAccount(ClientModel client) {
|
||||||
// Just an attempt to find the user from cache by default serviceAccount username
|
// Just an attempt to find the user from cache by default serviceAccount username
|
||||||
|
|
|
@ -497,6 +497,20 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
|
||||||
|
TypedQuery<UserEntity> query = em.createNamedQuery("usersInRole", UserEntity.class);
|
||||||
|
query.setParameter("roleId", role.getId());
|
||||||
|
List<UserEntity> results = query.getResultList();
|
||||||
|
|
||||||
|
List<UserModel> users = new ArrayList<UserModel>();
|
||||||
|
for (UserEntity user : results) {
|
||||||
|
users.add(new UserAdapter(session, realm, em, user));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preRemove(RealmModel realm, GroupModel group) {
|
public void preRemove(RealmModel realm, GroupModel group) {
|
||||||
em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
|
em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
|
||||||
|
@ -636,6 +650,25 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
TypedQuery<UserEntity> query = em.createNamedQuery("usersInRole", UserEntity.class);
|
||||||
|
query.setParameter("roleId", role.getId());
|
||||||
|
if (firstResult != -1) {
|
||||||
|
query.setFirstResult(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults != -1) {
|
||||||
|
query.setMaxResults(maxResults);
|
||||||
|
}
|
||||||
|
List<UserEntity> results = query.getResultList();
|
||||||
|
|
||||||
|
List<UserModel> users = new LinkedList<>();
|
||||||
|
for (UserEntity user : results) {
|
||||||
|
users.add(new UserAdapter(session, realm, em, user));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||||
return searchForUser(search, realm, -1, -1);
|
return searchForUser(search, realm, -1, -1);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import java.io.Serializable;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="usersInRole", query="select u from UserRoleMappingEntity m, UserEntity u where m.roleId=:roleId and u.id=m.user"),
|
||||||
@NamedQuery(name="userHasRole", query="select m from UserRoleMappingEntity m where m.user = :user and m.roleId = :roleId"),
|
@NamedQuery(name="userHasRole", query="select m from UserRoleMappingEntity m where m.user = :user and m.roleId = :roleId"),
|
||||||
@NamedQuery(name="userRoleMappings", query="select m from UserRoleMappingEntity m where m.user = :user"),
|
@NamedQuery(name="userRoleMappings", query="select m from UserRoleMappingEntity m where m.user = :user"),
|
||||||
@NamedQuery(name="userRoleMappingIds", query="select m.roleId from UserRoleMappingEntity m where m.user = :user"),
|
@NamedQuery(name="userRoleMappingIds", query="select m.roleId from UserRoleMappingEntity m where m.user = :user"),
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.storage.user;
|
||||||
|
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -117,6 +118,35 @@ public interface UserQueryProvider {
|
||||||
*/
|
*/
|
||||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get users that belong to a specific role.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param role
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default List<UserModel> getRoleMembers(RealmModel realm, RoleModel role)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for users that have a specific role with a specific roleId.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param firstResult
|
||||||
|
* @param maxResults
|
||||||
|
* @param role
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider
|
* Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider
|
||||||
* as this is done automatically.
|
* as this is done automatically.
|
||||||
|
|
|
@ -19,20 +19,24 @@ package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.representations.idm.ManagementPermissionReference;
|
import org.keycloak.representations.idm.ManagementPermissionReference;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
|
@ -373,4 +377,35 @@ public class RoleContainerResource extends RoleResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return List of Users that have the specified role name
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param roleName
|
||||||
|
* @param firstResult
|
||||||
|
* @param maxResults
|
||||||
|
* @return initialized manage permissions reference
|
||||||
|
*/
|
||||||
|
@Path("{role-name}/users")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public List<UserRepresentation> getUsersInRole(final @PathParam("role-name") String roleName,
|
||||||
|
@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults) {
|
||||||
|
|
||||||
|
auth.roles().requireView(roleContainer);
|
||||||
|
firstResult = firstResult != null ? firstResult : 0;
|
||||||
|
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
|
||||||
|
|
||||||
|
RoleModel role = roleContainer.getRole(roleName);
|
||||||
|
List<UserRepresentation> results = new ArrayList<UserRepresentation>();
|
||||||
|
List<UserModel> userModels = session.users().getRoleMembers(realm, role, firstResult, maxResults);
|
||||||
|
|
||||||
|
for (UserModel user : userModels) {
|
||||||
|
results.add(ModelToRepresentation.toRepresentation(session, realm, user));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,6 +329,11 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
|
||||||
return getGroupMembers(realm, group, -1, -1);
|
return getGroupMembers(realm, group, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
|
||||||
|
return getRoleMembers(realm, role, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
UserModel user = localStorage().getUserByUsername(username, realm);
|
UserModel user = localStorage().getUserByUsername(username, realm);
|
||||||
|
@ -577,6 +582,17 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
|
||||||
return importValidation(realm, results);
|
return importValidation(realm, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getRoleMembers(final RealmModel realm, final RoleModel role, int firstResult, int maxResults) {
|
||||||
|
List<UserModel> results = query((provider, first, max) -> {
|
||||||
|
if (provider instanceof UserQueryProvider) {
|
||||||
|
return ((UserQueryProvider)provider).getRoleMembers(realm, role, first, max);
|
||||||
|
}
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}, realm, firstResult, maxResults);
|
||||||
|
return importValidation(realm, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preRemove(RealmModel realm) {
|
public void preRemove(RealmModel realm) {
|
||||||
|
|
|
@ -137,7 +137,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getUsersCount(RealmModel realm) {
|
public int getUsersCount(RealmModel realm) {
|
||||||
return userPasswords.size();
|
return userPasswords.size();
|
||||||
|
@ -207,6 +206,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||||
return getUsers(realm, 0, Integer.MAX_VALUE - 1);
|
return getUsers(realm, 0, Integer.MAX_VALUE - 1);
|
||||||
|
|
|
@ -132,5 +132,4 @@ public class ClientRolesTest extends AbstractClientTest {
|
||||||
assertFalse(rolesRsc.get("role-a").toRepresentation().isComposite());
|
assertFalse(rolesRsc.get("role-a").toRepresentation().isComposite());
|
||||||
assertEquals(0, rolesRsc.get("role-a").getRoleComposites().size());
|
assertEquals(0, rolesRsc.get("role-a").getRoleComposites().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,15 @@ package org.keycloak.testsuite.admin.realm;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
import org.keycloak.admin.client.resource.RolesResource;
|
import org.keycloak.admin.client.resource.RolesResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
@ -33,6 +37,8 @@ import org.keycloak.testsuite.util.RoleBuilder;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -44,6 +50,8 @@ import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.keycloak.testsuite.Assert.assertNames;
|
||||||
|
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -59,8 +67,14 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
public void before() {
|
public void before() {
|
||||||
RoleRepresentation roleA = RoleBuilder.create().name("role-a").description("Role A").build();
|
RoleRepresentation roleA = RoleBuilder.create().name("role-a").description("Role A").build();
|
||||||
RoleRepresentation roleB = RoleBuilder.create().name("role-b").description("Role B").build();
|
RoleRepresentation roleB = RoleBuilder.create().name("role-b").description("Role B").build();
|
||||||
|
//KEYCLOAK-2035
|
||||||
|
RoleRepresentation roleWithUsers = RoleBuilder.create().name("role-with-users").description("Role with users").build();
|
||||||
|
RoleRepresentation roleWithoutUsers = RoleBuilder.create().name("role-without-users").description("role-without-users").build();
|
||||||
adminClient.realm(REALM_NAME).roles().create(roleA);
|
adminClient.realm(REALM_NAME).roles().create(roleA);
|
||||||
adminClient.realm(REALM_NAME).roles().create(roleB);
|
adminClient.realm(REALM_NAME).roles().create(roleB);
|
||||||
|
adminClient.realm(REALM_NAME).roles().create(roleWithUsers);
|
||||||
|
adminClient.realm(REALM_NAME).roles().create(roleWithoutUsers);
|
||||||
|
|
||||||
|
|
||||||
ClientRepresentation clientRep = ClientBuilder.create().clientId("client-a").build();
|
ClientRepresentation clientRep = ClientBuilder.create().clientId("client-a").build();
|
||||||
Response response = adminClient.realm(REALM_NAME).clients().create(clientRep);
|
Response response = adminClient.realm(REALM_NAME).clients().create(clientRep);
|
||||||
|
@ -79,17 +93,34 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
ids.put(r.getName(), r.getId());
|
ids.put(r.getName(), r.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserRepresentation userRep = new UserRepresentation();
|
||||||
|
userRep.setUsername("test-role-member");
|
||||||
|
userRep.setEmail("test-role-member@test-role-member.com");
|
||||||
|
userRep.setRequiredActions(Collections.<String>emptyList());
|
||||||
|
userRep.setEnabled(true);
|
||||||
|
adminClient.realm(REALM_NAME).users().create(userRep);
|
||||||
|
|
||||||
getCleanup().addRoleId(ids.get("role-a"));
|
getCleanup().addRoleId(ids.get("role-a"));
|
||||||
getCleanup().addRoleId(ids.get("role-b"));
|
getCleanup().addRoleId(ids.get("role-b"));
|
||||||
getCleanup().addRoleId(ids.get("role-c"));
|
getCleanup().addRoleId(ids.get("role-c"));
|
||||||
|
getCleanup().addRoleId(ids.get("role-with-users"));
|
||||||
|
getCleanup().addRoleId(ids.get("role-without-users"));
|
||||||
|
getCleanup().addUserId(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId());
|
||||||
|
|
||||||
|
|
||||||
resource = adminClient.realm(REALM_NAME).roles();
|
resource = adminClient.realm(REALM_NAME).roles();
|
||||||
|
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-a"), roleA, ResourceType.REALM_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-a"), roleA, ResourceType.REALM_ROLE);
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-b"), roleB, ResourceType.REALM_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-b"), roleB, ResourceType.REALM_ROLE);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-with-users"), roleWithUsers, ResourceType.REALM_ROLE);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-without-users"), roleWithoutUsers, ResourceType.REALM_ROLE);
|
||||||
|
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientUuid), clientRep, ResourceType.CLIENT);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientUuid), clientRep, ResourceType.CLIENT);
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientUuid, "role-c"), roleC, ResourceType.CLIENT_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientUuid, "role-c"), roleC, ResourceType.CLIENT_ROLE);
|
||||||
|
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userResourcePath(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId()), userRep, ResourceType.USER);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -163,4 +194,68 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
assertEquals(0, resource.get("role-a").getRoleComposites().size());
|
assertEquals(0, resource.get("role-a").getRoleComposites().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-2035 Verifies that Users assigned to Role are being properly retrieved as members in API endpoint for role membership
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUsersInRole() {
|
||||||
|
RoleResource role = resource.get("role-with-users");
|
||||||
|
|
||||||
|
List<UserRepresentation> users = adminClient.realm(REALM_NAME).users().search("test-role-member", null, null, null, null, null);
|
||||||
|
assertEquals(1, users.size());
|
||||||
|
UserResource user = adminClient.realm(REALM_NAME).users().get(users.get(0).getId());
|
||||||
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
|
||||||
|
RoleResource roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
List<RoleRepresentation> rolesToAdd = new LinkedList<>();
|
||||||
|
rolesToAdd.add(roleResource.toRepresentation());
|
||||||
|
adminClient.realm(REALM_NAME).users().get(userRep.getId()).roles().realmLevel().add(rolesToAdd);
|
||||||
|
|
||||||
|
roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
roleResource.getRoleUserMembers();
|
||||||
|
//roleResource.getRoleUserMembers().stream().forEach((member) -> log.infof("Found user {}", member.getUsername()));
|
||||||
|
assertEquals(1, roleResource.getRoleUserMembers().size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-2035 Verifies that Role with no users assigned is being properly retrieved without members in API endpoint for role membership
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUsersNotInRole() {
|
||||||
|
RoleResource role = resource.get("role-without-users");
|
||||||
|
|
||||||
|
role = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
role.getRoleUserMembers();
|
||||||
|
assertEquals(0, role.getRoleUserMembers().size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-2035 Verifies that Role Membership is ok after user removal
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void roleMembershipAfterUserRemoval() {
|
||||||
|
RoleResource role = resource.get("role-with-users");
|
||||||
|
|
||||||
|
List<UserRepresentation> users = adminClient.realm(REALM_NAME).users().search("test-role-member", null, null, null, null, null);
|
||||||
|
assertEquals(1, users.size());
|
||||||
|
UserResource user = adminClient.realm(REALM_NAME).users().get(users.get(0).getId());
|
||||||
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
|
||||||
|
RoleResource roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
List<RoleRepresentation> rolesToAdd = new LinkedList<>();
|
||||||
|
rolesToAdd.add(roleResource.toRepresentation());
|
||||||
|
adminClient.realm(REALM_NAME).users().get(userRep.getId()).roles().realmLevel().add(rolesToAdd);
|
||||||
|
|
||||||
|
roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
roleResource.getRoleUserMembers();
|
||||||
|
assertEquals(1, roleResource.getRoleUserMembers().size());
|
||||||
|
|
||||||
|
adminClient.realm(REALM_NAME).users().delete(userRep.getId());
|
||||||
|
roleResource.getRoleUserMembers();
|
||||||
|
assertEquals(0, roleResource.getRoleUserMembers().size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.keycloak.testsuite.console.roles;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
|
import org.keycloak.admin.client.resource.RolesResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.console.page.roles.DefaultRoles;
|
||||||
|
import org.keycloak.testsuite.console.page.roles.RealmRoles;
|
||||||
|
import org.keycloak.testsuite.console.page.roles.Role;
|
||||||
|
import org.keycloak.testsuite.console.page.roles.Roles;
|
||||||
|
import org.keycloak.testsuite.console.page.users.UserRoleMappings;
|
||||||
|
import org.keycloak.testsuite.console.page.users.Users;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:antonio.ferreira@fiercely.pt">Antonio Ferreira</a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UsersInRoleTest extends AbstractRolesTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private DefaultRoles defaultRolesPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private UserRoleMappings userRolesPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private Users usersPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private Roles rolesPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private Role rolePage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private RealmRoles realmRolesPage;
|
||||||
|
|
||||||
|
private RoleRepresentation testRoleRep;
|
||||||
|
private UserRepresentation newUser;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeDefaultRolesTest() {
|
||||||
|
// create a role via admin client
|
||||||
|
testRoleRep = new RoleRepresentation("test-role", "", false);
|
||||||
|
rolesResource().create(testRoleRep);
|
||||||
|
|
||||||
|
newUser = new UserRepresentation();
|
||||||
|
newUser.setUsername("test_user");
|
||||||
|
newUser.setEnabled(true);
|
||||||
|
newUser.setEmail("test-role-member@test-role-member.com");
|
||||||
|
newUser.setRequiredActions(Collections.<String>emptyList());
|
||||||
|
//testRealmResource().users().create(newUser);
|
||||||
|
createUserWithAdminClient(testRealmResource(), newUser);
|
||||||
|
rolesResource().create(testRoleRep);
|
||||||
|
rolesPage.navigateTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RolesResource rolesResource() {
|
||||||
|
return testRealmResource().roles();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Added for KEYCLOAK-2035
|
||||||
|
@Test
|
||||||
|
public void usersInRoleTabIsPresent() {
|
||||||
|
|
||||||
|
rolesPage.navigateTo();
|
||||||
|
rolesPage.tabs().realmRoles();
|
||||||
|
realmRolesPage.table().search(testRoleRep.getName());
|
||||||
|
realmRolesPage.table().clickRole(testRoleRep.getName());
|
||||||
|
//assert no users in list
|
||||||
|
//Role Page class missing a getUsers() method
|
||||||
|
|
||||||
|
List<UserRepresentation> users = testRealmResource().users().search("test_user", null, null, null, null, null);
|
||||||
|
assertEquals(1, users.size());
|
||||||
|
UserResource user = testRealmResource().users().get(users.get(0).getId());
|
||||||
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
|
||||||
|
usersPage.navigateTo();
|
||||||
|
usersPage.table().search(userRep.getUsername());
|
||||||
|
usersPage.table().clickUser(userRep.getUsername());
|
||||||
|
|
||||||
|
assertFalse(userRolesPage.form().isAssignedRole(testRoleRep.getName()));
|
||||||
|
|
||||||
|
RoleResource roleResource = testRealmResource().roles().get(testRoleRep.getName());
|
||||||
|
List<RoleRepresentation> rolesToAdd = new LinkedList<>();
|
||||||
|
rolesToAdd.add(roleResource.toRepresentation());
|
||||||
|
testRealmResource().users().get(userRep.getId()).roles().realmLevel().add(rolesToAdd);
|
||||||
|
|
||||||
|
rolesPage.navigateTo();
|
||||||
|
rolesPage.tabs().realmRoles();
|
||||||
|
realmRolesPage.table().search(testRoleRep.getName());
|
||||||
|
realmRolesPage.table().clickRole(testRoleRep.getName());
|
||||||
|
|
||||||
|
assertTrue(userRolesPage.form().isAssignedRole(testRoleRep.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -573,6 +573,7 @@ select-a-type.placeholder=selecione um tipo
|
||||||
available-groups=Grupos disponíveis
|
available-groups=Grupos disponíveis
|
||||||
value=Valor
|
value=Valor
|
||||||
table-of-group-members=Tabela de membros do grupo
|
table-of-group-members=Tabela de membros do grupo
|
||||||
|
table-of-role-members=Tabela de membros do role
|
||||||
last-name=Sobrenome
|
last-name=Sobrenome
|
||||||
first-name=Primeiro nome
|
first-name=Primeiro nome
|
||||||
email=E-mail
|
email=E-mail
|
||||||
|
@ -672,6 +673,7 @@ download-keys-and-cert=Download chave e certificado
|
||||||
no-value-assigned.placeholder=Nenhum valor associado
|
no-value-assigned.placeholder=Nenhum valor associado
|
||||||
remove=Remover
|
remove=Remover
|
||||||
no-group-members=Nenhum membro
|
no-group-members=Nenhum membro
|
||||||
|
no-role-members=Nenhum membro no role
|
||||||
temporary=Temporária
|
temporary=Temporária
|
||||||
join=Participar
|
join=Participar
|
||||||
event-type=Tipo de evento
|
event-type=Tipo de evento
|
||||||
|
@ -697,6 +699,7 @@ authz-scope=Escopo
|
||||||
authz-authz-scopes=Autorização de escopos
|
authz-authz-scopes=Autorização de escopos
|
||||||
authz-policies=Políticas
|
authz-policies=Políticas
|
||||||
authz-permissions=Permissões
|
authz-permissions=Permissões
|
||||||
|
authz-users=Usuários no role
|
||||||
authz-evaluate=Avaliar
|
authz-evaluate=Avaliar
|
||||||
authz-icon-uri=URI do ícone
|
authz-icon-uri=URI do ícone
|
||||||
authz-select-scope=Selecione um escopo
|
authz-select-scope=Selecione um escopo
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
<script src="${resourceUrl}/js/controllers/clients.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/js/controllers/clients.js" type="text/javascript"></script>
|
||||||
<script src="${resourceUrl}/js/controllers/users.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/js/controllers/users.js" type="text/javascript"></script>
|
||||||
<script src="${resourceUrl}/js/controllers/groups.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/js/controllers/groups.js" type="text/javascript"></script>
|
||||||
|
<script src="${resourceUrl}/js/controllers/roles.js" type="text/javascript"></script>
|
||||||
<script src="${resourceUrl}/js/loaders.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/js/loaders.js" type="text/javascript"></script>
|
||||||
<script src="${resourceUrl}/js/services.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/js/services.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
|
|
@ -938,6 +938,7 @@ available-groups=Available Groups
|
||||||
available-groups.tooltip=Select a group you want to add as a default.
|
available-groups.tooltip=Select a group you want to add as a default.
|
||||||
value=Value
|
value=Value
|
||||||
table-of-group-members=Table of group members
|
table-of-group-members=Table of group members
|
||||||
|
table-of-role-members=Table of role members
|
||||||
last-name=Last Name
|
last-name=Last Name
|
||||||
first-name=First Name
|
first-name=First Name
|
||||||
email=Email
|
email=Email
|
||||||
|
@ -1063,6 +1064,7 @@ download-keys-and-cert=Download keys and cert
|
||||||
no-value-assigned.placeholder=No value assigned
|
no-value-assigned.placeholder=No value assigned
|
||||||
remove=Remove
|
remove=Remove
|
||||||
no-group-members=No group members
|
no-group-members=No group members
|
||||||
|
no-role-members=No role members
|
||||||
temporary=Temporary
|
temporary=Temporary
|
||||||
join=Join
|
join=Join
|
||||||
event-type=Event Type
|
event-type=Event Type
|
||||||
|
@ -1088,6 +1090,7 @@ authz-scope=Scope
|
||||||
authz-authz-scopes=Authorization Scopes
|
authz-authz-scopes=Authorization Scopes
|
||||||
authz-policies=Policies
|
authz-policies=Policies
|
||||||
authz-permissions=Permissions
|
authz-permissions=Permissions
|
||||||
|
authz-users=Users in Role
|
||||||
authz-evaluate=Evaluate
|
authz-evaluate=Evaluate
|
||||||
authz-icon-uri=Icon URI
|
authz-icon-uri=Icon URI
|
||||||
authz-icon-uri.tooltip=An URI pointing to an icon.
|
authz-icon-uri.tooltip=An URI pointing to an icon.
|
||||||
|
|
|
@ -770,6 +770,18 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'RoleDetailCtrl'
|
controller : 'RoleDetailCtrl'
|
||||||
})
|
})
|
||||||
|
.when('/realms/:realm/roles/:role/users', {
|
||||||
|
templateUrl : resourceUrl + '/partials/realm-role-users.html',
|
||||||
|
resolve : {
|
||||||
|
realm : function(RealmLoader) {
|
||||||
|
return RealmLoader();
|
||||||
|
},
|
||||||
|
role : function(RoleLoader) {
|
||||||
|
return RoleLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller : 'RoleMembersCtrl'
|
||||||
|
})
|
||||||
.when('/realms/:realm/roles', {
|
.when('/realms/:realm/roles', {
|
||||||
templateUrl : resourceUrl + '/partials/role-list.html',
|
templateUrl : resourceUrl + '/partials/role-list.html',
|
||||||
resolve : {
|
resolve : {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
module.controller('RoleMembersCtrl', function($scope, realm, role, RoleMembership) {
|
||||||
|
$scope.realm = realm;
|
||||||
|
$scope.page = 0;
|
||||||
|
$scope.role = role;
|
||||||
|
|
||||||
|
$scope.query = {
|
||||||
|
realm: realm.realm,
|
||||||
|
role: role.name,
|
||||||
|
max : 5,
|
||||||
|
first : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$scope.firstPage = function() {
|
||||||
|
$scope.query.first = 0;
|
||||||
|
$scope.searchQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.previousPage = function() {
|
||||||
|
$scope.query.first -= parseInt($scope.query.max);
|
||||||
|
if ($scope.query.first < 0) {
|
||||||
|
$scope.query.first = 0;
|
||||||
|
}
|
||||||
|
$scope.searchQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.nextPage = function() {
|
||||||
|
$scope.query.first += parseInt($scope.query.max);
|
||||||
|
$scope.searchQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.searchQuery = function() {
|
||||||
|
console.log("query.search: " + $scope.query.search);
|
||||||
|
$scope.searchLoaded = false;
|
||||||
|
|
||||||
|
$scope.users = RoleMembership.query($scope.query, function() {
|
||||||
|
console.log('search loaded');
|
||||||
|
$scope.searchLoaded = true;
|
||||||
|
$scope.lastSearch = $scope.query.search;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.searchQuery();
|
||||||
|
|
||||||
|
});
|
|
@ -1684,6 +1684,13 @@ module.factory('GroupMembership', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('RoleMembership', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/roles/:role/users', {
|
||||||
|
realm : '@realm',
|
||||||
|
role : '@role'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.factory('UserGroupMembership', function($resource) {
|
module.factory('UserGroupMembership', function($resource) {
|
||||||
return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', {
|
return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="#/realms/{{realm.realm}}/roles">{{:: 'roles' | translate}}</a></li>
|
||||||
|
<li>{{role.name}}</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<kc-tabs-role></kc-tabs-role>
|
||||||
|
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<caption data-ng-show="users" class="hidden">{{:: 'table-of-role-members' | translate}}</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<tr data-ng-show="searchLoaded && users.length > 0">
|
||||||
|
<th>{{:: 'username' | translate}}</th>
|
||||||
|
<th>{{:: 'last-name' | translate}}</th>
|
||||||
|
<th>{{:: 'first-name' | translate}}</th>
|
||||||
|
<th>{{:: 'email' | translate}}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot data-ng-show="users && (users.length >= query.max || query.first > 0)">
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<div class="table-nav">
|
||||||
|
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
|
||||||
|
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
|
||||||
|
<button data-ng-click="nextPage()" class="next" ng-disabled="users.length < query.max">{{:: 'next-page' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="user in users">
|
||||||
|
<td><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{user.username}}</a></td>
|
||||||
|
<td>{{user.lastName}}</td>
|
||||||
|
<td>{{user.firstName}}</td>
|
||||||
|
<td>{{user.email}}</td>
|
||||||
|
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr data-ng-show="!users || users.length == 0">
|
||||||
|
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch != null">{{:: 'no-role-members' | translate}}</td>
|
||||||
|
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch == null">{{:: 'no-role-members' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
|
@ -9,5 +9,7 @@
|
||||||
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
<li ng-class="{active: path[4] == 'users'}" data-ng-show="access.manageRealm && access.manageAuthorization">
|
||||||
|
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/users">{{:: 'authz-users' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
Loading…
Reference in a new issue