GUACAMOLE-220: Add management tab and editor for user groups.

This commit is contained in:
Michael Jumper
2018-04-19 23:51:25 -07:00
parent de80957404
commit 8ad3f25371
18 changed files with 1244 additions and 3 deletions

View File

@@ -171,6 +171,15 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
resolve : { updateCurrentToken: updateCurrentToken }
})
// User group editor
.when('/manage/:dataSource/userGroups/:id?', {
title : 'APP.NAME',
bodyClassName : 'manage',
templateUrl : 'app/manage/templates/manageUserGroup.html',
controller : 'manageUserGroupController',
resolve : { updateCurrentToken: updateCurrentToken }
})
// Client view
.when('/client/:id/:params?', {
bodyClassName : 'client',

View File

@@ -18,12 +18,14 @@
*/
.user,
.user-group,
.connection-group,
.connection {
cursor: pointer;
}
.user a,
.user-group a,
.connection a,
.connection-group a {
text-decoration:none;
@@ -31,6 +33,7 @@
}
.user a:hover,
.user-group a:hover,
.connection a:hover,
.connection-group a:hover {
text-decoration:none;
@@ -38,6 +41,7 @@
}
.user a:visited,
.user-group a:visited,
.connection a:visited,
.connection-group a:visited {
text-decoration:none;

View File

@@ -156,6 +156,14 @@ div.section {
background-image: url('images/action-icons/guac-user-add.png');
}
.icon.user-group {
background-image: url('images/user-icons/guac-user-group.png');
}
.icon.user-group.add {
background-image: url('images/action-icons/guac-user-group-add.png');
}
.icon.connection {
background-image: url('images/protocol-icons/guac-plug.png');
}

View File

@@ -0,0 +1,538 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* The controller for editing user groups.
*/
angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
function manageUserGroupController($scope, $injector) {
// Required types
var ManagementPermissions = $injector.get('ManagementPermissions');
var PermissionFlagSet = $injector.get('PermissionFlagSet');
var PermissionSet = $injector.get('PermissionSet');
var UserGroup = $injector.get('UserGroup');
// Required services
var $location = $injector.get('$location');
var $routeParams = $injector.get('$routeParams');
var $q = $injector.get('$q');
var authenticationService = $injector.get('authenticationService');
var dataSourceService = $injector.get('dataSourceService');
var membershipService = $injector.get('membershipService');
var permissionService = $injector.get('permissionService');
var requestService = $injector.get('requestService');
var schemaService = $injector.get('schemaService');
var userGroupService = $injector.get('userGroupService');
var userService = $injector.get('userService');
/**
* The identifiers of all data sources currently available to the
* authenticated user.
*
* @type String[]
*/
var dataSources = authenticationService.getAvailableDataSources();
/**
* The username of the current, authenticated user.
*
* @type String
*/
var currentUsername = authenticationService.getCurrentUsername();
/**
* The identifier of the original user group from which this user group is
* being cloned. Only valid if this is a new user group.
*
* @type String
*/
var cloneSourceIdentifier = $location.search().clone;
/**
* The identifier of the user group being edited. If a new user group is
* being created, this will not be defined.
*
* @type String
*/
var identifier = $routeParams.id;
/**
* The unique identifier of the data source containing the user group being
* edited.
*
* @type String
*/
$scope.dataSource = $routeParams.dataSource;
/**
* All user groups associated with the same identifier as the group being
* created or edited, as a map of data source identifier to the UserGroup
* object within that data source.
*
* @type Object.<String, UserGroup>
*/
$scope.userGroups = null;
/**
* The user group being modified.
*
* @type UserGroup
*/
$scope.userGroup = null;
/**
* All permissions associated with the user group being modified.
*
* @type PermissionFlagSet
*/
$scope.permissionFlags = null;
/**
* The set of permissions that will be added to the user group when the
* user group is saved. Permissions will only be present in this set if they
* are manually added, and not later manually removed before saving.
*
* @type PermissionSet
*/
$scope.permissionsAdded = new PermissionSet();
/**
* The set of permissions that will be removed from the user group when the
* user group is saved. Permissions will only be present in this set if they
* are manually removed, and not later manually added before saving.
*
* @type PermissionSet
*/
$scope.permissionsRemoved = new PermissionSet();
/**
* The identifiers of all user groups which can be manipulated (all groups
* for which the user accessing this interface has UPDATE permission),
* whether that means changing the members of those groups or changing the
* groups of which those groups are members. If this information has not
* yet been retrieved, this will be null.
*
* @type String[]
*/
$scope.availableGroups = null;
/**
* The identifiers of all users which can be manipulated (all users for
* which the user accessing this interface has UPDATE permission), either
* through adding those users as a member of the current group or removing
* those users from the current group. If this information has not yet been
* retrieved, this will be null.
*
* @type String[]
*/
$scope.availableUsers = null;
/**
* The identifiers of all user groups of which this group is a member,
* taking into account any user groups which will be added/removed when
* saved. If this information has not yet been retrieved, this will be
* null.
*
* @type String[]
*/
$scope.parentGroups = null;
/**
* The set of identifiers of all parent user groups to which this group
* will be added when saved. Parent groups will only be present in this set
* if they are manually added, and not later manually removed before
* saving.
*
* @type String[]
*/
$scope.parentGroupsAdded = [];
/**
* The set of identifiers of all parent user groups from which this group
* will be removed when saved. Parent groups will only be present in this
* set if they are manually removed, and not later manually added before
* saving.
*
* @type String[]
*/
$scope.parentGroupsRemoved = [];
/**
* The identifiers of all user groups which are members of this group,
* taking into account any user groups which will be added/removed when
* saved. If this information has not yet been retrieved, this will be
* null.
*
* @type String[]
*/
$scope.memberGroups = null;
/**
* The set of identifiers of all member user groups which will be added to
* this group when saved. Member groups will only be present in this set if
* they are manually added, and not later manually removed before saving.
*
* @type String[]
*/
$scope.memberGroupsAdded = [];
/**
* The set of identifiers of all member user groups which will be removed
* from this group when saved. Member groups will only be present in this
* set if they are manually removed, and not later manually added before
* saving.
*
* @type String[]
*/
$scope.memberGroupsRemoved = [];
/**
* The identifiers of all users which are members of this group, taking
* into account any users which will be added/removed when saved. If this
* information has not yet been retrieved, this will be null.
*
* @type String[]
*/
$scope.memberUsers = null;
/**
* The set of identifiers of all member users which will be added to this
* group when saved. Member users will only be present in this set if they
* are manually added, and not later manually removed before saving.
*
* @type String[]
*/
$scope.memberUsersAdded = [];
/**
* The set of identifiers of all member users which will be removed from
* this group when saved. Member users will only be present in this set if
* they are manually removed, and not later manually added before saving.
*
* @type String[]
*/
$scope.memberUsersRemoved = [];
/**
* For each applicable data source, the management-related actions that the
* current user may perform on the user group currently being created
* or modified, as a map of data source identifier to the
* {@link ManagementPermissions} object describing the actions available
* within that data source, or null if the current user's permissions have
* not yet been loaded.
*
* @type Object.<String, ManagementPermissions>
*/
$scope.managementPermissions = null;
/**
* All available user group attributes. This is only the set of attribute
* definitions, organized as logical groupings of attributes, not attribute
* values.
*
* @type Form[]
*/
$scope.attributes = null;
/**
* Returns whether critical data has completed being loaded.
*
* @returns {Boolean}
* true if enough data has been loaded for the user group interface to
* be useful, false otherwise.
*/
$scope.isLoaded = function isLoaded() {
return $scope.userGroups !== null
&& $scope.permissionFlags !== null
&& $scope.managementPermissions !== null
&& $scope.availableGroups !== null
&& $scope.availableUsers !== null
&& $scope.parentGroups !== null
&& $scope.memberGroups !== null
&& $scope.memberUsers !== null
&& $scope.attributes !== null;
};
/**
* Returns whether the current user can edit the identifier of the user
* group being edited.
*
* @returns {Boolean}
* true if the current user can edit the identifier of the user group
* being edited, false otherwise.
*/
$scope.canEditIdentifier = function canEditIdentifier() {
return !identifier;
};
/**
* Loads the data associated with the user group having the given
* identifier, preparing the interface for making modifications to that
* existing user group.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group
* to load.
*
* @param {String} identifier
* The unique identifier of the user group to load.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* editing the given user group.
*/
var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
return $q.all({
userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
permissions : permissionService.getPermissions(dataSource, identifier, true),
parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
memberUsers : membershipService.getMemberUsers(dataSource, identifier)
})
.then(function userGroupDataRetrieved(values) {
$scope.userGroups = values.userGroups;
$scope.userGroup = values.userGroups[dataSource];
$scope.parentGroups = values.parentGroups;
$scope.memberGroups = values.memberGroups;
$scope.memberUsers = values.memberUsers;
// Create skeleton user group if user group does not exist
if (!$scope.userGroup)
$scope.userGroup = new UserGroup({
'identifier' : identifier
});
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
});
};
/**
* Loads the data associated with the user group having the given
* identifier, preparing the interface for cloning that existing user
* group.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group to
* be cloned.
*
* @param {String} identifier
* The unique identifier of the user group being cloned.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* cloning the given user group.
*/
var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
return $q.all({
userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
permissions : permissionService.getPermissions(dataSource, identifier, true),
parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
memberUsers : membershipService.getMemberUsers(dataSource, identifier)
})
.then(function userGroupDataRetrieved(values) {
$scope.userGroups = {};
$scope.userGroup = values.userGroups[dataSource];
$scope.parentGroups = values.parentGroups;
$scope.parentGroupsAdded = values.parentGroups;
$scope.memberGroups = values.memberGroups;
$scope.memberGroupsAdded = values.memberGroups;
$scope.memberUsers = values.memberUsers;
$scope.memberUsersAdded = values.memberUsers;
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
$scope.permissionsAdded = values.permissions;
});
};
/**
* Loads skeleton user group data, preparing the interface for creating a
* new user group.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* creating a new user group.
*/
var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
// No user groups exist regardless of data source if the user group is
// being created
$scope.userGroups = {};
// Use skeleton user group object with no associated permissions
$scope.userGroup = new UserGroup();
$scope.parentGroups = [];
$scope.memberGroups = [];
$scope.memberUsers = [];
$scope.permissionFlags = new PermissionFlagSet();
return $q.resolve();
};
/**
* Loads the data required for performing the management task requested
* through the route parameters given at load time, automatically preparing
* the interface for editing an existing user group, cloning an existing
* user group, or creating an entirely new user group.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared
* for performing the requested management task.
*/
var loadRequestedUserGroup = function loadRequestedUserGroup() {
// Pull user group data and permissions if we are editing an existing
// user group
if (identifier)
return loadExistingUserGroup($scope.dataSource, identifier);
// If we are cloning an existing user group, pull its data instead
if (cloneSourceIdentifier)
return loadClonedUserGroup($scope.dataSource, cloneSourceIdentifier);
// If we are creating a new user group, populate skeleton user group data
return loadSkeletonUserGroup();
};
// Populate interface with requested data
$q.all({
userGroupData : loadRequestedUserGroup(),
permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
userGroups : userGroupService.getUserGroups($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
users : userService.getUsers($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
attributes : schemaService.getUserGroupAttributes($scope.dataSource)
})
.then(function dataReceived(values) {
$scope.attributes = values.attributes;
$scope.managementPermissions = {};
angular.forEach(dataSources, function deriveManagementPermissions(dataSource) {
// Determine whether data source contains this user group
var exists = (dataSource in $scope.userGroups);
// Add the identifiers of all modifiable user groups
$scope.availableGroups = [];
angular.forEach(values.userGroups, function addUserGroupIdentifier(userGroup) {
$scope.availableGroups.push(userGroup.identifier);
});
// Add the identifiers of all modifiable users
$scope.availableUsers = [];
angular.forEach(values.users, function addUserIdentifier(user) {
$scope.availableUsers.push(user.username);
});
// Calculate management actions available for this specific group
$scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
values.permissions[dataSource],
PermissionSet.SystemPermissionType.CREATE_USER_GROUP,
PermissionSet.hasUserGroupPermission,
exists ? identifier : null);
});
}, requestService.WARN);
/**
* Returns the URL for the page which manages the user group currently
* being edited under the given data source. The given data source need not
* be the same as the data source currently selected.
*
* @param {String} dataSource
* The unique identifier of the data source that the URL is being
* generated for.
*
* @returns {String}
* The URL for the page which manages the user group currently being
* edited under the given data source.
*/
$scope.getUserGroupURL = function getUserGroupURL(dataSource) {
return '/manage/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier || '');
};
/**
* Cancels all pending edits, returning to the main list of user groups.
*/
$scope.returnToUserGroupList = function returnToUserGroupList() {
$location.url('/settings/userGroups');
};
/**
* Cancels all pending edits, opening an edit page for a new user group
* which is prepopulated with the data from the user currently being edited.
*/
$scope.cloneUserGroup = function cloneUserGroup() {
$location.path('/manage/' + encodeURIComponent($scope.dataSource) + '/userGroups').search('clone', identifier);
};
/**
* Saves the current user group, creating a new user group or updating the
* existing user group depending on context, returning a promise which is
* resolved if the save operation succeeds and rejected if the save
* operation fails.
*
* @returns {Promise}
* A promise which is resolved if the save operation succeeds and is
* rejected with an {@link Error} if the save operation fails.
*/
$scope.saveUserGroup = function saveUserGroup() {
// Save or create the user group, depending on whether the user group exists
var saveUserGroupPromise;
if ($scope.dataSource in $scope.userGroups)
saveUserGroupPromise = userGroupService.saveUserGroup($scope.dataSource, $scope.userGroup);
else
saveUserGroupPromise = userGroupService.createUserGroup($scope.dataSource, $scope.userGroup);
return saveUserGroupPromise.then(function savedUserGroup() {
return $q.all([
permissionService.patchPermissions($scope.dataSource, $scope.userGroup.identifier, $scope.permissionsAdded, $scope.permissionsRemoved, true),
membershipService.patchUserGroups($scope.dataSource, $scope.userGroup.identifier, $scope.parentGroupsAdded, $scope.parentGroupsRemoved, true),
membershipService.patchMemberUserGroups($scope.dataSource, $scope.userGroup.identifier, $scope.memberGroupsAdded, $scope.memberGroupsRemoved),
membershipService.patchMemberUsers($scope.dataSource, $scope.userGroup.identifier, $scope.memberUsersAdded, $scope.memberUsersRemoved)
]);
});
};
/**
* Deletes the current user group, returning a promise which is resolved if
* the delete operation succeeds and rejected if the delete operation
* fails.
*
* @returns {Promise}
* A promise which is resolved if the delete operation succeeds and is
* rejected with an {@link Error} if the delete operation fails.
*/
$scope.deleteUserGroup = function deleteUserGroup() {
return userGroupService.deleteUserGroup($scope.dataSource, $scope.userGroup);
};
}]);

View File

@@ -125,6 +125,10 @@ angular.module('manage').directive('systemPermissionEditor', ['$injector',
label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
value: PermissionSet.SystemPermissionType.CREATE_USER
},
{
label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USER_GROUPS",
value: PermissionSet.SystemPermissionType.CREATE_USER_GROUP
},
{
label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
value: PermissionSet.SystemPermissionType.CREATE_CONNECTION

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.manage-user-group .page-tabs .page-list li.read-only a[href],
.manage-user-group .page-tabs .page-list li.unlinked a[href],
.manage-user-group .page-tabs .page-list li.linked a[href] {
padding-right: 2.5em;
position: relative;
}
.manage-user-group .page-tabs .page-list li.read-only a[href]:before,
.manage-user-group .page-tabs .page-list li.unlinked a[href]:before,
.manage-user-group .page-tabs .page-list li.linked a[href]:before {
content: ' ';
position: absolute;
right: 0;
bottom: 0;
top: 0;
width: 2.5em;
background-size: 1.25em;
background-repeat: no-repeat;
background-position: center;
}
.manage-user-group .page-tabs .page-list li.read-only a[href]:before {
background-image: url('images/lock.png');
}
.manage-user-group .page-tabs .page-list li.unlinked a[href]:before {
background-image: url('images/plus.png');
}
.manage-user-group .page-tabs .page-list li.unlinked a[href] {
opacity: 0.5;
}
.manage-user-group .page-tabs .page-list li.unlinked a[href]:hover,
.manage-user-group .page-tabs .page-list li.unlinked a[href].current {
opacity: 1;
}
.manage-user-group .page-tabs .page-list li.linked a[href]:before {
background-image: url('images/checkmark.png');
}
.manage-user-group .notice.read-only {
background: #FDA;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25em;
text-align: center;
padding: 1em;
}

View File

@@ -0,0 +1,101 @@
<div class="manage-user-group view" ng-class="{loading: !isLoaded()}">
<!-- User group header and data source tabs -->
<div class="header tabbed">
<h2>{{'MANAGE_USER_GROUP.SECTION_HEADER_EDIT_USER_GROUP' | translate}}</h2>
<guac-user-menu></guac-user-menu>
</div>
<data-data-source-tabs ng-hide="cloneSourceIdentifier"
permissions="managementPermissions"
url="getUserGroupURL(dataSource)">
</data-data-source-tabs>
<!-- Warn if user group is read-only -->
<div class="section" ng-hide="managementPermissions[dataSource].canSaveObject">
<p class="notice read-only">{{'MANAGE_USER_GROUP.INFO_READ_ONLY' | translate}}</p>
</div>
<!-- Sections applicable to non-read-only user groups -->
<div ng-show="managementPermissions[dataSource].canSaveObject">
<!-- User group name -->
<div class="section">
<table class="properties">
<tr>
<th>{{'MANAGE_USER_GROUP.FIELD_HEADER_USER_GROUP_NAME' | translate}}</th>
<td>
<input ng-show="canEditIdentifier()" ng-model="userGroup.identifier" type="text"/>
<span ng-hide="canEditIdentifier()">{{userGroup.identifier}}</span>
</td>
</tr>
</table>
</div>
<!-- User group attributes section -->
<div class="attributes" ng-show="managementPermissions[dataSource].canChangeAttributes">
<guac-form namespace="'USER_GROUP_ATTRIBUTES'" content="attributes"
model="userGroup.attributes"
model-only="!managementPermissions[dataSource].canChangeAllAttributes"></guac-form>
</div>
<!-- System permissions section -->
<system-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions"
data-data-source="dataSource"
permission-flags="permissionFlags"
permissions-added="permissionsAdded"
permissions-removed="permissionsRemoved">
</system-permission-editor>
<!-- Parent group section -->
<identifier-set-editor
header="MANAGE_USER_GROUP.SECTION_HEADER_USER_GROUPS"
empty-placeholder="MANAGE_USER_GROUP.HELP_NO_USER_GROUPS"
unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USER_GROUPS_AVAILABLE"
identifiers-available="availableGroups"
identifiers="parentGroups"
identifiers-added="parentGroupsAdded"
identifiers-removed="parentGroupsRemoved">
</identifier-set-editor>
<!-- Member group section -->
<identifier-set-editor
header="MANAGE_USER_GROUP.SECTION_HEADER_MEMBER_USER_GROUPS"
empty-placeholder="MANAGE_USER_GROUP.HELP_NO_MEMBER_USER_GROUPS"
unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USER_GROUPS_AVAILABLE"
identifiers-available="availableGroups"
identifiers="memberGroups"
identifiers-added="memberGroupsAdded"
identifiers-removed="memberGroupsRemoved">
</identifier-set-editor>
<!-- Member user section -->
<identifier-set-editor
header="MANAGE_USER_GROUP.SECTION_HEADER_MEMBER_USERS"
empty-placeholder="MANAGE_USER_GROUP.HELP_NO_MEMBER_USERS"
unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USERS_AVAILABLE"
identifiers-available="availableUsers"
identifiers="memberUsers"
identifiers-added="memberUsersAdded"
identifiers-removed="memberUsersRemoved">
</identifier-set-editor>
<!-- Connection permissions section -->
<connection-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions"
data-data-source="dataSource"
permission-flags="permissionFlags"
permissions-added="permissionsAdded"
permissions-removed="permissionsRemoved">
</connection-permission-editor>
<!-- Form action buttons -->
<management-buttons namespace="MANAGE_USER_GROUP"
permissions="managementPermissions[dataSource]"
save="saveUserGroup()"
delete="deleteUserGroup()"
clone="cloneUserGroup()"
return="returnToUserGroupList()">
</management-buttons>
</div>
</div>

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A service for defining the ManageableUserGroup class.
*/
angular.module('manage').factory('ManageableUserGroup', [function defineManageableUserGroup() {
/**
* A pairing of an @link{UserGroup} with the identifier of its corresponding
* data source.
*
* @constructor
* @param {Object|ManageableUserGroup} template
*/
var ManageableUserGroup = function ManageableUserGroup(template) {
/**
* The unique identifier of the data source containing this user.
*
* @type String
*/
this.dataSource = template.dataSource;
/**
* The @link{UserGroup} object represented by this ManageableUserGroup
* and contained within the associated data source.
*
* @type UserGroup
*/
this.userGroup = template.userGroup;
};
return ManageableUserGroup;
}]);

View File

@@ -192,6 +192,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
var pages = [];
var canManageUsers = [];
var canManageUserGroups = [];
var canManageConnections = [];
var canViewConnectionRecords = [];
var canManageSessions = [];
@@ -235,6 +236,24 @@ angular.module('navigation').factory('userPageService', ['$injector',
canManageUsers.push(dataSource);
}
// Determine whether the current user needs access to the group management UI
if (
// System permissions
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER_GROUP)
// Permission to update user groups
|| PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
// Permission to delete user groups
|| PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
// Permission to administer user groups
|| PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
) {
canManageUserGroups.push(dataSource);
}
// Determine whether the current user needs access to the connection management UI
if (
// System permissions
@@ -295,6 +314,14 @@ angular.module('navigation').factory('userPageService', ['$injector',
}));
}
// If user can manage user groups, add link to group management page
if (canManageUserGroups.length) {
pages.push(new PageDefinition({
name : 'USER_MENU.ACTION_MANAGE_USER_GROUPS',
url : '/settings/userGroups'
}));
}
// If user can manage connections, add links for connection management pages
angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) {
pages.push(new PageDefinition({

View File

@@ -36,8 +36,8 @@ angular.module('manage').controller('settingsController', ['$scope', '$injector'
$scope.settingsPages = null;
/**
* The currently-selected settings tab. This may be 'users', 'connections',
* or 'sessions'.
* The currently-selected settings tab. This may be 'users', 'userGroups',
* 'connections', 'history', 'preferences', or 'sessions'.
*
* @type String
*/

View File

@@ -0,0 +1,270 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A directive for managing all user groups in the system.
*/
angular.module('settings').directive('guacSettingsUserGroups', ['$injector',
function guacSettingsUserGroups($injector) {
// Required types
var ManageableUserGroup = $injector.get('ManageableUserGroup');
var PermissionSet = $injector.get('PermissionSet');
var SortOrder = $injector.get('SortOrder');
// Required services
var $location = $injector.get('$location');
var authenticationService = $injector.get('authenticationService');
var dataSourceService = $injector.get('dataSourceService');
var permissionService = $injector.get('permissionService');
var requestService = $injector.get('requestService');
var userGroupService = $injector.get('userGroupService');
var directive = {
restrict : 'E',
replace : true,
templateUrl : 'app/settings/templates/settingsUserGroups.html',
scope : {}
};
directive.controller = ['$scope', function settingsUserGroupsController($scope) {
// Identifier of the current user
var currentUsername = authenticationService.getCurrentUsername();
/**
* The identifiers of all data sources accessible by the current
* user.
*
* @type String[]
*/
var dataSources = authenticationService.getAvailableDataSources();
/**
* Map of data source identifiers to all permissions associated
* with the current user within that data source, or null if the
* user's permissions have not yet been loaded.
*
* @type Object.<String, PermissionSet>
*/
var permissions = null;
/**
* All visible user groups, along with their corresponding data
* sources.
*
* @type ManageableUserGroup[]
*/
$scope.manageableUserGroups = null;
/**
* Array of all user group properties that are filterable.
*
* @type String[]
*/
$scope.filteredUserGroupProperties = [
'userGroup.identifier'
];
/**
* SortOrder instance which stores the sort order of the listed
* user groups.
*
* @type SortOrder
*/
$scope.order = new SortOrder([
'userGroup.identifier'
]);
/**
* Returns whether critical data has completed being loaded.
*
* @returns {Boolean}
* true if enough data has been loaded for the user group
* interface to be useful, false otherwise.
*/
$scope.isLoaded = function isLoaded() {
return $scope.manageableUserGroups !== null;
};
/**
* Returns the identifier of the data source that should be used by
* default when creating a new user group.
*
* @return {String}
* The identifier of the data source that should be used by
* default when creating a new user group, or null if user group
* creation is not allowed.
*/
$scope.getDefaultDataSource = function getDefaultDataSource() {
// Abort if permissions have not yet loaded
if (!permissions)
return null;
// For each data source
for (var dataSource in permissions) {
// Retrieve corresponding permission set
var permissionSet = permissions[dataSource];
// Can create user groups if adminstrator or have explicit permission
if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER)
|| PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_USER_GROUP))
return dataSource;
}
// No data sources allow user group creation
return null;
};
/**
* Returns whether the current user can create new user groups
* within at least one data source.
*
* @return {Boolean}
* true if the current user can create new user groups within at
* least one data source, false otherwise.
*/
$scope.canCreateUserGroups = function canCreateUserGroups() {
return $scope.getDefaultDataSource() !== null;
};
/**
* Returns whether the current user can create new user groups or
* make changes to existing user groups within at least one data
* source. The user group management interface as a whole is useless
* if this function returns false.
*
* @return {Boolean}
* true if the current user can create new user groups or make
* changes to existing user groups within at least one data
* source, false otherwise.
*/
var canManageUserGroups = function canManageUserGroups() {
// Abort if permissions have not yet loaded
if (!permissions)
return false;
// Creating user groups counts as management
if ($scope.canCreateUserGroups())
return true;
// For each data source
for (var dataSource in permissions) {
// Retrieve corresponding permission set
var permissionSet = permissions[dataSource];
// Can manage user groups if granted explicit update or delete
if (PermissionSet.hasUserGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE)
|| PermissionSet.hasUserGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE))
return true;
}
// No data sources allow management of user groups
return false;
};
/**
* Sets the displayed list of user groups. If any user groups are
* already shown within the interface, those user groups are replaced
* with the given user groups.
*
* @param {Object.<String, PermissionSet>} permissions
* A map of data source identifiers to all permissions associated
* with the current user within that data source.
*
* @param {Object.<String, Object.<String, UserGroup>>} userGroups
* A map of all user groups which should be displayed, where each
* key is the data source identifier from which the user groups
* were retrieved and each value is a map of user group identifiers
* to their corresponding @link{UserGroup} objects.
*/
var setDisplayedUserGroups = function setDisplayedUserGroups(permissions, userGroups) {
var addedUserGroups = {};
$scope.manageableUserGroups = [];
// For each user group in each data source
angular.forEach(dataSources, function addUserGroupList(dataSource) {
angular.forEach(userGroups[dataSource], function addUserGroup(userGroup) {
// Do not add the same user group twice
if (addedUserGroups[userGroup.identifier])
return;
// Link to default creation data source if we cannot manage this user
if (!PermissionSet.hasSystemPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.ADMINISTER)
&& !PermissionSet.hasUserGroupPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.UPDATE, userGroup.identifier)
&& !PermissionSet.hasUserGroupPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.DELETE, userGroup.identifier))
dataSource = $scope.getDefaultDataSource();
// Add user group to overall list
addedUserGroups[userGroup.identifier] = userGroup;
$scope.manageableUserGroups.push(new ManageableUserGroup ({
'dataSource' : dataSource,
'userGroup' : userGroup
}));
});
});
};
// Retrieve current permissions
dataSourceService.apply(
permissionService.getEffectivePermissions,
dataSources,
currentUsername
)
.then(function permissionsRetrieved(retrievedPermissions) {
// Store retrieved permissions
permissions = retrievedPermissions;
// Return to home if there's nothing to do here
if (!canManageUserGroups())
$location.path('/');
// If user groups can be created, list all readable user groups
if ($scope.canCreateUserGroups())
return dataSourceService.apply(userGroupService.getUserGroups, dataSources);
// Otherwise, list only updateable/deletable users
return dataSourceService.apply(userGroupService.getUserGroups, dataSources, [
PermissionSet.ObjectPermissionType.UPDATE,
PermissionSet.ObjectPermissionType.DELETE
]);
})
.then(function userGroupsReceived(userGroups) {
setDisplayedUserGroups(permissions, userGroups);
}, requestService.WARN);
}];
return directive;
}]);

View File

@@ -18,6 +18,7 @@
*/
a.button.add-user,
a.button.add-user-group,
a.button.add-connection,
a.button.add-connection-group {
font-size: 0.8em;
@@ -26,6 +27,7 @@ a.button.add-connection-group {
}
a.button.add-user::before,
a.button.add-user-group::before,
a.button.add-connection::before,
a.button.add-connection-group::before {
@@ -46,6 +48,10 @@ a.button.add-user::before {
background-image: url('images/action-icons/guac-user-add.png');
}
a.button.add-user-group::before {
background-image: url('images/action-icons/guac-user-group-add.png');
}
a.button.add-connection::before {
background-image: url('images/action-icons/guac-monitor-add.png');
}

View File

@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.settings.user-groups table.user-group-list {
width: 100%;
}
.settings.user-groups table.user-group-list th.user-group-name,
.settings.user-groups table.user-group-list td.user-group-name {
width: 100%;
}
.settings.user-groups table.user-group-list tr.user td.user-group-name a[href] {
display: block;
padding: .5em 1em;
}
.settings.user-groups table.user-group-list tr.user td.user-group-name {
padding: 0;
}

View File

@@ -13,6 +13,7 @@
<!-- Selected tab -->
<guac-settings-users ng-if="activeTab === 'users'"></guac-settings-users>
<guac-settings-user-groups ng-if="activeTab === 'userGroups'"></guac-settings-user-groups>
<guac-settings-connections ng-if="activeTab === 'connections'"></guac-settings-connections>
<guac-settings-connection-history ng-if="activeTab === 'history'"></guac-settings-connection-history>
<guac-settings-sessions ng-if="activeTab === 'sessions'"></guac-settings-sessions>

View File

@@ -0,0 +1,48 @@
<div class="settings section user-groups" ng-class="{loading: !isLoaded()}">
<!-- User group management -->
<p>{{'SETTINGS_USER_GROUPS.HELP_USER_GROUPS' | translate}}</p>
<!-- User management toolbar -->
<div class="toolbar">
<!-- Form action buttons -->
<div class="action-buttons">
<a class="add-user-group button" ng-show="canCreateUserGroups()"
href="#/manage/{{getDefaultDataSource()}}/userGroups/">{{'SETTINGS_USER_GROUPS.ACTION_NEW_USER_GROUP' | translate}}</a>
</div>
<!-- User group filter -->
<guac-filter filtered-items="filteredManageableUserGroups" items="manageableUserGroups"
placeholder="'SETTINGS_USER_GROUPS.FIELD_PLACEHOLDER_FILTER' | translate"
properties="filteredUserGroupProperties"></guac-filter>
</div>
<!-- List of user groups this user has access to -->
<table class="sorted user-group-list">
<thead>
<tr>
<th guac-sort-order="order" guac-sort-property="'userGroup.identifier'" class="user-group-name">
{{'SETTINGS_USER_GROUPS.TABLE_HEADER_USER_GROUP_NAME' | translate}}
</th>
</tr>
</thead>
<tbody ng-class="{loading: !isLoaded()}">
<tr ng-repeat="manageableUserGroup in manageableUserGroupPage" class="user-group">
<td class="user-group-name">
<a ng-href="#/manage/{{manageableUserGroup.dataSource}}/userGroups/{{manageableUserGroup.userGroup.identifier}}">
<div class="icon user-group"></div>
<span class="name">{{manageableUserGroup.userGroup.identifier}}</span>
</a>
</td>
</tr>
</tbody>
</table>
<!-- Pager controls for user group list -->
<guac-pager page="manageableUserGroupPage" page-size="25"
items="filteredManageableUserGroups | orderBy : order.predicate"></guac-pager>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -21,6 +21,7 @@
"ACTION_MANAGE_SETTINGS" : "Settings",
"ACTION_MANAGE_SESSIONS" : "Active Sessions",
"ACTION_MANAGE_USERS" : "Users",
"ACTION_MANAGE_USER_GROUPS" : "Groups",
"ACTION_NAVIGATE_BACK" : "Back",
"ACTION_NAVIGATE_HOME" : "Home",
"ACTION_SAVE" : "Save",
@@ -292,6 +293,7 @@
"FIELD_HEADER_ADMINISTER_SYSTEM" : "Administer system:",
"FIELD_HEADER_CHANGE_OWN_PASSWORD" : "Change own password:",
"FIELD_HEADER_CREATE_NEW_USERS" : "Create new users:",
"FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "Create new user groups:",
"FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "Create new connections:",
"FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "Create new connection groups:",
"FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "Create new sharing profiles:",
@@ -316,6 +318,49 @@
"TEXT_CONFIRM_DELETE" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?"
},
"MANAGE_USER_GROUP" : {
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
"ACTION_CLONE" : "@:APP.ACTION_CLONE",
"ACTION_DELETE" : "@:APP.ACTION_DELETE",
"ACTION_SAVE" : "@:APP.ACTION_SAVE",
"DIALOG_HEADER_CONFIRM_DELETE" : "Delete Group",
"DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR",
"FIELD_HEADER_ADMINISTER_SYSTEM" : "@:MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM",
"FIELD_HEADER_CHANGE_OWN_PASSWORD" : "@:MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD",
"FIELD_HEADER_CREATE_NEW_USERS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
"FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USER_GROUPS",
"FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
"FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS",
"FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES",
"FIELD_HEADER_USER_GROUP_NAME" : "Group name:",
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
"HELP_NO_USER_GROUPS" : "This group does not currently belong to any groups. Expand this section to add groups.",
"HELP_NO_MEMBER_USER_GROUPS" : "This group does not currently contain any groups. Expand this section to add groups.",
"HELP_NO_MEMBER_USERS" : "This group does not currently contain any users. Expand this section to add users.",
"INFO_READ_ONLY" : "Sorry, but this group cannot be edited.",
"INFO_NO_USER_GROUPS_AVAILABLE" : "@:MANAGE_USER.INFO_NO_USER_GROUPS_AVAILABLE",
"INFO_NO_USERS_AVAILABLE" : "No users available.",
"SECTION_HEADER_ALL_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_ALL_CONNECTIONS",
"SECTION_HEADER_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CONNECTIONS",
"SECTION_HEADER_CURRENT_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CURRENT_CONNECTIONS",
"SECTION_HEADER_EDIT_USER_GROUP" : "Edit Group",
"SECTION_HEADER_MEMBER_USERS" : "Member Users",
"SECTION_HEADER_MEMBER_USER_GROUPS" : "Member Groups",
"SECTION_HEADER_PERMISSIONS" : "@:MANAGE_USER.SECTION_HEADER_PERMISSIONS",
"SECTION_HEADER_USER_GROUPS" : "Parent Groups",
"TEXT_CONFIRM_DELETE" : "Groups cannot be restored after they have been deleted. Are you sure you want to delete this group?"
},
"PROTOCOL_RDP" : {
@@ -747,7 +792,26 @@
"TABLE_HEADER_USERNAME" : "Username"
},
"SETTINGS_USER_GROUPS" : {
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_NEW_USER_GROUP" : "New Group",
"DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR",
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
"FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE",
"HELP_USER_GROUPS" : "Click or tap on a group below to manage that group. Depending on your access level, groups can be added and deleted, and their member users and groups can be changed.",
"SECTION_HEADER_USER_GROUPS" : "Groups",
"TABLE_HEADER_USER_GROUP_NAME" : "Group Name"
},
"SETTINGS_SESSIONS" : {
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
@@ -793,6 +857,7 @@
"ACTION_MANAGE_SESSIONS" : "@:APP.ACTION_MANAGE_SESSIONS",
"ACTION_MANAGE_SETTINGS" : "@:APP.ACTION_MANAGE_SETTINGS",
"ACTION_MANAGE_USERS" : "@:APP.ACTION_MANAGE_USERS",
"ACTION_MANAGE_USER_GROUPS" : "@:APP.ACTION_MANAGE_USER_GROUPS",
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
"ACTION_VIEW_HISTORY" : "@:APP.ACTION_VIEW_HISTORY"