GUACAMOLE-220: Merge add user interface for managing user groups and membership.

This commit is contained in:
Nick Couchman
2018-08-14 19:33:02 -04:00
45 changed files with 2816 additions and 68 deletions

View File

@@ -308,7 +308,7 @@
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>lodash</artifactId>
<version>2.4.1</version>
<version>4.17.10</version>
<scope>runtime</scope>
</dependency>
@@ -316,7 +316,7 @@
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery</artifactId>
<version>2.1.3</version>
<version>3.3.1</version>
<scope>runtime</scope>
</dependency>

View File

@@ -486,13 +486,21 @@ Jettison (https://github.com/jettison-json/jettison)
jQuery (http://jquery.com/)
---------------------------
Version: 2.1.3
From: 'jQuery Foundation' (http://jquery.com/)
Version: 3.3.1
From: 'JS Foundation' (https://js.foundation/)
License(s):
MIT (bundled/jquery-2.1.3/MIT-LICENSE.txt)
MIT (bundled/jquery-3.3.1/LICENSE.txt)
Copyright 2014 jQuery Foundation and other contributors
http://jquery.com/
Copyright JS Foundation and other contributors, https://js.foundation/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -513,6 +521,13 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
JSR-250 Reference Implementation
(https://jcp.org/aboutJava/communityprocess/final/jsr250/index.html)
@@ -536,15 +551,25 @@ JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/)
Lodash (https://lodash.com/)
----------------------------
Version: 2.4.1
From: 'The Dojo Foundation' (http://dojofoundation.org/)
Version: 4.17.10
From: 'JS Foundation' (https://js.foundation/)
License(s):
MIT (bundled/lodash-2.4.1/LICENSE.txt)
MIT (bundled/lodash-4.17.10/LICENSE)
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
Copyright JS Foundation and other contributors <https://js.foundation/>
Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
@@ -564,6 +589,21 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.
Logback (http://logback.qos.ch/)
--------------------------------

View File

@@ -1,5 +1,13 @@
Copyright 2014 jQuery Foundation and other contributors
http://jquery.com/
Copyright JS Foundation and other contributors, https://js.foundation/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -19,3 +27,10 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.

View File

@@ -1,7 +1,17 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
Copyright JS Foundation and other contributors <https://js.foundation/>
Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
@@ -19,4 +29,19 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.

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

@@ -36,9 +36,11 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
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');
/**
@@ -133,6 +135,46 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
*/
$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),
* either through adding the current user as a member or removing the
* current user from that group. If this information has not yet been
* retrieved, this will be null.
*
* @type String[]
*/
$scope.availableGroups = null;
/**
* The identifiers of all user groups of which the user 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 the user 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 the user
* 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 = [];
/**
* For each applicable data source, the management-related actions that the
* current user may perform on the user account currently being created
@@ -166,6 +208,8 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
return $scope.users !== null
&& $scope.permissionFlags !== null
&& $scope.managementPermissions !== null
&& $scope.availableGroups !== null
&& $scope.parentGroups !== null
&& $scope.attributes !== null;
};
@@ -204,12 +248,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
var loadExistingUser = function loadExistingUser(dataSource, username) {
return $q.all({
users : dataSourceService.apply(userService.getUser, dataSources, username),
permissions : permissionService.getPermissions(dataSource, username)
permissions : permissionService.getPermissions(dataSource, username),
parentGroups : membershipService.getUserGroups(dataSource, username)
})
.then(function userDataRetrieved(values) {
$scope.users = values.users;
$scope.user = values.users[dataSource];
$scope.parentGroups = values.parentGroups;
// Create skeleton user if user does not exist
if (!$scope.user)
@@ -243,12 +289,15 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
var loadClonedUser = function loadClonedUser(dataSource, username) {
return $q.all({
users : dataSourceService.apply(userService.getUser, [dataSource], username),
permissions : permissionService.getPermissions(dataSource, username)
permissions : permissionService.getPermissions(dataSource, username),
parentGroups : membershipService.getUserGroups(dataSource, username)
})
.then(function userDataRetrieved(values) {
$scope.users = {};
$scope.user = values.users[dataSource];
$scope.parentGroups = values.parentGroups;
$scope.parentGroupsAdded = values.parentGroups;
// The current user will be associated with cloneSourceUsername in the
// retrieved permission set
@@ -274,6 +323,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
// Use skeleton user object with no associated permissions
$scope.user = new User();
$scope.parentGroups = [];
$scope.permissionFlags = new PermissionFlagSet();
// As no permissions are yet associated with the user, it is safe to
@@ -314,6 +364,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
$q.all({
userData : loadRequestedUser(),
permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
userGroups : userGroupService.getUserGroups($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
attributes : schemaService.getUserAttributes($scope.dataSource)
})
.then(function dataReceived(values) {
@@ -326,6 +377,12 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
// Determine whether data source contains this user
var exists = (dataSource in $scope.users);
// Add the identifiers of all modifiable user groups
$scope.availableGroups = [];
angular.forEach(values.userGroups, function addUserGroupIdentifier(userGroup) {
$scope.availableGroups.push(userGroup.identifier);
});
// Calculate management actions available for this specific account
$scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
values.permissions[dataSource],
@@ -415,9 +472,11 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
}
// Upon success, save any changed permissions
return permissionService.patchPermissions($scope.dataSource, $scope.user.username,
$scope.permissionsAdded, $scope.permissionsRemoved);
// Upon success, save any changed permissions/groups
return $q.all([
permissionService.patchPermissions($scope.dataSource, $scope.user.username, $scope.permissionsAdded, $scope.permissionsRemoved),
membershipService.patchUserGroups($scope.dataSource, $scope.user.username, $scope.parentGroupsAdded, $scope.parentGroupsRemoved)
]);
});

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

@@ -76,9 +76,12 @@ angular.module('manage').directive('dataSourceTabs', ['$injector',
$scope.$watch('permissions', function permissionsChanged(permissions) {
$scope.pages = [];
angular.forEach(permissions, function addDataSourcePage(managementPermissions, dataSource) {
var dataSources = _.keys($scope.permissions).sort();
angular.forEach(dataSources, function addDataSourcePage(dataSource) {
// Determine whether data source contains this object
var managementPermissions = permissions[dataSource];
var exists = !!managementPermissions.identifier;
// Data source is not relevant if the associated object does not

View File

@@ -0,0 +1,300 @@
/*
* 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 manipulating a set of objects sharing some common relation
* and represented by an array of their identifiers. The specific objects
* added or removed are tracked within a separate pair of arrays of
* identifiers.
*/
angular.module('manage').directive('identifierSetEditor', ['$injector',
function identifierSetEditor($injector) {
var directive = {
// Element only
restrict: 'E',
replace: true,
scope: {
/**
* The translation key of the text which should be displayed within
* the main header of the identifier set editor.
*
* @type String
*/
header : '@',
/**
* The translation key of the text which should be displayed if no
* identifiers are currently present within the set.
*
* @type String
*/
emptyPlaceholder : '@',
/**
* The translation key of the text which should be displayed if no
* identifiers are available to be added within the set.
*
* @type String
*/
unavailablePlaceholder : '@',
/**
* All identifiers which are available to be added to or removed
* from the identifier set being edited.
*
* @type String[]
*/
identifiersAvailable : '=',
/**
* The current state of the identifier set being manipulated. This
* array will be modified as changes are made through this
* identifier set editor.
*
* @type String[]
*/
identifiers : '=',
/**
* The set of identifiers that have been added, relative to the
* initial state of the identifier set being manipulated.
*
* @type String[]
*/
identifiersAdded : '=',
/**
* The set of identifiers that have been removed, relative to the
* initial state of the identifier set being manipulated.
*
* @type String[]
*/
identifiersRemoved : '='
},
templateUrl: 'app/manage/templates/identifierSetEditor.html'
};
directive.controller = ['$scope', function identifierSetEditorController($scope) {
/**
* Whether the full list of available identifiers should be displayed.
* Initially, only an abbreviated list of identifiers currently present
* is shown.
*
* @type Boolean
*/
$scope.expanded = false;
/**
* Map of identifiers to boolean flags indicating whether that
* identifier is currently present (true) or absent (false). If an
* identifier is absent, it may also be absent from this map.
*
* @type Object.<String, Boolean>
*/
$scope.identifierFlags = {};
/**
* Map of identifiers to boolean flags indicating whether that
* identifier is editable. If an identifier is not editable, it will be
* absent from this map.
*
* @type Object.<String, Boolean>
*/
$scope.isEditable = {};
/**
* Adds the given identifier to the given sorted array of identifiers,
* preserving the sorted order of the array. If the identifier is
* already present, no change is made to the array. The given array
* must already be sorted in ascending order.
*
* @param {String[]} arr
* The sorted array of identifiers to add the given identifier to.
*
* @param {String} identifier
* The identifier to add to the given array.
*/
var addIdentifier = function addIdentifier(arr, identifier) {
// Determine location that the identifier should be added to
// maintain sorted order
var index = _.sortedIndex(arr, identifier);
// Do not add if already present
if (arr[index] === identifier)
return;
// Insert identifier at determined location
arr.splice(index, 0, identifier);
};
/**
* Removes the given identifier from the given sorted array of
* identifiers, preserving the sorted order of the array. If the
* identifier is already absent, no change is made to the array. The
* given array must already be sorted in ascending order.
*
* @param {String[]} arr
* The sorted array of identifiers to remove the given identifier
* from.
*
* @param {String} identifier
* The identifier to remove from the given array.
*
* @returns {Boolean}
* true if the identifier was present in the given array and has
* been removed, false otherwise.
*/
var removeIdentifier = function removeIdentifier(arr, identifier) {
// Search for identifier in sorted array
var index = _.sortedIndexOf(arr, identifier);
// Nothing to do if already absent
if (index === -1)
return false;
// Remove identifier
arr.splice(index, 1);
return true;
};
// Keep identifierFlags up to date when identifiers array is replaced
// or initially assigned
$scope.$watch('identifiers', function identifiersChanged(identifiers) {
// Maintain identifiers in sorted order so additions and removals
// can be made more efficiently
if (identifiers)
identifiers.sort();
// Convert array of identifiers into set of boolean
// presence/absence flags
$scope.identifierFlags = {};
angular.forEach(identifiers, function storeIdentifierFlag(identifier) {
$scope.identifierFlags[identifier] = true;
});
});
// An identifier is editable iff it is available to be added or removed
// from the identifier set being edited (iff it is within the
// identifiersAvailable array)
$scope.$watch('identifiersAvailable', function availableIdentifiersChanged(identifiers) {
$scope.isEditable = {};
angular.forEach(identifiers, function storeEditableIdentifier(identifier) {
$scope.isEditable[identifier] = true;
});
});
/**
* Notifies the controller that a change has been made to the flag
* denoting presence/absence of a particular identifier within the
* <code>identifierFlags</code> map. The <code>identifiers</code>,
* <code>identifiersAdded</code>, and <code>identifiersRemoved</code>
* arrays are updated accordingly.
*
* @param {String} identifier
* The identifier which has been added or removed through modifying
* its boolean flag within <code>identifierFlags</code>.
*/
$scope.identifierChanged = function identifierChanged(identifier) {
// Determine status of modified identifier
var present = !!$scope.identifierFlags[identifier];
// Add/remove identifier from added/removed sets depending on
// change in flag state
if (present) {
addIdentifier($scope.identifiers, identifier);
if (!removeIdentifier($scope.identifiersRemoved, identifier))
addIdentifier($scope.identifiersAdded, identifier);
}
else {
removeIdentifier($scope.identifiers, identifier);
if (!removeIdentifier($scope.identifiersAdded, identifier))
addIdentifier($scope.identifiersRemoved, identifier);
}
};
/**
* Removes the given identifier, updating <code>identifierFlags</code>,
* <code>identifiers</code>, <code>identifiersAdded</code>, and
* <code>identifiersRemoved</code> accordingly.
*
* @param {String} identifier
* The identifier to remove.
*/
$scope.removeIdentifier = function removeIdentifier(identifier) {
$scope.identifierFlags[identifier] = false;
$scope.identifierChanged(identifier);
};
/**
* Shows the full list of available identifiers. If the full list is
* already shown, this function has no effect.
*/
$scope.expand = function expand() {
$scope.expanded = true;
};
/**
* Hides the full list of available identifiers. If the full list is
* already hidden, this function has no effect.
*/
$scope.collapse = function collapse() {
$scope.expanded = false;
};
/**
* Returns whether there are absolutely no identifiers that can be
* managed using this editor. If true, the editor is effectively
* useless, as there is nothing whatsoever to display.
*
* @returns {Boolean}
* true if there are no identifiers that can be managed using this
* editor, false otherwise.
*/
$scope.isEmpty = function isEmpty() {
return _.isEmpty($scope.identifiers)
&& _.isEmpty($scope.identifiersAvailable);
};
}];
return directive;
}]);

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,82 @@
/*
* 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.
*/
.related-objects .abbreviated-related-objects {
display: table;
margin: 1em 0;
}
.related-objects .abbreviated-related-objects ul {
display: table-cell;
vertical-align: top;
}
.related-objects .abbreviated-related-objects ul,
.related-objects .all-related-objects ul {
padding: 0;
list-style: none;
}
.related-objects .abbreviated-related-objects ul li {
display: inline-block;
margin: 0.25em;
padding: 0.25em;
border: 1px solid silver;
background: #F5F5F5;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
}
.related-objects .abbreviated-related-objects ul li img.remove {
max-height: 0.75em;
max-width: 0.75em;
margin: 0 0.25em;
}
.related-objects .abbreviated-related-objects ul li .identifier {
margin: 0 0.25em;
}
.related-objects .abbreviated-related-objects img.expand,
.related-objects .abbreviated-related-objects img.collapse {
display: table-cell;
max-height: 1.5em;
max-width: 1.5em;
margin: 0.375em 0;
}
.related-objects .all-related-objects {
border-top: 1px solid silver;
}
.related-objects .abbreviated-related-objects p.no-related-objects,
.related-objects .all-related-objects p.no-objects-available {
font-style: italic;
opacity: 0.5;
}
.related-objects .abbreviated-related-objects p.no-related-objects {
display: table-cell;
vertical-align: middle;
}

View File

@@ -0,0 +1,47 @@
<div class="related-objects" ng-hide="isEmpty()">
<div class="header">
<h2>{{ header | translate }}</h2>
<div class="filter">
<input class="search-string" type="text"
placeholder="{{ 'SETTINGS_USERS.FIELD_PLACEHOLDER_FILTER' | translate }}"
ng-model="filterString"/>
</div>
</div>
<div class="section">
<!-- Abbreviated list of only the currently selected objects -->
<div class="abbreviated-related-objects">
<img src="images/arrows/right.png" alt="Expand" class="expand" ng-hide="expanded" ng-click="expand()"/>
<img src="images/arrows/down.png" alt="Collapse" class="collapse" ng-show="expanded" ng-click="collapse()"/>
<p ng-hide="identifiers.length" class="no-related-objects">{{ emptyPlaceholder | translate }}</p>
<ul>
<li ng-repeat="identifier in identifiers | filter: filterString">
<label><img src="images/x-red.png" alt="Remove" class="remove"
ng-click="removeIdentifier(identifier)"
ng-show="isEditable[identifier]"/><span class="identifier">{{ identifier }}</span>
</label>
</li>
</ul>
</div>
<!-- Exhaustive, paginated list of all objects -->
<div class="all-related-objects" ng-show="expanded">
<p ng-hide="identifiersAvailablePage.length" class="no-objects-available">{{ unavailablePlaceholder | translate }}</p>
<ul>
<li ng-repeat="identifier in identifiersAvailablePage">
<label><input type="checkbox"
ng-model="identifierFlags[identifier]"
ng-change="identifierChanged(identifier)"/>
<span class="identifier">{{ identifier }}</span>
</label>
</li>
</ul>
<!-- Pager controls for user list -->
<guac-pager page="identifiersAvailablePage" page-size="25"
items="identifiersAvailable | orderBy | filter: filterString"></guac-pager>
</div>
</div>
</div>

View File

@@ -56,6 +56,17 @@
permissions-removed="permissionsRemoved">
</system-permission-editor>
<!-- Parent group section -->
<identifier-set-editor
header="MANAGE_USER.SECTION_HEADER_USER_GROUPS"
empty-placeholder="MANAGE_USER.HELP_NO_USER_GROUPS"
unavailable-placeholder="MANAGE_USER.INFO_NO_USER_GROUPS_AVAILABLE"
identifiers-available="availableGroups"
identifiers="parentGroups"
identifiers-added="parentGroupsAdded"
identifiers-removed="parentGroupsRemoved">
</identifier-set-editor>
<!-- Connection permissions section -->
<connection-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions"
data-data-source="dataSource"

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

@@ -60,7 +60,8 @@ angular.module('rest').factory('cacheService', ['$injector',
service.schema = $cacheFactory('API-SCHEMA');
/**
* Shared cache used by both userService and permissionService.
* Shared cache used by userService, userGroupService, permissionService,
* and membershipService.
*
* @type $cacheFactory.Cache
*/

View File

@@ -0,0 +1,385 @@
/*
* 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.
*/
/**
* Service for operating on user group memberships via the REST API.
*/
angular.module('rest').factory('membershipService', ['$injector',
function membershipService($injector) {
// Required services
var requestService = $injector.get('requestService');
var authenticationService = $injector.get('authenticationService');
var cacheService = $injector.get('cacheService');
// Required types
var RelatedObjectPatch = $injector.get('RelatedObjectPatch');
var service = {};
/**
* Creates a new array of patches which represents the given changes to an
* arbitrary set of objects sharing some common relation.
*
* @param {String[]} [identifiersToAdd]
* The identifiers of all objects which should be added to the
* relation, if any.
*
* @param {String[]} [identifiersToRemove]
* The identifiers of all objects which should be removed from the
* relation, if any.
*
* @returns {RelatedObjectPatch[]}
* A new array of patches which represents the given changes.
*/
var getRelatedObjectPatch = function getRelatedObjectPatch(identifiersToAdd, identifiersToRemove) {
var patch = [];
angular.forEach(identifiersToAdd, function addIdentifier(identifier) {
patch.push(new RelatedObjectPatch({
op : RelatedObjectPatch.Operation.ADD,
value : identifier
}));
});
angular.forEach(identifiersToRemove, function removeIdentifier(identifier) {
patch.push(new RelatedObjectPatch({
op : RelatedObjectPatch.Operation.REMOVE,
value : identifier
}));
});
return patch;
};
/**
* Returns the URL for the REST resource most appropriate for accessing
* the parent user groups of the user or group having the given identifier.
*
* It is important to note that a particular data source can authenticate
* and provide user groups for a user, even if that user does not exist
* within that data source (and thus cannot be found beneath
* "api/session/data/{dataSource}/users")
*
* @param {String} dataSource
* The unique identifier of the data source containing the user or
* group whose parent user groups should be retrieved. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @param {String} identifier
* The identifier of the user or group for which the URL of the proper
* REST resource should be derived.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {String}
* The URL for the REST resource representing the parent user groups of
* the user or group having the given identifier.
*/
var getUserGroupsResourceURL = function getUserGroupsResourceURL(dataSource, identifier, group) {
// Create base URL for data source
var base = 'api/session/data/' + encodeURIComponent(dataSource);
// Access parent groups directly (there is no "self" for user groups
// as there is for users)
if (group)
return base + '/userGroups/' + encodeURIComponent(identifier) + '/userGroups';
// If the username is that of the current user, do not rely on the
// user actually existing (they may not). Access their parent groups via
// "self" rather than the collection of defined users.
if (identifier === authenticationService.getCurrentUsername())
return base + '/self/userGroups';
// Otherwise, the user must exist for their parent groups to be
// accessible. Use the collection of defined users.
return base + '/users/' + encodeURIComponent(identifier) + '/userGroups';
};
/**
* Makes a request to the REST API to retrieve the identifiers of all
* parent user groups of which a given user or group is a member, returning
* a promise that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user or
* group whose parent user groups should be retrieved. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @param {String} identifier
* The identifier of the user or group to retrieve the parent user
* groups of.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {Promise.<String[]>}
* A promise for the HTTP call which will resolve with an array
* containing the requested identifiers upon success.
*/
service.getUserGroups = function getUserGroups(dataSource, identifier, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve parent groups
return requestService({
cache : cacheService.users,
method : 'GET',
url : getUserGroupsResourceURL(dataSource, identifier, group),
params : httpParameters
});
};
/**
* Makes a request to the REST API to modify the parent user groups of
* which a given user or group is a member, returning a promise that can be
* used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user or
* group whose parent user groups should be modified. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @param {String} identifier
* The identifier of the user or group to modify the parent user
* groups of.
*
* @param {String[]} [addToUserGroups]
* The identifier of all parent user groups to which the given user or
* group should be added as a member, if any.
*
* @param {String[]} [removeFromUserGroups]
* The identifier of all parent user groups from which the given member
* user or group should be removed, if any.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchUserGroups = function patchUserGroups(dataSource, identifier,
addToUserGroups, removeFromUserGroups, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update parent user groups
return requestService({
method : 'PATCH',
url : getUserGroupsResourceURL(dataSource, identifier, group),
params : httpParameters,
data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups)
})
// Clear the cache
.then(function parentUserGroupsChanged(){
cacheService.users.removeAll();
});
};
/**
* Makes a request to the REST API to retrieve the identifiers of all
* users which are members of the given user group, returning a promise
* that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group
* whose member users should be retrieved. This identifier corresponds
* to an AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user group to retrieve the member users of.
*
* @returns {Promise.<String[]>}
* A promise for the HTTP call which will resolve with an array
* containing the requested identifiers upon success.
*/
service.getMemberUsers = function getMemberUsers(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve member users
return requestService({
cache : cacheService.users,
method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers',
params : httpParameters
});
};
/**
* Makes a request to the REST API to modify the member users of a given
* user group, returning a promise that can be used for processing the
* results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group
* whose member users should be modified. This identifier corresponds
* to an AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user group to modify the member users of.
*
* @param {String[]} [usersToAdd]
* The identifier of all users to add as members of the given user
* group, if any.
*
* @param {String[]} [usersToRemove]
* The identifier of all users to remove from the given user group,
* if any.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchMemberUsers = function patchMemberUsers(dataSource, identifier,
usersToAdd, usersToRemove) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update member users
return requestService({
method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers',
params : httpParameters,
data : getRelatedObjectPatch(usersToAdd, usersToRemove)
})
// Clear the cache
.then(function memberUsersChanged(){
cacheService.users.removeAll();
});
};
/**
* Makes a request to the REST API to retrieve the identifiers of all
* user groups which are members of the given user group, returning a
* promise that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group
* whose member user groups should be retrieved. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @param {String} identifier
* The identifier of the user group to retrieve the member user
* groups of.
*
* @returns {Promise.<String[]>}
* A promise for the HTTP call which will resolve with an array
* containing the requested identifiers upon success.
*/
service.getMemberUserGroups = function getMemberUserGroups(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve member user groups
return requestService({
cache : cacheService.users,
method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups',
params : httpParameters
});
};
/**
* Makes a request to the REST API to modify the member user groups of a
* given user group, returning a promise that can be used for processing
* the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group
* whose member user groups should be modified. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @param {String} identifier
* The identifier of the user group to modify the member user groups of.
*
* @param {String[]} [userGroupsToAdd]
* The identifier of all user groups to add as members of the given
* user group, if any.
*
* @param {String[]} [userGroupsToRemove]
* The identifier of all member user groups to remove from the given
* user group, if any.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchMemberUserGroups = function patchMemberUserGroups(dataSource,
identifier, userGroupsToAdd, userGroupsToRemove) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update member user groups
return requestService({
method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups',
params : httpParameters,
data : getRelatedObjectPatch(userGroupsToAdd, userGroupsToRemove)
})
// Clear the cache
.then(function memberUserGroupsChanged(){
cacheService.users.removeAll();
});
};
return service;
}]);

View File

@@ -45,6 +45,11 @@ angular.module('rest').factory('permissionService', ['$injector',
* within that data source (and thus cannot be found beneath
* "api/session/data/{dataSource}/users")
*
* NOTE: Unlike getPermissionsResourceURL(),
* getEffectivePermissionsResourceURL() CANNOT be applied to user groups.
* Only users have retrievable effective permissions as far as the REST API
* is concerned.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user whose
* permissions should be retrieved. This identifier corresponds to an
@@ -82,6 +87,10 @@ angular.module('rest').factory('permissionService', ['$injector',
* from the permissions returned via getPermissions() in that permissions
* which are not directly granted to the user are included.
*
* NOTE: Unlike getPermissions(), getEffectivePermissions() CANNOT be
* applied to user groups. Only users have retrievable effective
* permissions as far as the REST API is concerned.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user whose
* permissions should be retrieved. This identifier corresponds to an
@@ -113,10 +122,10 @@ angular.module('rest').factory('permissionService', ['$injector',
/**
* Returns the URL for the REST resource most appropriate for accessing
* the permissions of the user having the given identifier. The permissions
* retrieved differ from effective permissions (those returned by
* getEffectivePermissions()) in that only permissions which are directly
* granted to the user are included.
* the permissions of the user or group having the given identifier. The
* permissions retrieved differ from effective permissions (those returned
* by getEffectivePermissions()) in that only permissions which are directly
* granted to the user or group are included.
*
* It is important to note that a particular data source can authenticate
* and provide permissions for a user, even if that user does not exist
@@ -129,18 +138,27 @@ angular.module('rest').factory('permissionService', ['$injector',
* AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user for which the URL of the proper REST
* resource should be derived.
* The identifier of the user or group for which the URL of the proper
* REST resource should be derived.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {String}
* The URL for the REST resource representing the user having the given
* identifier.
* The URL for the REST resource representing the user or group having
* the given identifier.
*/
var getPermissionsResourceURL = function getPermissionsResourceURL(dataSource, identifier) {
var getPermissionsResourceURL = function getPermissionsResourceURL(dataSource, identifier, group) {
// Create base URL for data source
var base = 'api/session/data/' + encodeURIComponent(dataSource);
// Access group permissions directly (there is no "self" for user groups
// as there is for users)
if (group)
return base + '/userGroups/' + encodeURIComponent(identifier) + '/permissions';
// If the username is that of the current user, do not rely on the
// user actually existing (they may not). Access their permissions via
// "self" rather than the collection of defined users.
@@ -155,36 +173,41 @@ angular.module('rest').factory('permissionService', ['$injector',
/**
* Makes a request to the REST API to get the list of permissions for a
* given user, returning a promise that provides an array of
* given user or user group, returning a promise that provides an array of
* @link{Permission} objects if successful. The permissions retrieved
* differ from effective permissions (those returned by
* getEffectivePermissions()) in that only permissions which are directly
* granted to the user included.
* getEffectivePermissions()) in that both users and groups may be queried,
* and only permissions which are directly granted to the user or group are
* included.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user whose
* permissions should be retrieved. This identifier corresponds to an
* AuthenticationProvider within the Guacamole web application.
* The unique identifier of the data source containing the user or group
* whose permissions should be retrieved. This identifier corresponds to
* an AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user to retrieve the permissions for.
* The identifier of the user or group to retrieve the permissions for.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {Promise.<PermissionSet>}
* A promise which will resolve with a @link{PermissionSet} upon
* success.
*/
service.getPermissions = function getPermissions(dataSource, identifier) {
service.getPermissions = function getPermissions(dataSource, identifier, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user permissions
// Retrieve user/group permissions
return requestService({
cache : cacheService.users,
method : 'GET',
url : getPermissionsResourceURL(dataSource, identifier),
url : getPermissionsResourceURL(dataSource, identifier, group),
params : httpParameters
});
@@ -261,6 +284,10 @@ angular.module('rest').factory('permissionService', ['$injector',
addObjectPatchOperations(patch, operation, "/userPermissions",
permissions.userPermissions);
// Add user group permission operations to patch
addObjectPatchOperations(patch, operation, "/userGroupPermissions",
permissions.userGroupPermissions);
// Add system operations to patch
permissions.systemPermissions.forEach(function addSystemPatch(type) {
patch.push({
@@ -274,18 +301,18 @@ angular.module('rest').factory('permissionService', ['$injector',
/**
* Makes a request to the REST API to modify the permissions for a given
* user, returning a promise that can be used for processing the results of
* the call. This request affects only the permissions directly granted to
* the user, and may not affect permissions inherited through other means
* (effective permissions).
* user or group, returning a promise that can be used for processing the
* results of the call. This request affects only the permissions directly
* granted to the user or group, and may not affect permissions inherited
* through other means (effective permissions).
*
* @param {String} dataSource
* The unique identifier of the data source containing the user whose
* permissions should be modified. This identifier corresponds to an
* AuthenticationProvider within the Guacamole web application.
* The unique identifier of the data source containing the user or group
* whose permissions should be modified. This identifier corresponds to
* an AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user to modify the permissions of.
* The identifier of the user or group to modify the permissions of.
*
* @param {PermissionSet} [permissionsToAdd]
* The set of permissions to add, if any.
@@ -293,12 +320,16 @@ angular.module('rest').factory('permissionService', ['$injector',
* @param {PermissionSet} [permissionsToRemove]
* The set of permissions to remove, if any.
*
* @param {Boolean} [group]
* Whether the provided identifier refers to a user group. If false or
* omitted, the identifier given is assumed to refer to a user.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchPermissions = function patchPermissions(dataSource, identifier,
permissionsToAdd, permissionsToRemove) {
permissionsToAdd, permissionsToRemove, group) {
var permissionPatch = [];
@@ -313,10 +344,10 @@ angular.module('rest').factory('permissionService', ['$injector',
// Add all the remove operations to the patch
addPatchOperations(permissionPatch, PermissionPatch.Operation.REMOVE, permissionsToRemove);
// Patch user permissions
// Patch user/group permissions
return requestService({
method : 'PATCH',
url : getPermissionsResourceURL(dataSource, identifier),
url : getPermissionsResourceURL(dataSource, identifier, group),
params : httpParameters,
data : permissionPatch
})

View File

@@ -64,6 +64,40 @@ angular.module('rest').factory('schemaService', ['$injector',
};
/**
* Makes a request to the REST API to get the list of available attributes
* for user group objects, returning a promise that provides an array of
* @link{Form} objects if successful. Each element of the array describes
* a logical grouping of possible attributes.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user groups
* whose available attributes are to be retrieved. This identifier
* corresponds to an AuthenticationProvider within the Guacamole web
* application.
*
* @returns {Promise.<Form[]>}
* A promise which will resolve with an array of @link{Form}
* objects, where each @link{Form} describes a logical grouping of
* possible attributes.
*/
service.getUserGroupAttributes = function getUserGroupAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available user group attributes
return requestService({
cache : cacheService.schema,
method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userGroupAttributes',
params : httpParameters
});
};
/**
* Makes a request to the REST API to get the list of available attributes
* for connection objects, returning a promise that provides an array of

View File

@@ -0,0 +1,223 @@
/*
* 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.
*/
/**
* Service for operating on user groups via the REST API.
*/
angular.module('rest').factory('userGroupService', ['$injector',
function userGroupService($injector) {
// Required services
var requestService = $injector.get('requestService');
var authenticationService = $injector.get('authenticationService');
var cacheService = $injector.get('cacheService');
var service = {};
/**
* Makes a request to the REST API to get the list of user groups,
* returning a promise that provides an array of @link{UserGroup} objects if
* successful.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user groups
* to be retrieved. This identifier corresponds to an
* AuthenticationProvider within the Guacamole web application.
*
* @param {String[]} [permissionTypes]
* The set of permissions to filter with. A user group must have one or
* more of these permissions for a user group to appear in the result.
* If null, no filtering will be performed. Valid values are listed
* within PermissionSet.ObjectType.
*
* @returns {Promise.<Object.<String, UserGroup>>}
* A promise which will resolve with a map of @link{UserGroup} objects
* where each key is the identifier of the corresponding user group.
*/
service.getUserGroups = function getUserGroups(dataSource, permissionTypes) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add permission filter if specified
if (permissionTypes)
httpParameters.permission = permissionTypes;
// Retrieve user groups
return requestService({
cache : cacheService.users,
method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups',
params : httpParameters
});
};
/**
* Makes a request to the REST API to get the user group having the given
* identifier, returning a promise that provides the corresponding
* @link{UserGroup} if successful.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group to
* be retrieved. This identifier corresponds to an
* AuthenticationProvider within the Guacamole web application.
*
* @param {String} identifier
* The identifier of the user group to retrieve.
*
* @returns {Promise.<UserGroup>}
* A promise which will resolve with a @link{UserGroup} upon success.
*/
service.getUserGroup = function getUserGroup(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user group
return requestService({
cache : cacheService.users,
method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier),
params : httpParameters
});
};
/**
* Makes a request to the REST API to delete a user group, returning a
* promise that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group to
* be deleted. This identifier corresponds to an AuthenticationProvider
* within the Guacamole web application.
*
* @param {UserGroup} userGroup
* The user group to delete.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* delete operation is successful.
*/
service.deleteUserGroup = function deleteUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete user group
return requestService({
method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier),
params : httpParameters
})
// Clear the cache
.then(function userGroupDeleted(){
cacheService.users.removeAll();
});
};
/**
* Makes a request to the REST API to create a user group, returning a promise
* that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source in which the user group
* should be created. This identifier corresponds to an
* AuthenticationProvider within the Guacamole web application.
*
* @param {UserGroup} userGroup
* The user group to create.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* create operation is successful.
*/
service.createUserGroup = function createUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Create user group
return requestService({
method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups',
params : httpParameters,
data : userGroup
})
// Clear the cache
.then(function userGroupCreated(){
cacheService.users.removeAll();
});
};
/**
* Makes a request to the REST API to save a user group, returning a
* promise that can be used for processing the results of the call.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user group to
* be updated. This identifier corresponds to an AuthenticationProvider
* within the Guacamole web application.
*
* @param {UserGroup} userGroup
* The user group to update.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* save operation is successful.
*/
service.saveUserGroup = function saveUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update user group
return requestService({
method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier),
params : httpParameters,
data : userGroup
})
// Clear the cache
.then(function userGroupUpdated(){
cacheService.users.removeAll();
});
};
return service;
}]);

View File

@@ -84,7 +84,7 @@ angular.module('rest').factory('Connection', [function defineConnection() {
*
* @type Object.<String, String>
*/
this.attributes = {};
this.attributes = template.attributes || {};
/**
* The count of currently active connections using this connection.

View File

@@ -95,7 +95,7 @@ angular.module('rest').factory('ConnectionGroup', [function defineConnectionGrou
*
* @type Object.<String, String>
*/
this.attributes = {};
this.attributes = template.attributes || {};
/**
* The count of currently active connections using this connection

View File

@@ -133,7 +133,7 @@ angular.module('rest').factory('PermissionFlagSet', ['PermissionSet',
* true. Valid permission type strings are defined within
* PermissionSet.ObjectPermissionType. Permissions which are not
* granted may be set to false, but this is not required.
*
*
* @type Object.<String, Object.<String, Boolean>>
*/
this.userPermissions = template.userPermissions || {
@@ -143,6 +143,24 @@ angular.module('rest').factory('PermissionFlagSet', ['PermissionSet',
'ADMINISTER' : {}
};
/**
* The granted state of each permission for each user group, as a map of
* object permission type string to permission map. The permission map
* is, in turn, a map of group identifier to boolean value. A particular
* permission is granted if its corresponding boolean value is set to
* true. Valid permission type strings are defined within
* PermissionSet.ObjectPermissionType. Permissions which are not
* granted may be set to false, but this is not required.
*
* @type Object.<String, Object.<String, Boolean>>
*/
this.userGroupPermissions = template.userGroupPermissions || {
'READ' : {},
'UPDATE' : {},
'DELETE' : {},
'ADMINISTER' : {}
};
};
/**
@@ -216,6 +234,9 @@ angular.module('rest').factory('PermissionFlagSet', ['PermissionSet',
// Add all granted user permissions
addObjectPermissions(permissionSet.userPermissions, permissionFlagSet.userPermissions);
// Add all granted user group permissions
addObjectPermissions(permissionSet.userGroupPermissions, permissionFlagSet.userGroupPermissions);
return permissionFlagSet;
};

View File

@@ -81,6 +81,15 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet()
*/
this.userPermissions = template.userPermissions || {};
/**
* Map of user group identifiers to the corresponding array of granted
* permissions. Each permission is represented by a string listed
* within PermissionSet.ObjectPermissionType.
*
* @type Object.<String, String[]>
*/
this.userGroupPermissions = template.userGroupPermissions || {};
/**
* Array of granted system permissions. Each permission is represented
* by a string listed within PermissionSet.SystemPermissionType.
@@ -306,7 +315,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet()
};
/**
* Returns whether the given permission is granted for the user having the
* Returns whether the given permission is granted for the user having the
* given ID.
*
* @param {PermissionSet|Object} permSet
@@ -315,7 +324,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet()
* @param {String} type
* The permission to search for, as defined by
* PermissionSet.ObjectPermissionType.
*
*
* @param {String} identifier
* The identifier of the user to which the permission applies.
*
@@ -326,6 +335,27 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet()
return hasPermission(permSet.userPermissions, type, identifier);
};
/**
* Returns whether the given permission is granted for the user group having
* the given identifier.
*
* @param {PermissionSet|Object} permSet
* The permission set to check.
*
* @param {String} type
* The permission to search for, as defined by
* PermissionSet.ObjectPermissionType.
*
* @param {String} identifier
* The identifier of the user group to which the permission applies.
*
* @returns {Boolean}
* true if the permission is present (granted), false otherwise.
*/
PermissionSet.hasUserGroupPermission = function hasUserGroupPermission(permSet, type, identifier) {
return hasPermission(permSet.userGroupPermissions, type, identifier);
};
/**
* Returns whether the given permission is granted at the system level.
*
@@ -733,6 +763,54 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet()
return removeObjectPermission(permSet.userPermissions, type, identifier);
};
/**
* Adds the given user group permission applying to the user group with the
* given identifier to the given permission set, if not already present. If
* the permission is already present, this function has no effect.
*
* @param {PermissionSet} permSet
* The permission set to modify.
*
* @param {String} type
* The permission to add, as defined by
* PermissionSet.ObjectPermissionType.
*
* @param {String} identifier
* The identifier of the user group to which the permission applies.
*
* @returns {Boolean}
* true if the permission was added, false if the permission was
* already present in the given permission set.
*/
PermissionSet.addUserGroupPermission = function addUserGroupPermission(permSet, type, identifier) {
permSet.userGroupPermissions = permSet.userGroupPermissions || {};
return addObjectPermission(permSet.userGroupPermissions, type, identifier);
};
/**
* Removes the given user group permission applying to the user group with
* the given identifier from the given permission set, if present. If the
* permission is not present, this function has no effect.
*
* @param {PermissionSet} permSet
* The permission set to modify.
*
* @param {String} type
* The permission to remove, as defined by
* PermissionSet.ObjectPermissionType.
*
* @param {String} identifier
* The identifier of the user group to whom the permission applies.
*
* @returns {Boolean}
* true if the permission was removed, false if the permission was not
* present in the given permission set.
*/
PermissionSet.removeUserGroupPermission = function removeUserGroupPermission(permSet, type, identifier) {
permSet.userGroupPermissions = permSet.userGroupPermissions || {};
return removeObjectPermission(permSet.userGroupPermissions, type, identifier);
};
return PermissionSet;
}]);

View File

@@ -0,0 +1,85 @@
/*
* 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.
*/
/**
* Service which defines the RelatedObjectPatch class.
*/
angular.module('rest').factory('RelatedObjectPatch', [function defineRelatedObjectPatch() {
/**
* The object returned by REST API calls when representing changes to an
* arbitrary set of objects which share some common relation.
*
* @constructor
* @param {RelatedObjectPatch|Object} [template={}]
* The object whose properties should be copied within the new
* RelatedObjectPatch.
*/
var RelatedObjectPatch = function RelatedObjectPatch(template) {
// Use empty object by default
template = template || {};
/**
* The operation to apply to the objects indicated by the path. Valid
* operation values are defined within RelatedObjectPatch.Operation.
*
* @type String
*/
this.op = template.op;
/**
* The path of the objects to modify. This will always be "/".
*
* @type String
* @default '/'
*/
this.path = template.path || '/';
/**
* The identifier of the object being added or removed from the
* relation.
*
* @type String
*/
this.value = template.value;
};
/**
* All valid patch operations for objects sharing some common relation.
* Currently, only add and remove are supported.
*/
RelatedObjectPatch.Operation = {
/**
* Adds the specified object to the relation.
*/
ADD : "add",
/**
* Removes the specified object from the relation.
*/
REMOVE : "remove"
};
return RelatedObjectPatch;
}]);

View File

@@ -76,7 +76,7 @@ angular.module('rest').factory('SharingProfile', [function defineSharingProfile(
*
* @type Object.<String, String>
*/
this.attributes = {};
this.attributes = template.attributes || {};
};

View File

@@ -69,7 +69,7 @@ angular.module('rest').factory('User', [function defineUser() {
*
* @type Object.<String, String>
*/
this.attributes = {};
this.attributes = template.attributes || {};
};

View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
/**
* Service which defines the UserGroup class.
*/
angular.module('rest').factory('UserGroup', [function defineUserGroup() {
/**
* The object returned by REST API calls when representing the data
* associated with a user group.
*
* @constructor
* @param {UserGroup|Object} [template={}]
* The object whose properties should be copied within the new
* UserGroup.
*/
var UserGroup = function UserGroup(template) {
// Use empty object by default
template = template || {};
/**
* The name which uniquely identifies this user group.
*
* @type String
*/
this.identifier = template.identifier;
/**
* Arbitrary name/value pairs which further describe this user group.
* The semantics and validity of these attributes are dictated by the
* extension which defines them.
*
* @type Object.<String, String>
*/
this.attributes = template.attributes || {};
};
return UserGroup;
}]);

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,272 @@
/*
* 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
var dataSources = _.keys(permissions).sort();
for (var i = 0; i < dataSources.length; i++) {
// Retrieve corresponding permission set
var dataSource = dataSources[i];
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

@@ -150,9 +150,11 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
return null;
// For each data source
for (var dataSource in $scope.permissions) {
var dataSources = _.keys($scope.permissions).sort();
for (var i = 0; i < dataSources.length; i++) {
// Retrieve corresponding permission set
var dataSource = dataSources[i];
var permissionSet = $scope.permissions[dataSource];
// Can create users if adminstrator or have explicit permission

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: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

View File

@@ -57,8 +57,8 @@
<script type="text/javascript" src="relocateParameters.js"></script>
<!-- Utility libraries -->
<script type="text/javascript" src="webjars/jquery/2.1.3/dist/jquery.min.js"></script>
<script type="text/javascript" src="webjars/lodash/2.4.1/dist/lodash.min.js"></script>
<script type="text/javascript" src="webjars/jquery/3.3.1/dist/jquery.min.js"></script>
<script type="text/javascript" src="webjars/lodash/4.17.10/dist/lodash.min.js"></script>
<!-- AngularJS -->
<script type="text/javascript" src="webjars/angular/1.6.9/angular.min.js"></script>

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:",
@@ -301,17 +303,64 @@
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
"INFO_READ_ONLY" : "Sorry, but this user account cannot be edited.",
"HELP_NO_USER_GROUPS" : "This user does not currently belong to any groups. Expand this section to add groups.",
"INFO_READ_ONLY" : "Sorry, but this user account cannot be edited.",
"INFO_NO_USER_GROUPS_AVAILABLE" : "No groups available.",
"SECTION_HEADER_ALL_CONNECTIONS" : "All Connections",
"SECTION_HEADER_CONNECTIONS" : "Connections",
"SECTION_HEADER_CURRENT_CONNECTIONS" : "Current Connections",
"SECTION_HEADER_EDIT_USER" : "Edit User",
"SECTION_HEADER_PERMISSIONS" : "Permissions",
"SECTION_HEADER_USER_GROUPS" : "Groups",
"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" : {
@@ -743,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",
@@ -789,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"