keycloak-aplcache

Merge pull request #1817 from patriot1burke/master user

11/12/2015 4:09:37 PM

Details

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 cd5292f..4414f24 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<String, List<String>>  attributes;
     protected List<String> realmRoles;
     protected Map<String, List<String>> 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<String> 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 2fd5382..c148a02 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 4f45d63..f42f8aa 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 b9e4705..c2d8c32 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 7904e56..d81bea7 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 @@
                     </div>
                 </div>
             </div>
-        </div>
     </form>
 </div>
 
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 0000000..a8e2c13
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
@@ -0,0 +1,85 @@
+ <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}}/users">Users</a></li>
+         <li>{{user.username}}</li>
+     </ol>
+
+     <kc-tabs-user></kc-tabs-user>
+
+     <form class="form-horizontal" name="realmForm" novalidate>
+         <div class="form-group" kc-read-only="!access.manageUsers">
+             <label class="col-md-1 control-label" class="control-label"></label>
+
+             <div class="col-md-8" >
+                  <div class="row">
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+                                     <div class="form-inline">
+                                         <label class="control-label">Group Membership</label>
+                                         <kc-tooltip>Groups user is a member of.  Select a listed group and click the Leave button to leave the group.</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                             <button id="leaveGroups" class="btn btn-default" ng-click="leaveGroup()">Leave</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                             <tbody>
+                             <tr>
+                                 <td>
+                                     <select id="groupMembership" class="form-control" size=5
+                                                            ng-model="selectedGroup"
+                                                            ng-options="r.path for r in groupMemberships">
+                                         <option style="display:none" value="">select a type</option>
+                                 </select>
+
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+
+                                     <div class="form-inline">
+                                         <label class="control-label">Available Groups</label>
+                                         <kc-tooltip>Groups a user can join.  Select a group and click the join button.</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                             <button id="joinGroup" class="btn btn-default" ng-click="joinGroup()">Join</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                         <tbody>
+                             <tr>
+                                 <td>                             <div
+                                         tree-id="tree"
+                                         angular-treeview="true"
+                                         tree-model="groupList"
+                                         node-id="id"
+                                         node-label="name"
+                                         node-children="subGroups" >
+                                 </div>
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                 </div>
+             </div>
+         </div>
+     </form>
+ </div>
+
+<kc-menu></kc-menu>
\ 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 edc3a66..7508c83
--- 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 @@
         <li ng-class="{active: path[4] == 'user-attributes'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-attributes">Attributes</a></li>
         <li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
         <li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
+        <li ng-class="{active: path[4] == 'groups'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/groups">Groups</a></li>
         <li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
         <li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
         <li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
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 c62d96e..59c640c 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<RoleModel> 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 9ba114c..a85c9d2 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<GroupRepresentation> groupMembership(@PathParam("id") String id) {
+        auth.requireView();
+
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        List<GroupRepresentation> 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);
+    }
+
+
 }