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:
howcroft 2017-09-20 17:05:33 +01:00 committed by Antonio Howcroft Ferreira
parent 15ddb2009d
commit e78bf5f876
21 changed files with 494 additions and 3 deletions

View file

@ -299,6 +299,16 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
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
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
return Collections.EMPTY_LIST;

View file

@ -26,6 +26,7 @@ import org.keycloak.credential.CredentialModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.StorageId;
@ -190,6 +191,19 @@ public class PropertyFileUserStorageProvider implements
// runtime automatically handles querying UserFederatedStorage
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
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {

View file

@ -18,6 +18,7 @@
package org.keycloak.admin.client.resource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@ -71,5 +72,10 @@ public interface RoleResource {
@Path("composites")
@Consumes(MediaType.APPLICATION_JSON)
void deleteComposites(List<RoleRepresentation> rolesToRemove);
@GET
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
Set<UserRepresentation> getRoleUserMembers();
}

View file

@ -511,6 +511,17 @@ public class UserCacheSession implements UserCache {
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
public UserModel getServiceAccount(ClientModel client) {
// Just an attempt to find the user from cache by default serviceAccount username

View file

@ -496,6 +496,20 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
}
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
public void preRemove(RealmModel realm, GroupModel group) {
@ -635,6 +649,25 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
}
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
public List<UserModel> searchForUser(String search, RealmModel realm) {

View file

@ -34,6 +34,7 @@ import java.io.Serializable;
* @version $Revision: 1 $
*/
@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="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"),

View file

@ -18,6 +18,7 @@ package org.keycloak.storage.user;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import java.util.List;
@ -117,6 +118,35 @@ public interface UserQueryProvider {
*/
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
* as this is done automatically.

View file

@ -19,20 +19,24 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
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.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse;
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;
}
}

View file

@ -328,7 +328,12 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return getGroupMembers(realm, group, -1, -1);
}
@Override
public List<UserModel> getRoleMembers(RealmModel realm, RoleModel role) {
return getRoleMembers(realm, role, -1, -1);
}
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
UserModel user = localStorage().getUserByUsername(username, realm);
@ -577,6 +582,17 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
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
public void preRemove(RealmModel realm) {

View file

@ -137,7 +137,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
}
}
@Override
public int getUsersCount(RealmModel realm) {
return userPasswords.size();
@ -207,6 +206,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
return Collections.EMPTY_LIST;
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
return getUsers(realm, 0, Integer.MAX_VALUE - 1);

View file

@ -132,5 +132,4 @@ public class ClientRolesTest extends AbstractClientTest {
assertFalse(rolesRsc.get("role-a").toRepresentation().isComposite());
assertEquals(0, rolesRsc.get("role-a").getRoleComposites().size());
}
}

View file

@ -19,11 +19,15 @@ package org.keycloak.testsuite.admin.realm;
import org.junit.Before;
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.UserResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.AbstractAdminTest;
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.core.Response;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
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.assertTrue;
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>
@ -59,9 +67,15 @@ public class RealmRolesTest extends AbstractAdminTest {
public void before() {
RoleRepresentation roleA = RoleBuilder.create().name("role-a").description("Role A").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(roleB);
adminClient.realm(REALM_NAME).roles().create(roleWithUsers);
adminClient.realm(REALM_NAME).roles().create(roleWithoutUsers);
ClientRepresentation clientRep = ClientBuilder.create().clientId("client-a").build();
Response response = adminClient.realm(REALM_NAME).clients().create(clientRep);
clientUuid = ApiUtil.getCreatedId(response);
@ -78,18 +92,35 @@ public class RealmRolesTest extends AbstractAdminTest {
for (RoleRepresentation r : adminClient.realm(REALM_NAME).clients().get(clientUuid).roles().list()) {
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-b"));
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();
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-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.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
@ -163,4 +194,68 @@ public class RealmRolesTest extends AbstractAdminTest {
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());
}
}

View file

@ -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()));
}
}

View file

@ -573,6 +573,7 @@ select-a-type.placeholder=selecione um tipo
available-groups=Grupos disponíveis
value=Valor
table-of-group-members=Tabela de membros do grupo
table-of-role-members=Tabela de membros do role
last-name=Sobrenome
first-name=Primeiro nome
email=E-mail
@ -672,6 +673,7 @@ download-keys-and-cert=Download chave e certificado
no-value-assigned.placeholder=Nenhum valor associado
remove=Remover
no-group-members=Nenhum membro
no-role-members=Nenhum membro no role
temporary=Temporária
join=Participar
event-type=Tipo de evento
@ -697,6 +699,7 @@ authz-scope=Escopo
authz-authz-scopes=Autorização de escopos
authz-policies=Políticas
authz-permissions=Permissões
authz-users=Usuários no role
authz-evaluate=Avaliar
authz-icon-uri=URI do ícone
authz-select-scope=Selecione um escopo

View file

@ -66,6 +66,7 @@
<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/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/services.js" type="text/javascript"></script>

View file

@ -938,6 +938,7 @@ available-groups=Available Groups
available-groups.tooltip=Select a group you want to add as a default.
value=Value
table-of-group-members=Table of group members
table-of-role-members=Table of role members
last-name=Last Name
first-name=First Name
email=Email
@ -1063,6 +1064,7 @@ download-keys-and-cert=Download keys and cert
no-value-assigned.placeholder=No value assigned
remove=Remove
no-group-members=No group members
no-role-members=No role members
temporary=Temporary
join=Join
event-type=Event Type
@ -1088,6 +1090,7 @@ authz-scope=Scope
authz-authz-scopes=Authorization Scopes
authz-policies=Policies
authz-permissions=Permissions
authz-users=Users in Role
authz-evaluate=Evaluate
authz-icon-uri=Icon URI
authz-icon-uri.tooltip=An URI pointing to an icon.

View file

@ -770,6 +770,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
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', {
templateUrl : resourceUrl + '/partials/role-list.html',
resolve : {

View file

@ -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();
});

View file

@ -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) {
return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', {

View file

@ -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>

View file

@ -9,5 +9,7 @@
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
</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>
</div>