user group membership

This commit is contained in:
Bill Burke 2015-11-12 11:31:44 -05:00
parent 33ac048c8c
commit 21119604c6
9 changed files with 262 additions and 2 deletions

View file

@ -14,6 +14,7 @@ import java.util.Map;
public class GroupRepresentation { public class GroupRepresentation {
protected String id; protected String id;
protected String name; protected String name;
protected String path;
protected Map<String, List<String>> attributes; protected Map<String, List<String>> attributes;
protected List<String> realmRoles; protected List<String> realmRoles;
protected Map<String, List<String>> clientRoles; protected Map<String, List<String>> clientRoles;
@ -35,6 +36,14 @@ public class GroupRepresentation {
this.name = name; this.name = name;
} }
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public List<String> getRealmRoles() { public List<String> getRealmRoles() {
return realmRoles; return realmRoles;
} }

View file

@ -447,6 +447,21 @@ module.config([ '$routeProvider', function($routeProvider) {
}, },
controller : 'UserRoleMappingCtrl' 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', { .when('/realms/:realm/users/:user/sessions', {
templateUrl : resourceUrl + '/partials/user-sessions.html', templateUrl : resourceUrl + '/partials/user-sessions.html',
resolve : { resolve : {

View file

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

View file

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

View file

@ -94,7 +94,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</form> </form>
</div> </div>

View file

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

View file

@ -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-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] == '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] == '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] == '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] == '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> <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>

View file

@ -60,10 +60,25 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ModelToRepresentation { 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) { public static GroupRepresentation toRepresentation(GroupModel group, boolean full) {
GroupRepresentation rep = new GroupRepresentation(); GroupRepresentation rep = new GroupRepresentation();
rep.setId(group.getId()); rep.setId(group.getId());
rep.setName(group.getName()); rep.setName(group.getName());
rep.setPath(buildGroupPath(group));
if (!full) return rep; if (!full) return rep;
// Role mappings // Role mappings
Set<RoleModel> roles = group.getRoleMappings(); Set<RoleModel> roles = group.getRoleMappings();

View file

@ -17,6 +17,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; 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.ClientMappingsRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation;
@ -911,4 +913,57 @@ public class UsersResource {
return clientSession; 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);
}
} }