From 21119604c6ca0de52c49fa8ae2e4c10f17d3429e Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 12 Nov 2015 11:31:44 -0500 Subject: [PATCH] user group membership --- .../idm/GroupRepresentation.java | 9 ++ .../theme/base/admin/resources/js/app.js | 15 ++++ .../admin/resources/js/controllers/users.js | 61 +++++++++++++ .../theme/base/admin/resources/js/services.js | 20 +++++ .../resources/partials/role-mappings.html | 1 - .../partials/user-group-membership.html | 85 +++++++++++++++++++ .../resources/templates/kc-tabs-user.html | 1 + .../models/utils/ModelToRepresentation.java | 15 ++++ .../resources/admin/UsersResource.java | 57 ++++++++++++- 9 files changed, 262 insertions(+), 2 deletions(-) create mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html mode change 100644 => 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java index cd5292f071..4414f240af 100755 --- a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java @@ -14,6 +14,7 @@ import java.util.Map; public class GroupRepresentation { protected String id; protected String name; + protected String path; protected Map> attributes; protected List realmRoles; protected Map> clientRoles; @@ -35,6 +36,14 @@ public class GroupRepresentation { this.name = name; } + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + public List getRealmRoles() { return realmRoles; } diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 2fd5382eb6..c148a02cdf 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -447,6 +447,21 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'UserRoleMappingCtrl' }) + .when('/realms/:realm/users/:user/groups', { + templateUrl : resourceUrl + '/partials/user-group-membership.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + groups : function(GroupListLoader) { + return GroupListLoader(); + } + }, + controller : 'UserGroupMembershipCtrl' + }) .when('/realms/:realm/users/:user/sessions', { templateUrl : resourceUrl + '/partials/user-sessions.html', resolve : { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 4f45d63bb3..f42f8aa2fb 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -1090,3 +1090,64 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov }); +module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, groups, user, UserGroupMembership, UserGroupMapping, Notifications, $location, Dialog) { + $scope.realm = realm; + $scope.user = user; + $scope.groupList = groups; + $scope.selectedGroup = null; + $scope.tree = []; + + UserGroupMembership.query({realm: realm.realm, userId: user.id}, function(data) { + $scope.groupMemberships = data; + + }); + + $scope.joinGroup = function() { + if (!$scope.tree.currentNode) { + Notifications.error('Please select a group to add'); + return; + }; + UserGroupMapping.update({realm: realm.realm, userId: user.id, groupId: $scope.tree.currentNode.id}, function() { + Notifications.success('Added group membership'); + $route.reload(); + }); + + }; + + $scope.leaveGroup = function() { + UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() { + Notifications.success('Removed group membership'); + $route.reload(); + }); + + }; + + var isLeaf = function(node) { + return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + }; + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + return 'cut'; + } + return undefined; + } + +}); + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index b9e4705c7e..c2d8c32b0b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1512,6 +1512,26 @@ module.factory('GroupCompositeClientRoleMapping', function($resource) { }); }); +module.factory('UserGroupMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', { + realm : '@realm', + userId : '@userId' + }); +}); + +module.factory('UserGroupMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/:groupId', { + realm : '@realm', + userId : '@userId', + groupId : '@groupId' + }, { + update : { + method : 'PUT' + } + }); +}); + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index 7904e56b1c..d81bea7c08 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -94,7 +94,6 @@ - diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html new file mode 100755 index 0000000000..a8e2c131c9 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html @@ -0,0 +1,85 @@ +
+ + + + +
+
+ + +
+
+
+ + + + + + + + + + + +
+
+ + Groups user is a member of. Select a listed group and click the Leave button to leave the group. + +
+ +
+
+
+ + + +
+
+
+ + + + + + + + + + + +
+ +
+ + Groups a user can join. Select a group and click the join button. + +
+ +
+
+
+
+ +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html old mode 100644 new mode 100755 index edc3a66481..7508c83a53 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html @@ -10,6 +10,7 @@
  • Attributes
  • Credentials
  • Role Mappings
  • +
  • Groups
  • Consents
  • Sessions
  • Identity Provider Links
  • diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index c62d96e446..59c640c16b 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -60,10 +60,25 @@ import java.util.Set; * @version $Revision: 1 $ */ public class ModelToRepresentation { + public static void buildGroupPath(StringBuilder sb, GroupModel group) { + if (group.getParent() != null) { + buildGroupPath(sb, group.getParent()); + } + sb.append('/').append(group.getName()); + } + + public static String buildGroupPath(GroupModel group) { + StringBuilder sb = new StringBuilder(); + buildGroupPath(sb, group); + return sb.toString(); + } + + public static GroupRepresentation toRepresentation(GroupModel group, boolean full) { GroupRepresentation rep = new GroupRepresentation(); rep.setId(group.getId()); rep.setName(group.getName()); + rep.setPath(buildGroupPath(group)); if (!full) return rep; // Role mappings Set roles = group.getRoleMappings(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 9ba114c778..a85c9d2bfd 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -17,6 +17,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -36,6 +37,7 @@ import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; @@ -676,7 +678,7 @@ public class UsersResource { } - /** + /** * Set up a temporary password for the user * * User will have to reset the temporary password next time they log in. @@ -911,4 +913,57 @@ public class UsersResource { return clientSession; } + @GET + @Path("{id}/groups") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public List groupMembership(@PathParam("id") String id) { + auth.requireView(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + List memberships = new LinkedList<>(); + for (GroupModel group : user.getGroups()) { + memberships.add(ModelToRepresentation.toRepresentation(group, false)); + } + return memberships; + } + + @DELETE + @Path("{id}/groups/{groupId}") + @NoCache + public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) { + auth.requireManage(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + GroupModel group = session.realms().getGroupById(groupId, realm); + if (group == null) { + throw new NotFoundException("Group not found"); + } + if (user.isMemberOf(group)) user.leaveGroup(group); + } + + @PUT + @Path("{id}/groups/{groupId}") + @NoCache + public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) { + auth.requireManage(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + GroupModel group = session.realms().getGroupById(groupId, realm); + if (group == null) { + throw new NotFoundException("Group not found"); + } + if (!user.isMemberOf(group)) user.joinGroup(group); + } + + }