user group membership
This commit is contained in:
parent
33ac048c8c
commit
21119604c6
9 changed files with 262 additions and 2 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 : {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
1
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
Normal file → Executable file
1
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
Normal file → Executable 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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue