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;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -191,6 +192,19 @@ public class PropertyFileUserStorageProvider implements
|
|||
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) {
|
||||
// runtime automatically handles querying UserFederatedStorage
|
||||
|
|
|
@ -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;
|
||||
|
@ -72,4 +73,9 @@ public interface RoleResource {
|
|||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
|
|
|
@ -497,6 +497,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) {
|
||||
em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
|
||||
|
@ -636,6 +650,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) {
|
||||
return searchForUser(search, realm, -1, -1);
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -329,6 +329,11 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
|
|||
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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -132,5 +132,4 @@ public class ClientRolesTest extends AbstractClientTest {
|
|||
assertFalse(rolesRsc.get("role-a").toRepresentation().isComposite());
|
||||
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.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,8 +67,14 @@ 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);
|
||||
|
@ -79,17 +93,34 @@ public class RealmRolesTest extends AbstractAdminTest {
|
|||
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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 : {
|
||||
|
|
|
@ -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) {
|
||||
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>
|
||||
<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>
|
Loading…
Reference in a new issue