PROTOCOL_NAME
- *
- * where NAME is the protocol name transformed via
- * translationStringService.canonicalize().
- *
- * @param {String} protocolName
- * The name of the protocol.
- *
- * @returns {String}
- * The translation namespace for the protocol specified, or null if no
- * namespace could be generated.
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
*/
- $scope.getNamespace = function getNamespace(protocolName) {
+ var loadRequestedConnection = function loadRequestedConnection() {
- // Do not generate a namespace if no protocol is selected
- if (!protocolName)
- return null;
+ // If we are editing an existing connection, pull its data
+ if (identifier)
+ return loadExistingConnection($scope.selectedDataSource, identifier);
- return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
+ // If we are cloning an existing connection, pull its data instead
+ if (cloneSourceIdentifier)
+ return loadClonedConnection($scope.selectedDataSource, cloneSourceIdentifier);
+
+ // If we are creating a new connection, populate skeleton connection data
+ return loadSkeletonConnection();
};
- /**
- * Given the internal name of a protocol, produces the translation string
- * for the localized version of that protocol's name. The translation
- * string will be of the form:
- *
- * NAMESPACE.NAME
- *
- * where NAMESPACE is the namespace generated from
- * $scope.getNamespace().
- *
- * @param {String} protocolName
- * The name of the protocol.
- *
- * @returns {String}
- * The translation string which produces the localized name of the
- * protocol specified.
- */
- $scope.getProtocolName = function getProtocolName(protocolName) {
- return $scope.getNamespace(protocolName) + '.NAME';
- };
+ // Populate interface with requested data
+ $q.all({
+ connectionData : loadRequestedConnection(),
+ attributes : schemaService.getConnectionAttributes($scope.selectedDataSource),
+ permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()),
+ protocols : schemaService.getProtocols($scope.selectedDataSource),
+ rootGroup : connectionGroupService.getConnectionGroupTree($scope.selectedDataSource, ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
+ })
+ .then(function dataRetrieved(values) {
+
+ $scope.attributes = values.attributes;
+ $scope.protocols = values.protocols;
+ $scope.rootGroup = values.rootGroup;
+
+ $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+ values.permissions,
+ PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+ PermissionSet.hasConnectionPermission,
+ identifier);
+
+ }, requestService.WARN);
+
+ // Get history date format
+ $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) {
+ $scope.historyDateFormat = historyDateFormat;
+ }, angular.noop);
/**
- * Cancels all pending edits, returning to the management page.
+ * @borrows Protocol.getNamespace
*/
- $scope.cancel = function cancel() {
+ $scope.getNamespace = Protocol.getNamespace;
+
+ /**
+ * @borrows Protocol.getName
+ */
+ $scope.getProtocolName = Protocol.getName;
+
+ /**
+ * Cancels all pending edits, returning to the main list of connections
+ * within the selected data source.
+ */
+ $scope.returnToConnectionList = function returnToConnectionList() {
$location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
};
@@ -368,76 +321,33 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
};
/**
- * Saves the connection, creating a new connection or updating the existing
- * connection.
+ * Saves the current connection, creating a new connection or updating the
+ * existing connection, 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.saveConnection = function saveConnection() {
$scope.connection.parameters = $scope.parameters;
// Save the connection
- connectionService.saveConnection($scope.selectedDataSource, $scope.connection)
- .then(function savedConnection() {
- $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
-
- };
-
- /**
- * An action to be provided along with the object sent to showStatus which
- * immediately deletes the current connection.
- */
- var DELETE_ACTION = {
- name : "MANAGE_CONNECTION.ACTION_DELETE",
- className : "danger",
- // Handle action
- callback : function deleteCallback() {
- deleteConnectionImmediately();
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * An action to be provided along with the object sent to showStatus which
- * closes the currently-shown status dialog.
- */
- var CANCEL_ACTION = {
- name : "MANAGE_CONNECTION.ACTION_CANCEL",
- // Handle action
- callback : function cancelCallback() {
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * Immediately deletes the current connection, without prompting the user
- * for confirmation.
- */
- var deleteConnectionImmediately = function deleteConnectionImmediately() {
-
- // Delete the connection
- connectionService.deleteConnection($scope.selectedDataSource, $scope.connection)
- .then(function deletedConnection() {
- $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
+ return connectionService.saveConnection($scope.selectedDataSource, $scope.connection);
};
/**
- * Deletes the connection, prompting the user first to confirm that
- * deletion is desired.
+ * Deletes the current connection, 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.deleteConnection = function deleteConnection() {
-
- // Confirm deletion request
- guacNotification.showStatus({
- 'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_CONFIRM_DELETE',
- 'text' : {
- key : 'MANAGE_CONNECTION.TEXT_CONFIRM_DELETE'
- },
- 'actions' : [ DELETE_ACTION, CANCEL_ACTION]
- });
-
+ return connectionService.deleteConnection($scope.selectedDataSource, $scope.connection);
};
}]);
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
index de29aff12..250ebc102 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
@@ -24,15 +24,16 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
function manageConnectionGroupController($scope, $injector) {
// Required types
- var ConnectionGroup = $injector.get('ConnectionGroup');
- var PermissionSet = $injector.get('PermissionSet');
+ var ConnectionGroup = $injector.get('ConnectionGroup');
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionSet = $injector.get('PermissionSet');
// Required services
var $location = $injector.get('$location');
+ var $q = $injector.get('$q');
var $routeParams = $injector.get('$routeParams');
var authenticationService = $injector.get('authenticationService');
var connectionGroupService = $injector.get('connectionGroupService');
- var guacNotification = $injector.get('guacNotification');
var permissionService = $injector.get('permissionService');
var requestService = $injector.get('requestService');
var schemaService = $injector.get('schemaService');
@@ -45,6 +46,15 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
*/
$scope.selectedDataSource = $routeParams.dataSource;
+ /**
+ * The identifier of the original connection group from which this
+ * connection group is being cloned. Only valid if this is a new
+ * connection group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
/**
* The identifier of the connection group being edited. If a new connection
* group is being created, this will not be defined.
@@ -53,6 +63,23 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
*/
var identifier = $routeParams.id;
+ /**
+ * Available connection group types, as translation string / internal value
+ * pairs.
+ *
+ * @type Object[]
+ */
+ $scope.types = [
+ {
+ label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_ORGANIZATIONAL",
+ value: ConnectionGroup.Type.ORGANIZATIONAL
+ },
+ {
+ label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_BALANCING",
+ value : ConnectionGroup.Type.BALANCING
+ }
+ ];
+
/**
* The root connection group of the connection group hierarchy.
*
@@ -68,26 +95,13 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
$scope.connectionGroup = null;
/**
- * Whether the user has UPDATE permission for the current connection group.
- *
- * @type Boolean
- */
- $scope.hasUpdatePermission = null;
-
- /**
- * Whether the user has DELETE permission for the current connection group.
- *
- * @type Boolean
- */
- $scope.hasDeletePermission = null;
-
- /**
- * All permissions associated with the current user, or null if the user's
- * permissions have not yet been loaded.
+ * The management-related actions that the current user may perform on the
+ * connection group currently being created/modified, or null if the current
+ * user's permissions have not yet been loaded.
*
- * @type PermissionSet
+ * @type ManagementPermissions
*/
- $scope.permissions = null;
+ $scope.managementPermissions = null;
/**
* All available connection group attributes. This is only the set of
@@ -109,177 +123,172 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
return $scope.rootGroup !== null
&& $scope.connectionGroup !== null
- && $scope.permissions !== null
- && $scope.attributes !== null
- && $scope.canSaveConnectionGroup !== null
- && $scope.canDeleteConnectionGroup !== null;
+ && $scope.managementPermissions !== null
+ && $scope.attributes !== null;
};
-
- // Pull connection group attribute schema
- schemaService.getConnectionGroupAttributes($scope.selectedDataSource)
- .then(function attributesReceived(attributes) {
- $scope.attributes = attributes;
- }, requestService.WARN);
- // Query the user's permissions for the current connection group
- permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
- .then(function permissionsReceived(permissions) {
-
- $scope.permissions = permissions;
-
- // Check if the connection group is new or if the user has UPDATE permission
- $scope.canSaveConnectionGroup =
- !identifier
- || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
- || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
-
- // Check if connection group is not new and the user has DELETE permission
- $scope.canDeleteConnectionGroup =
- !!identifier && (
- PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
- || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier)
- );
-
- }, requestService.WARN);
-
-
- // Pull connection group hierarchy
- connectionGroupService.getConnectionGroupTree(
- $scope.selectedDataSource,
- ConnectionGroup.ROOT_IDENTIFIER,
- [PermissionSet.ObjectPermissionType.ADMINISTER]
- )
- .then(function connectionGroupReceived(rootGroup) {
- $scope.rootGroup = rootGroup;
- }, requestService.WARN);
-
- // If we are editing an existing connection group, pull its data
- if (identifier) {
- connectionGroupService.getConnectionGroup($scope.selectedDataSource, identifier)
+ /**
+ * Loads the data associated with the connection group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing connection group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the connection
+ * group to load.
+ *
+ * @param {String} identifier
+ * The identifier of the connection group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given connection group.
+ */
+ var loadExistingConnectionGroup = function loadExistingConnectionGroup(dataSource, identifier) {
+ return connectionGroupService.getConnectionGroup(
+ dataSource,
+ identifier
+ )
.then(function connectionGroupReceived(connectionGroup) {
$scope.connectionGroup = connectionGroup;
- }, requestService.WARN);
- }
+ });
+ };
- // If we are creating a new connection group, populate skeleton connection group data
- else
+ /**
+ * Loads the data associated with the connection group having the given
+ * identifier, preparing the interface for cloning that existing
+ * connection group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the connection
+ * group to be cloned.
+ *
+ * @param {String} identifier
+ * The identifier of the connection group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given connection group.
+ */
+ var loadClonedConnectionGroup = function loadClonedConnectionGroup(dataSource, identifier) {
+ return connectionGroupService.getConnectionGroup(
+ dataSource,
+ identifier
+ )
+ .then(function connectionGroupReceived(connectionGroup) {
+ $scope.connectionGroup = connectionGroup;
+ delete $scope.connectionGroup.identifier;
+ });
+ };
+
+ /**
+ * Loads skeleton connection group data, preparing the interface for
+ * creating a new connection group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new connection group.
+ */
+ var loadSkeletonConnectionGroup = function loadSkeletonConnectionGroup() {
+
+ // Use skeleton connection group object with specified parent
$scope.connectionGroup = new ConnectionGroup({
parentIdentifier : $location.search().parent
});
- /**
- * Available connection group types, as translation string / internal value
- * pairs.
- *
- * @type Object[]
- */
- $scope.types = [
- {
- label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_ORGANIZATIONAL",
- value: ConnectionGroup.Type.ORGANIZATIONAL
- },
- {
- label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_BALANCING",
- value : ConnectionGroup.Type.BALANCING
- }
- ];
-
- /**
- * Returns whether the current user can change/set all connection group
- * attributes for the connection group being edited, regardless of whether
- * those attributes are already explicitly associated with that connection
- * group.
- *
- * @returns {Boolean}
- * true if the current user can change all attributes for the
- * connection group being edited, regardless of whether those
- * attributes are already explicitly associated with that connection
- * group, false otherwise.
- */
- $scope.canChangeAllAttributes = function canChangeAllAttributes() {
-
- // All attributes can be set if we are creating the connection group
- return !identifier;
+ return $q.resolve();
};
/**
- * Cancels all pending edits, returning to the management page.
+ * 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 connection group, cloning an
+ * existing connection group, or creating an entirely new connection group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
*/
- $scope.cancel = function cancel() {
+ var loadRequestedConnectionGroup = function loadRequestedConnectionGroup() {
+
+ // If we are editing an existing connection group, pull its data
+ if (identifier)
+ return loadExistingConnectionGroup($scope.selectedDataSource, identifier);
+
+ // If we are cloning an existing connection group, pull its data
+ // instead
+ if (cloneSourceIdentifier)
+ return loadClonedConnectionGroup($scope.selectedDataSource, cloneSourceIdentifier);
+
+ // If we are creating a new connection group, populate skeleton
+ // connection group data
+ return loadSkeletonConnectionGroup();
+
+ };
+
+ // Query the user's permissions for the current connection group
+ $q.all({
+ connectionGroupData : loadRequestedConnectionGroup(),
+ attributes : schemaService.getConnectionGroupAttributes($scope.selectedDataSource),
+ permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()),
+ rootGroup : connectionGroupService.getConnectionGroupTree($scope.selectedDataSource, ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
+ })
+ .then(function connectionGroupDataRetrieved(values) {
+
+ $scope.attributes = values.attributes;
+ $scope.rootGroup = values.rootGroup;
+
+ $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+ values.permissions,
+ PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+ PermissionSet.hasConnectionPermission,
+ identifier);
+
+ }, requestService.WARN);
+
+ /**
+ * Cancels all pending edits, returning to the main list of connections
+ * within the selected data source.
+ */
+ $scope.returnToConnectionList = function returnToConnectionList() {
$location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
};
-
+
/**
- * Saves the connection group, creating a new connection group or updating
- * the existing connection group.
+ * Cancels all pending edits, opening an edit page for a new connection
+ * group which is prepopulated with the data from the connection group
+ * currently being edited.
+ */
+ $scope.cloneConnectionGroup = function cloneConnectionGRoup() {
+ $location.path('/manage/' + encodeURIComponent($scope.selectedDataSource) + '/connectionGroups').search('clone', identifier);
+ };
+
+ /**
+ * Saves the current connection group, creating a new connection group or
+ * updating the existing connection group, 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.saveConnectionGroup = function saveConnectionGroup() {
-
- // Save the connection
- connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup)
- .then(function savedConnectionGroup() {
- $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
-
+ return connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup);
};
/**
- * An action to be provided along with the object sent to showStatus which
- * immediately deletes the current connection group.
- */
- var DELETE_ACTION = {
- name : "MANAGE_CONNECTION_GROUP.ACTION_DELETE",
- className : "danger",
- // Handle action
- callback : function deleteCallback() {
- deleteConnectionGroupImmediately();
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * An action to be provided along with the object sent to showStatus which
- * closes the currently-shown status dialog.
- */
- var CANCEL_ACTION = {
- name : "MANAGE_CONNECTION_GROUP.ACTION_CANCEL",
- // Handle action
- callback : function cancelCallback() {
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * Immediately deletes the current connection group, without prompting the
- * user for confirmation.
- */
- var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() {
-
- // Delete the connection group
- connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup)
- .then(function deletedConnectionGroup() {
- $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
-
- };
-
- /**
- * Deletes the connection group, prompting the user first to confirm that
- * deletion is desired.
+ * Deletes the current connection 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.deleteConnectionGroup = function deleteConnectionGroup() {
-
- // Confirm deletion request
- guacNotification.showStatus({
- 'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_CONFIRM_DELETE',
- 'text' : {
- key : 'MANAGE_CONNECTION_GROUP.TEXT_CONFIRM_DELETE'
- },
- 'actions' : [ DELETE_ACTION, CANCEL_ACTION]
- });
-
+ return connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup);
};
}]);
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index 7414e653a..61bfbe93f 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -24,32 +24,21 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
function manageSharingProfileController($scope, $injector) {
// Required types
- var SharingProfile = $injector.get('SharingProfile');
- var PermissionSet = $injector.get('PermissionSet');
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var SharingProfile = $injector.get('SharingProfile');
+ var PermissionSet = $injector.get('PermissionSet');
+ var Protocol = $injector.get('Protocol');
// Required services
var $location = $injector.get('$location');
+ var $q = $injector.get('$q');
var $routeParams = $injector.get('$routeParams');
var authenticationService = $injector.get('authenticationService');
var connectionService = $injector.get('connectionService');
- var guacNotification = $injector.get('guacNotification');
var permissionService = $injector.get('permissionService');
var requestService = $injector.get('requestService');
var schemaService = $injector.get('schemaService');
var sharingProfileService = $injector.get('sharingProfileService');
- var translationStringService = $injector.get('translationStringService');
-
- /**
- * An action to be provided along with the object sent to showStatus which
- * closes the currently-shown status dialog, effectively canceling the
- * operation which was pending user confirmation.
- */
- var CANCEL_ACTION = {
- name : "MANAGE_SHARING_PROFILE.ACTION_CANCEL",
- callback : function cancelCallback() {
- guacNotification.showStatus(false);
- }
- };
/**
* The unique identifier of the data source containing the sharing profile
@@ -98,34 +87,13 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
$scope.parameters = null;
/**
- * Whether the user can save the sharing profile being edited. This could be
- * updating an existing sharing profile, or creating a new sharing profile.
+ * The management-related actions that the current user may perform on the
+ * sharing profile currently being created/modified, or null if the current
+ * user's permissions have not yet been loaded.
*
- * @type Boolean
+ * @type ManagementPermissions
*/
- $scope.canSaveSharingProfile = null;
-
- /**
- * Whether the user can delete the sharing profile being edited.
- *
- * @type Boolean
- */
- $scope.canDeleteSharingProfile = null;
-
- /**
- * Whether the user can clone the sharing profile being edited.
- *
- * @type Boolean
- */
- $scope.canCloneSharingProfile = null;
-
- /**
- * All permissions associated with the current user, or null if the user's
- * permissions have not yet been loaded.
- *
- * @type PermissionSet
- */
- $scope.permissions = null;
+ $scope.managementPermissions = null;
/**
* All available sharing profile attributes. This is only the set of
@@ -149,176 +117,178 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
&& $scope.sharingProfile !== null
&& $scope.primaryConnection !== null
&& $scope.parameters !== null
- && $scope.permissions !== null
- && $scope.attributes !== null
- && $scope.canSaveSharingProfile !== null
- && $scope.canDeleteSharingProfile !== null
- && $scope.canCloneSharingProfile !== null;
+ && $scope.managementPermissions !== null
+ && $scope.attributes !== null;
};
- // Pull sharing profile attribute schema
- schemaService.getSharingProfileAttributes($scope.selectedDataSource)
- .then(function attributesReceived(attributes) {
- $scope.attributes = attributes;
- }, requestService.WARN);
+ /**
+ * Loads the data associated with the sharing profile having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing sharing profile.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the sharing
+ * profile to load.
+ *
+ * @param {String} identifier
+ * The identifier of the sharing profile to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given sharing profile.
+ */
+ var loadExistingSharingProfile = function loadExistingSharingProfile(dataSource, identifier) {
+ return $q.all({
+ sharingProfile : sharingProfileService.getSharingProfile(dataSource, identifier),
+ parameters : sharingProfileService.getSharingProfileParameters(dataSource, identifier)
+ })
+ .then(function sharingProfileDataRetrieved(values) {
- // Query the user's permissions for the current sharing profile
- permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
- .then(function permissionsReceived(permissions) {
+ $scope.sharingProfile = values.sharingProfile;
+ $scope.parameters = values.parameters;
- $scope.permissions = permissions;
+ // Load connection object for associated primary connection
+ return connectionService.getConnection(
+ dataSource,
+ values.sharingProfile.primaryConnectionIdentifier
+ )
+ .then(function connectionRetrieved(connection) {
+ $scope.primaryConnection = connection;
+ });
- // The sharing profile can be saved if it is new or if the user has
- // UPDATE permission for that sharing profile (either explicitly or by
- // virtue of being an administrator)
- $scope.canSaveSharingProfile =
- !identifier
- || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
- || PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
+ });
+ };
- // The sharing profile can be saved only if it exists and the user has
- // DELETE permission (either explicitly or by virtue of being an
- // administrator)
- $scope.canDeleteSharingProfile =
- !!identifier && (
- PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
- || PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier)
- );
+ /**
+ * Loads the data associated with the sharing profile having the given
+ * identifier, preparing the interface for cloning that existing
+ * sharing profile.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the sharing
+ * profile to be cloned.
+ *
+ * @param {String} identifier
+ * The identifier of the sharing profile being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given sharing profile.
+ */
+ var loadClonedSharingProfile = function loadClonedSharingProfile(dataSource, identifier) {
+ return $q.all({
+ sharingProfile : sharingProfileService.getSharingProfile(dataSource, identifier),
+ parameters : sharingProfileService.getSharingProfileParameters(dataSource, identifier)
+ })
+ .then(function sharingProfileDataRetrieved(values) {
- // The sharing profile can be cloned only if it exists, the user has
- // UPDATE permission on the sharing profile being cloned (is able to
- // read parameters), and the user can create new sharing profiles
- $scope.canCloneSharingProfile =
- !!identifier && (
- PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || (
- PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier)
- && PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE)
- )
- );
-
- }, requestService.WARN);
-
- // Get protocol metadata
- schemaService.getProtocols($scope.selectedDataSource)
- .then(function protocolsReceived(protocols) {
- $scope.protocols = protocols;
- }, requestService.WARN);
-
- // If we are editing an existing sharing profile, pull its data
- if (identifier) {
-
- // Pull data from existing sharing profile
- sharingProfileService.getSharingProfile($scope.selectedDataSource, identifier)
- .then(function sharingProfileRetrieved(sharingProfile) {
- $scope.sharingProfile = sharingProfile;
- }, requestService.WARN);
-
- // Pull sharing profile parameters
- sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, identifier)
- .then(function parametersReceived(parameters) {
- $scope.parameters = parameters;
- }, requestService.WARN);
-
- }
-
- // If we are cloning an existing sharing profile, pull its data instead
- else if (cloneSourceIdentifier) {
-
- // Pull data from cloned sharing profile
- sharingProfileService.getSharingProfile($scope.selectedDataSource, cloneSourceIdentifier)
- .then(function sharingProfileRetrieved(sharingProfile) {
-
- // Store data of sharing profile being cloned
- $scope.sharingProfile = sharingProfile;
+ $scope.sharingProfile = values.sharingProfile;
+ $scope.parameters = values.parameters;
// Clear the identifier field because this sharing profile is new
delete $scope.sharingProfile.identifier;
- }, requestService.WARN);
+ // Load connection object for associated primary connection
+ return connectionService.getConnection(
+ dataSource,
+ values.sharingProfile.primaryConnectionIdentifier
+ )
+ .then(function connectionRetrieved(connection) {
+ $scope.primaryConnection = connection;
+ });
- // Pull sharing profile parameters from cloned sharing profile
- sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, cloneSourceIdentifier)
- .then(function parametersReceived(parameters) {
- $scope.parameters = parameters;
- }, requestService.WARN);
+ });
+ };
- }
-
- // If we are creating a new sharing profile, populate skeleton sharing
- // profile data
- else {
+ /**
+ * Loads skeleton sharing profile data, preparing the interface for
+ * creating a new sharing profile.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the sharing
+ * profile to be created.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new sharing profile.
+ */
+ var loadSkeletonSharingProfile = function loadSkeletonSharingProfile(dataSource) {
+ // Use skeleton sharing profile object with no associated parameters
$scope.sharingProfile = new SharingProfile({
primaryConnectionIdentifier : $location.search().parent
});
-
$scope.parameters = {};
- }
-
- // Populate primary connection once its identifier is known
- $scope.$watch('sharingProfile.primaryConnectionIdentifier',
- function retrievePrimaryConnection(identifier) {
-
- if (identifier) {
- connectionService.getConnection($scope.selectedDataSource, identifier)
- .then(function connectionRetrieved(connection) {
- $scope.primaryConnection = connection;
- }, requestService.WARN);
- }
-
- });
-
- /**
- * Returns whether the current user can change/set all sharing profile
- * attributes for the sharing profile being edited, regardless of whether
- * those attributes are already explicitly associated with that sharing
- * profile.
- *
- * @returns {Boolean}
- * true if the current user can change all attributes for the sharing
- * profile being edited, regardless of whether those attributes are
- * already explicitly associated with that sharing profile, false
- * otherwise.
- */
- $scope.canChangeAllAttributes = function canChangeAllAttributes() {
-
- // All attributes can be set if we are creating the sharing profile
- return !identifier;
+ // Load connection object for associated primary connection
+ return connectionService.getConnection(
+ dataSource,
+ $scope.sharingProfile.primaryConnectionIdentifier
+ )
+ .then(function connectionRetrieved(connection) {
+ $scope.primaryConnection = connection;
+ });
};
/**
- * Returns the translation string namespace for the protocol having the
- * given name. The namespace will be of the form:
+ * 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 sharing profile, cloning an
+ * existing sharing profile, or creating an entirely new sharing profile.
*
- * PROTOCOL_NAME
- *
- * where NAME is the protocol name transformed via
- * translationStringService.canonicalize().
- *
- * @param {String} protocolName
- * The name of the protocol.
- *
- * @returns {String}
- * The translation namespace for the protocol specified, or null if no
- * namespace could be generated.
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
*/
- $scope.getNamespace = function getNamespace(protocolName) {
+ var loadRequestedSharingProfile = function loadRequestedSharingProfile() {
- // Do not generate a namespace if no protocol is selected
- if (!protocolName)
- return null;
+ // If we are editing an existing sharing profile, pull its data
+ if (identifier)
+ return loadExistingSharingProfile($scope.selectedDataSource, identifier);
- return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
+ // If we are cloning an existing sharing profile, pull its data instead
+ if (cloneSourceIdentifier)
+ return loadClonedSharingProfile($scope.selectedDataSource, cloneSourceIdentifier);
+
+ // If we are creating a new sharing profile, populate skeleton sharing
+ // profile data
+ return loadSkeletonSharingProfile($scope.selectedDataSource);
};
+
+ // Query the user's permissions for the current sharing profile
+ $q.all({
+ sharingProfileData : loadRequestedSharingProfile(),
+ attributes : schemaService.getSharingProfileAttributes($scope.selectedDataSource),
+ protocols : schemaService.getProtocols($scope.selectedDataSource),
+ permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
+ })
+ .then(function sharingProfileDataRetrieved(values) {
+
+ $scope.attributes = values.attributes;
+ $scope.protocols = values.protocols;
+
+ $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+ values.permissions,
+ PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+ PermissionSet.hasConnectionPermission,
+ identifier);
+
+ }, requestService.WARN);
+
/**
- * Cancels all pending edits, returning to the management page.
+ * @borrows Protocol.getNamespace
*/
- $scope.cancel = function cancel() {
+ $scope.getNamespace = Protocol.getNamespace;
+
+ /**
+ * Cancels all pending edits, returning to the main list of connections
+ * within the selected data source.
+ */
+ $scope.returnToConnectionList = function returnToConnectionList() {
$location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
};
@@ -332,64 +302,35 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
};
/**
- * Saves the sharing profile, creating a new sharing profile or updating
- * the existing sharing profile.
+ * Saves the current sharing profile, creating a new sharing profile or
+ * updating the existing sharing profile, 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.saveSharingProfile = function saveSharingProfile() {
$scope.sharingProfile.parameters = $scope.parameters;
// Save the sharing profile
- sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile)
- .then(function savedSharingProfile() {
- $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
+ return sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile);
};
/**
- * An action to be provided along with the object sent to showStatus which
- * immediately deletes the current sharing profile.
- */
- var DELETE_ACTION = {
- name : "MANAGE_SHARING_PROFILE.ACTION_DELETE",
- className : "danger",
- // Handle action
- callback : function deleteCallback() {
- deleteSharingProfileImmediately();
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * Immediately deletes the current sharing profile, without prompting the
- * user for confirmation.
- */
- var deleteSharingProfileImmediately = function deleteSharingProfileImmediately() {
-
- // Delete the sharing profile
- sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile)
- .then(function deletedSharingProfile() {
- $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
- }, guacNotification.SHOW_REQUEST_ERROR);
-
- };
-
- /**
- * Deletes the sharing profile, prompting the user first to confirm that
- * deletion is desired.
+ * Deletes the current sharing profile, 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.deleteSharingProfile = function deleteSharingProfile() {
-
- // Confirm deletion request
- guacNotification.showStatus({
- 'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_CONFIRM_DELETE',
- 'text' : {
- 'key' : 'MANAGE_SHARING_PROFILE.TEXT_CONFIRM_DELETE'
- },
- 'actions' : [ DELETE_ACTION, CANCEL_ACTION]
- });
-
+ return sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile);
};
}]);
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index eae141b26..167988623 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -24,38 +24,23 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
function manageUserController($scope, $injector) {
// Required types
- var ConnectionGroup = $injector.get('ConnectionGroup');
- var GroupListItem = $injector.get('GroupListItem');
- var PageDefinition = $injector.get('PageDefinition');
- var PermissionFlagSet = $injector.get('PermissionFlagSet');
- var PermissionSet = $injector.get('PermissionSet');
- var User = $injector.get('User');
+ var Error = $injector.get('Error');
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var User = $injector.get('User');
// Required services
var $location = $injector.get('$location');
var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
var authenticationService = $injector.get('authenticationService');
- var connectionGroupService = $injector.get('connectionGroupService');
var dataSourceService = $injector.get('dataSourceService');
- var guacNotification = $injector.get('guacNotification');
var permissionService = $injector.get('permissionService');
var requestService = $injector.get('requestService');
var schemaService = $injector.get('schemaService');
- var translationStringService = $injector.get('translationStringService');
var userService = $injector.get('userService');
- /**
- * An action to be provided along with the object sent to showStatus which
- * closes the currently-shown status dialog.
- */
- var ACKNOWLEDGE_ACTION = {
- name : "MANAGE_USER.ACTION_ACKNOWLEDGE",
- // Handle action
- callback : function acknowledgeCallback() {
- guacNotification.showStatus(false);
- }
- };
-
/**
* The identifiers of all data sources currently available to the
* authenticated user.
@@ -71,14 +56,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
*/
var currentUsername = authenticationService.getCurrentUsername();
- /**
- * The unique identifier of the data source containing the user being
- * edited.
- *
- * @type String
- */
- var selectedDataSource = $routeParams.dataSource;
-
/**
* The username of the original user from which this user is
* being cloned. Only valid if this is a new user.
@@ -95,6 +72,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
*/
var username = $routeParams.id;
+ /**
+ * The unique identifier of the data source containing the user being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
/**
* The string value representing the user currently being edited within the
* permission flag set. Note that his may not match the user's actual
@@ -131,41 +116,34 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
$scope.permissionFlags = null;
/**
- * A map of data source identifiers to the root connection groups within
- * thost data sources. As only one data source is applicable to any one
- * user being edited/created, this will only contain a single key.
+ * The set of permissions that will be added to the user when the user is
+ * saved. Permissions will only be present in this set if they are
+ * manually added, and not later manually removed before saving.
*
- * @type Object.
+ * @type PermissionSet
*/
- $scope.rootGroups = null;
+ $scope.permissionsAdded = new PermissionSet();
/**
- * Array of all connection properties that are filterable.
+ * The set of permissions that will be removed from the user when the user
+ * is saved. Permissions will only be present in this set if they are
+ * manually removed, and not later manually added before saving.
*
- * @type String[]
+ * @type PermissionSet
*/
- $scope.filteredConnectionProperties = [
- 'name',
- 'protocol'
- ];
+ $scope.permissionsRemoved = new PermissionSet();
/**
- * Array of all connection group properties that are filterable.
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user account 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 String[]
+ * @type Object.
*/
- $scope.filteredConnectionGroupProperties = [
- 'name'
- ];
-
- /**
- * A map of data source identifiers to the set of all permissions
- * associated with the current user under that data source, or null if the
- * user's permissions have not yet been loaded.
- *
- * @type Object.
- */
- $scope.permissions = null;
+ $scope.managementPermissions = null;
/**
* All available user attributes. This is only the set of attribute
@@ -176,14 +154,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
*/
$scope.attributes = null;
- /**
- * The pages associated with each user account having the given username.
- * Each user account will be associated with a particular data source.
- *
- * @type PageDefinition[]
- */
- $scope.accountPages = [];
-
/**
* Returns whether critical data has completed being loaded.
*
@@ -193,154 +163,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
*/
$scope.isLoaded = function isLoaded() {
- return $scope.users !== null
- && $scope.permissionFlags !== null
- && $scope.rootGroups !== null
- && $scope.permissions !== null
- && $scope.attributes !== null;
-
- };
-
- /**
- * Returns whether the user being edited already exists within the data
- * source specified.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the user being edited already exists, false otherwise.
- */
- $scope.userExists = function userExists(dataSource) {
-
- // Do not check if users are not yet loaded
- if (!$scope.users)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // Account exists only if it was successfully retrieved
- return (dataSource in $scope.users);
-
- };
-
- /**
- * Returns whether the current user can change attributes explicitly
- * associated with the user being edited within the given data source.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the current user can change attributes associated with the
- * user being edited, false otherwise.
- */
- $scope.canChangeAttributes = function canChangeAttributes(dataSource) {
-
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // Attributes can always be set if we are creating the user
- if (!$scope.userExists(dataSource))
- return true;
-
- // The administrator can always change attributes
- if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER))
- return true;
-
- // Otherwise, can change attributes if we have permission to update this user
- return PermissionSet.hasUserPermission($scope.permissions[dataSource],
- PermissionSet.ObjectPermissionType.UPDATE, username);
-
- };
-
- /**
- * Returns whether the current user can change/set all user attributes for
- * the user being edited, regardless of whether those attributes are
- * already explicitly associated with that user.
- *
- * @returns {Boolean}
- * true if the current user can change all attributes for the user
- * being edited, regardless of whether those attributes are already
- * explicitly associated with that user, false otherwise.
- */
- $scope.canChangeAllAttributes = function canChangeAllAttributes() {
-
- // All attributes can be set if we are creating the user
- return !$scope.userExists(selectedDataSource);
-
- };
-
- /**
- * Returns whether the current user can change permissions of any kind
- * which are associated with the user being edited within the given data
- * source.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the current user can grant or revoke permissions of any kind
- * which are associated with the user being edited, false otherwise.
- */
- $scope.canChangePermissions = function canChangePermissions(dataSource) {
-
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // Permissions can always be set if we are creating the user
- if (!$scope.userExists(dataSource))
- return true;
-
- // The administrator can always modify permissions
- if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER))
- return true;
-
- // Otherwise, can only modify permissions if we have explicit
- // ADMINISTER permission
- return PermissionSet.hasUserPermission($scope.permissions[dataSource],
- PermissionSet.ObjectPermissionType.ADMINISTER, username);
-
- };
-
- /**
- * Returns whether the current user can change the system permissions
- * granted to the user being edited within the given data source.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the current user can grant or revoke system permissions to
- * the user being edited, false otherwise.
- */
- $scope.canChangeSystemPermissions = function canChangeSystemPermissions(dataSource) {
-
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // Only the administrator can modify system permissions
- return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER);
+ return $scope.users !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.attributes !== null;
};
@@ -361,189 +187,29 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
};
/**
- * Returns whether the current user can save the user being edited within
- * the given data source. Saving will create or update that user depending
- * on whether the user already exists.
+ * Loads the data associated with the user having the given username,
+ * preparing the interface for making modifications to that existing user.
*
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user to
+ * load.
*
- * @returns {Boolean}
- * true if the current user can save changes to the user being edited,
- * false otherwise.
+ * @param {String} username
+ * The username of the user to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user.
*/
- $scope.canSaveUser = function canSaveUser(dataSource) {
+ var loadExistingUser = function loadExistingUser(dataSource, username) {
+ return $q.all({
+ users : dataSourceService.apply(userService.getUser, dataSources, username),
+ permissions : permissionService.getPermissions(dataSource, username)
+ })
+ .then(function userDataRetrieved(values) {
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // The administrator can always save users
- if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER))
- return true;
-
- // If user does not exist, can only save if we have permission to create users
- if (!$scope.userExists(dataSource))
- return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.CREATE_USER);
-
- // Otherwise, can only save if we have permission to update this user
- return PermissionSet.hasUserPermission($scope.permissions[dataSource],
- PermissionSet.ObjectPermissionType.UPDATE, username);
-
- };
-
- /**
- * Returns whether the current user can clone the user being edited within
- * the given data source.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the current user can clone the user being edited, false
- * otherwise.
- */
- $scope.canCloneUser = function canCloneUser(dataSource) {
-
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // If we are not editing an existing user, we cannot clone
- if (!$scope.userExists(selectedDataSource))
- return false;
-
- // The administrator can always clone users
- if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER))
- return true;
-
- // Otherwise we need explicit CREATE_USER permission
- return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.CREATE_USER);
-
- };
-
- /**
- * Returns whether the current user can delete the user being edited from
- * the given data source.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the current user can delete the user being edited, false
- * otherwise.
- */
- $scope.canDeleteUser = function canDeleteUser(dataSource) {
-
- // Do not check if permissions are not yet loaded
- if (!$scope.permissions)
- return false;
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // Can't delete what doesn't exist
- if (!$scope.userExists(dataSource))
- return false;
-
- // The administrator can always delete users
- if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
- PermissionSet.SystemPermissionType.ADMINISTER))
- return true;
-
- // Otherwise, require explicit DELETE permission on the user
- return PermissionSet.hasUserPermission($scope.permissions[dataSource],
- PermissionSet.ObjectPermissionType.DELETE, username);
-
- };
-
- /**
- * Returns whether the user being edited within the given data source is
- * read-only, and thus cannot be modified by the current user.
- *
- * @param {String} [dataSource]
- * The identifier of the data source to check. If omitted, this will
- * default to the currently-selected data source.
- *
- * @returns {Boolean}
- * true if the user being edited is actually read-only and cannot be
- * edited at all, false otherwise.
- */
- $scope.isReadOnly = function isReadOnly(dataSource) {
-
- // Use currently-selected data source if unspecified
- dataSource = dataSource || selectedDataSource;
-
- // User is read-only if they cannot be saved
- return !$scope.canSaveUser(dataSource);
-
- };
-
- // Update visible account pages whenever available users/permissions changes
- $scope.$watchGroup(['users', 'permissions'], function updateAccountPages() {
-
- // Generate pages for each applicable data source
- $scope.accountPages = [];
- angular.forEach(dataSources, function addAccountPage(dataSource) {
-
- // Determine whether data source contains this user
- var linked = $scope.userExists(dataSource);
- var readOnly = $scope.isReadOnly(dataSource);
-
- // Account is not relevant if it does not exist and cannot be
- // created
- if (!linked && readOnly)
- return;
-
- // Only the selected data source is relevant when cloning
- if (cloneSourceUsername && dataSource !== selectedDataSource)
- return;
-
- // Determine class name based on read-only / linked status
- var className;
- if (readOnly) className = 'read-only';
- else if (linked) className = 'linked';
- else className = 'unlinked';
-
- // Add page entry
- $scope.accountPages.push(new PageDefinition({
- name : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
- url : '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username || ''),
- className : className
- }));
-
- });
-
- });
-
- // Pull user attribute schema
- schemaService.getUserAttributes(selectedDataSource).then(function attributesReceived(attributes) {
- $scope.attributes = attributes;
- }, requestService.WARN);
-
- // Pull user data and permissions if we are editing an existing user
- if (username) {
-
- // Pull user data
- dataSourceService.apply(userService.getUser, dataSources, username)
- .then(function usersReceived(users) {
-
- // Get user for currently-selected data source
- $scope.users = users;
- $scope.user = users[selectedDataSource];
+ $scope.users = values.users;
+ $scope.user = values.users[dataSource];
// Create skeleton user if user does not exist
if (!$scope.user)
@@ -551,54 +217,57 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
'username' : username
});
- }, requestService.WARN);
+ // The current user will be associated with username of the existing
+ // user in the retrieved permission set
+ $scope.selfUsername = username;
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
- // The current user will be associated with username of the existing
- // user in the retrieved permission set
- $scope.selfUsername = username;
+ });
+ };
- // Pull user permissions
- permissionService.getPermissions(selectedDataSource, username).then(function gotPermissions(permissions) {
- $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
+ /**
+ * Loads the data associated with the user having the given username,
+ * preparing the interface for cloning that existing user.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user to
+ * be cloned.
+ *
+ * @param {String} username
+ * The username of the user being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user.
+ */
+ var loadClonedUser = function loadClonedUser(dataSource, username) {
+ return $q.all({
+ users : dataSourceService.apply(userService.getUser, [dataSource], username),
+ permissions : permissionService.getPermissions(dataSource, username)
})
+ .then(function userDataRetrieved(values) {
- // If permissions cannot be retrieved, use empty permissions
- ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
- $scope.permissionFlags = new PermissionFlagSet();
- }));
- }
-
- // If we are cloning an existing user, pull his/her data instead
- else if (cloneSourceUsername) {
-
- dataSourceService.apply(userService.getUser, dataSources, cloneSourceUsername)
- .then(function usersReceived(users) {
-
- // Get user for currently-selected data source
$scope.users = {};
- $scope.user = users[selectedDataSource];
+ $scope.user = values.users[dataSource];
- }, requestService.WARN);
+ // The current user will be associated with cloneSourceUsername in the
+ // retrieved permission set
+ $scope.selfUsername = username;
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
- // The current user will be associated with cloneSourceUsername in the
- // retrieved permission set
- $scope.selfUsername = cloneSourceUsername;
+ });
+ };
- // Pull user permissions
- permissionService.getPermissions(selectedDataSource, cloneSourceUsername)
- .then(function gotPermissions(permissions) {
- $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
- permissionsAdded = permissions;
- })
-
- // If permissions cannot be retrieved, use empty permissions
- ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
- $scope.permissionFlags = new PermissionFlagSet();
- }));
- }
-
- // Use skeleton data if we are creating a new user
- else {
+ /**
+ * Loads skeleton user data, preparing the interface for creating a new
+ * user.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user.
+ */
+ var loadSkeletonUser = function loadSkeletonUser() {
// No users exist regardless of data source if there is no username
$scope.users = {};
@@ -607,481 +276,88 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
$scope.user = new User();
$scope.permissionFlags = new PermissionFlagSet();
- }
+ // As no permissions are yet associated with the user, it is safe to
+ // use any non-empty username as a placeholder for self-referential
+ // permissions
+ $scope.selfUsername = 'SELF';
- /**
- * Expands all items within the tree descending from the given
- * GroupListItem which have at least one descendant for which explicit READ
- * permission is granted. The expanded state of all other items is left
- * untouched.
- *
- * @param {GroupListItem} item
- * The GroupListItem which should be conditionally expanded depending
- * on whether READ permission is granted for any of its descendants.
- *
- * @param {PemissionFlagSet} flags
- * The set of permissions which should be used to determine whether the
- * given item and its descendants are expanded.
- */
- var expandReadable = function expandReadable(item, flags) {
-
- // If the current item is expandable and has defined children,
- // determine whether it should be expanded
- if (item.expandable && item.children) {
- angular.forEach(item.children, function expandReadableChild(child) {
-
- // Determine whether the user has READ permission for the
- // current child object
- var readable = false;
- switch (child.type) {
-
- case GroupListItem.Type.CONNECTION:
- readable = flags.connectionPermissions.READ[child.identifier];
- break;
-
- case GroupListItem.Type.CONNECTION_GROUP:
- readable = flags.connectionGroupPermissions.READ[child.identifier];
- break;
-
- case GroupListItem.Type.SHARING_PROFILE:
- readable = flags.sharingProfilePermissions.READ[child.identifier];
- break;
-
- }
-
- // The parent should be expanded by default if the child is
- // expanded by default OR the user has READ permission on the
- // child
- item.expanded |= expandReadable(child, flags) || readable;
-
- });
- }
-
- return item.expanded;
+ 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, cloning an existing user, or
+ * creating an entirely new user.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
+ */
+ var loadRequestedUser = function loadRequestedUser() {
- // Retrieve all connections for which we have ADMINISTER permission
- dataSourceService.apply(
- connectionGroupService.getConnectionGroupTree,
- [selectedDataSource],
- ConnectionGroup.ROOT_IDENTIFIER,
- [PermissionSet.ObjectPermissionType.ADMINISTER]
- )
- .then(function connectionGroupReceived(rootGroups) {
+ // Pull user data and permissions if we are editing an existing user
+ if (username)
+ return loadExistingUser($scope.dataSource, username);
+
+ // If we are cloning an existing user, pull his/her data instead
+ if (cloneSourceUsername)
+ return loadClonedUser($scope.dataSource, cloneSourceUsername);
+
+ // If we are creating a new user, populate skeleton user data
+ return loadSkeletonUser();
+
+ };
+
+ // Populate interface with requested data
+ $q.all({
+ userData : loadRequestedUser(),
+ permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
+ attributes : schemaService.getUserAttributes($scope.dataSource)
+ })
+ .then(function dataReceived(values) {
+
+ $scope.attributes = values.attributes;
+
+ $scope.managementPermissions = {};
+ angular.forEach(dataSources, function addAccountPage(dataSource) {
+
+ // Determine whether data source contains this user
+ var exists = (dataSource in $scope.users);
+
+ // Calculate management actions available for this specific account
+ $scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+ values.permissions[dataSource],
+ PermissionSet.SystemPermissionType.CREATE_USER,
+ PermissionSet.hasUserPermission,
+ exists ? username : null);
- // Convert all received ConnectionGroup objects into GroupListItems
- $scope.rootGroups = {};
- angular.forEach(rootGroups, function addGroupListItem(rootGroup, dataSource) {
- $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup);
});
}, requestService.WARN);
-
- // Query the user's permissions for the current user
- dataSourceService.apply(
- permissionService.getEffectivePermissions,
- dataSources,
- currentUsername
- )
- .then(function permissionsReceived(permissions) {
- $scope.permissions = permissions;
- }, requestService.WARN);
-
- // Update default expanded state whenever connection groups and associated
- // permissions change
- $scope.$watchGroup(['rootGroups', 'permissionFlags'], function updateDefaultExpandedStates() {
- angular.forEach($scope.rootGroups, function updateExpandedStates(rootGroup) {
-
- // Automatically expand all objects with any descendants for which
- // the user has READ permission
- if ($scope.permissionFlags)
- expandReadable(rootGroup, $scope.permissionFlags);
-
- });
- });
/**
- * Available system permission types, as translation string / internal
- * value pairs.
- *
- * @type Object[]
- */
- $scope.systemPermissionTypes = [
- {
- label: "MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM",
- value: PermissionSet.SystemPermissionType.ADMINISTER
- },
- {
- label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
- value: PermissionSet.SystemPermissionType.CREATE_USER
- },
- {
- label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
- value: PermissionSet.SystemPermissionType.CREATE_CONNECTION
- },
- {
- label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS",
- value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP
- },
- {
- label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES",
- value: PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE
- }
- ];
-
- /**
- * The set of permissions that will be added to the user when the user is
- * saved. Permissions will only be present in this set if they are
- * manually added, and not later manually removed before saving.
+ * Returns the URL for the page which manages the user account currently
+ * being edited under the given data source. The given data source need not
+ * be the same as the data source currently selected.
*
- * @type PermissionSet
- */
- var permissionsAdded = new PermissionSet();
-
- /**
- * The set of permissions that will be removed from the user when the user
- * is saved. Permissions will only be present in this set if they are
- * manually removed, and not later manually added before saving.
+ * @param {String} dataSource
+ * The unique identifier of the data source that the URL is being
+ * generated for.
*
- * @type PermissionSet
+ * @returns {String}
+ * The URL for the page which manages the user account currently being
+ * edited under the given data source.
*/
- var permissionsRemoved = new PermissionSet();
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the addition of the given system permission.
- *
- * @param {String} type
- * The system permission to add, as defined by
- * PermissionSet.SystemPermissionType.
- */
- var addSystemPermission = function addSystemPermission(type) {
-
- // If permission was previously removed, simply un-remove it
- if (PermissionSet.hasSystemPermission(permissionsRemoved, type))
- PermissionSet.removeSystemPermission(permissionsRemoved, type);
-
- // Otherwise, explicitly add the permission
- else
- PermissionSet.addSystemPermission(permissionsAdded, type);
-
+ $scope.getUserURL = function getUserURL(dataSource) {
+ return '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username || '');
};
/**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the removal of the given system permission.
- *
- * @param {String} type
- * The system permission to remove, as defined by
- * PermissionSet.SystemPermissionType.
+ * Cancels all pending edits, returning to the main list of users.
*/
- var removeSystemPermission = function removeSystemPermission(type) {
-
- // If permission was previously added, simply un-add it
- if (PermissionSet.hasSystemPermission(permissionsAdded, type))
- PermissionSet.removeSystemPermission(permissionsAdded, type);
-
- // Otherwise, explicitly remove the permission
- else
- PermissionSet.addSystemPermission(permissionsRemoved, type);
-
- };
-
- /**
- * Notifies the controller that a change has been made to the given
- * system permission for the user being edited.
- *
- * @param {String} type
- * The system permission that was changed, as defined by
- * PermissionSet.SystemPermissionType.
- */
- $scope.systemPermissionChanged = function systemPermissionChanged(type) {
-
- // Determine current permission setting
- var granted = $scope.permissionFlags.systemPermissions[type];
-
- // Add/remove permission depending on flag state
- if (granted)
- addSystemPermission(type);
- else
- removeSystemPermission(type);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the addition of the given user permission.
- *
- * @param {String} type
- * The user permission to add, as defined by
- * PermissionSet.ObjectPermissionType.
- *
- * @param {String} identifier
- * The identifier of the user affected by the permission being added.
- */
- var addUserPermission = function addUserPermission(type, identifier) {
-
- // If permission was previously removed, simply un-remove it
- if (PermissionSet.hasUserPermission(permissionsRemoved, type, identifier))
- PermissionSet.removeUserPermission(permissionsRemoved, type, identifier);
-
- // Otherwise, explicitly add the permission
- else
- PermissionSet.addUserPermission(permissionsAdded, type, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the removal of the given user permission.
- *
- * @param {String} type
- * The user permission to remove, as defined by
- * PermissionSet.ObjectPermissionType.
- *
- * @param {String} identifier
- * The identifier of the user affected by the permission being removed.
- */
- var removeUserPermission = function removeUserPermission(type, identifier) {
-
- // If permission was previously added, simply un-add it
- if (PermissionSet.hasUserPermission(permissionsAdded, type, identifier))
- PermissionSet.removeUserPermission(permissionsAdded, type, identifier);
-
- // Otherwise, explicitly remove the permission
- else
- PermissionSet.addUserPermission(permissionsRemoved, type, identifier);
-
- };
-
- /**
- * Notifies the controller that a change has been made to the given user
- * permission for the user being edited.
- *
- * @param {String} type
- * The user permission that was changed, as defined by
- * PermissionSet.ObjectPermissionType.
- *
- * @param {String} identifier
- * The identifier of the user affected by the changed permission.
- */
- $scope.userPermissionChanged = function userPermissionChanged(type, identifier) {
-
- // Determine current permission setting
- var granted = $scope.permissionFlags.userPermissions[type][identifier];
-
- // Add/remove permission depending on flag state
- if (granted)
- addUserPermission(type, identifier);
- else
- removeUserPermission(type, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the addition of the given connection permission.
- *
- * @param {String} identifier
- * The identifier of the connection to add READ permission for.
- */
- var addConnectionPermission = function addConnectionPermission(identifier) {
-
- // If permission was previously removed, simply un-remove it
- if (PermissionSet.hasConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly add the permission
- else
- PermissionSet.addConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the removal of the given connection permission.
- *
- * @param {String} identifier
- * The identifier of the connection to remove READ permission for.
- */
- var removeConnectionPermission = function removeConnectionPermission(identifier) {
-
- // If permission was previously added, simply un-add it
- if (PermissionSet.hasConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly remove the permission
- else
- PermissionSet.addConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the addition of the given connection group permission.
- *
- * @param {String} identifier
- * The identifier of the connection group to add READ permission for.
- */
- var addConnectionGroupPermission = function addConnectionGroupPermission(identifier) {
-
- // If permission was previously removed, simply un-remove it
- if (PermissionSet.hasConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly add the permission
- else
- PermissionSet.addConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the removal of the given connection permission.
- *
- * @param {String} identifier
- * The identifier of the connection to remove READ permission for.
- */
- var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) {
-
- // If permission was previously added, simply un-add it
- if (PermissionSet.hasConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly remove the permission
- else
- PermissionSet.addConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the addition of the given sharing profile permission.
- *
- * @param {String} identifier
- * The identifier of the sharing profile to add READ permission for.
- */
- var addSharingProfilePermission = function addSharingProfilePermission(identifier) {
-
- // If permission was previously removed, simply un-remove it
- if (PermissionSet.hasSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly add the permission
- else
- PermissionSet.addSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
- /**
- * Updates the permissionsAdded and permissionsRemoved permission sets to
- * reflect the removal of the given sharing profile permission.
- *
- * @param {String} identifier
- * The identifier of the sharing profile to remove READ permission for.
- */
- var removeSharingProfilePermission = function removeSharingProfilePermission(identifier) {
-
- // If permission was previously added, simply un-add it
- if (PermissionSet.hasSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
- PermissionSet.removeSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
- // Otherwise, explicitly remove the permission
- else
- PermissionSet.addSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
- };
-
-
- // Expose permission query and modification functions to group list template
- $scope.groupListContext = {
-
- /**
- * Returns the PermissionFlagSet that contains the current state of
- * granted permissions.
- *
- * @returns {PermissionFlagSet}
- * The PermissionFlagSet describing the current state of granted
- * permissions for the user being edited.
- */
- getPermissionFlags : function getPermissionFlags() {
- return $scope.permissionFlags;
- },
-
- /**
- * Notifies the controller that a change has been made to the given
- * connection permission for the user being edited. This only applies
- * to READ permissions.
- *
- * @param {String} identifier
- * The identifier of the connection affected by the changed
- * permission.
- */
- connectionPermissionChanged : function connectionPermissionChanged(identifier) {
-
- // Determine current permission setting
- var granted = $scope.permissionFlags.connectionPermissions.READ[identifier];
-
- // Add/remove permission depending on flag state
- if (granted)
- addConnectionPermission(identifier);
- else
- removeConnectionPermission(identifier);
-
- },
-
- /**
- * Notifies the controller that a change has been made to the given
- * connection group permission for the user being edited. This only
- * applies to READ permissions.
- *
- * @param {String} identifier
- * The identifier of the connection group affected by the changed
- * permission.
- */
- connectionGroupPermissionChanged : function connectionGroupPermissionChanged(identifier) {
-
- // Determine current permission setting
- var granted = $scope.permissionFlags.connectionGroupPermissions.READ[identifier];
-
- // Add/remove permission depending on flag state
- if (granted)
- addConnectionGroupPermission(identifier);
- else
- removeConnectionGroupPermission(identifier);
-
- },
-
- /**
- * Notifies the controller that a change has been made to the given
- * sharing profile permission for the user being edited. This only
- * applies to READ permissions.
- *
- * @param {String} identifier
- * The identifier of the sharing profile affected by the changed
- * permission.
- */
- sharingProfilePermissionChanged : function sharingProfilePermissionChanged(identifier) {
-
- // Determine current permission setting
- var granted = $scope.permissionFlags.sharingProfilePermissions.READ[identifier];
-
- // Add/remove permission depending on flag state
- if (granted)
- addSharingProfilePermission(identifier);
- else
- removeSharingProfilePermission(identifier);
-
- }
-
- };
-
- /**
- * Cancels all pending edits, returning to the management page.
- */
- $scope.cancel = function cancel() {
+ $scope.returnToUserList = function returnToUserList() {
$location.url('/settings/users');
};
@@ -1090,118 +366,75 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
* which is prepopulated with the data from the user currently being edited.
*/
$scope.cloneUser = function cloneUser() {
- $location.path('/manage/' + encodeURIComponent(selectedDataSource) + '/users').search('clone', username);
+ $location.path('/manage/' + encodeURIComponent($scope.dataSource) + '/users').search('clone', username);
};
/**
- * Saves the user, updating the existing user only.
+ * Saves the current user, creating a new user or updating the existing
+ * user 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.saveUser = function saveUser() {
// Verify passwords match
if ($scope.passwordMatch !== $scope.user.password) {
- guacNotification.showStatus({
- 'className' : 'error',
- 'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
- 'text' : {
+ return $q.reject(new Error({
+ translatableMessage : {
key : 'MANAGE_USER.ERROR_PASSWORD_MISMATCH'
- },
- 'actions' : [ ACKNOWLEDGE_ACTION ]
- });
- return;
+ }
+ }));
}
// Save or create the user, depending on whether the user exists
var saveUserPromise;
- if ($scope.userExists(selectedDataSource))
- saveUserPromise = userService.saveUser(selectedDataSource, $scope.user);
+ if ($scope.dataSource in $scope.users)
+ saveUserPromise = userService.saveUser($scope.dataSource, $scope.user);
else
- saveUserPromise = userService.createUser(selectedDataSource, $scope.user);
+ saveUserPromise = userService.createUser($scope.dataSource, $scope.user);
- saveUserPromise.then(function savedUser() {
+ return saveUserPromise.then(function savedUser() {
// Move permission flags if username differs from marker
if ($scope.selfUsername !== $scope.user.username) {
// Rename added permission
- if (permissionsAdded.userPermissions[$scope.selfUsername]) {
- permissionsAdded.userPermissions[$scope.user.username] = permissionsAdded.userPermissions[$scope.selfUsername];
- delete permissionsAdded.userPermissions[$scope.selfUsername];
+ if ($scope.permissionsAdded.userPermissions[$scope.selfUsername]) {
+ $scope.permissionsAdded.userPermissions[$scope.user.username] = $scope.permissionsAdded.userPermissions[$scope.selfUsername];
+ delete $scope.permissionsAdded.userPermissions[$scope.selfUsername];
}
// Rename removed permission
- if (permissionsRemoved.userPermissions[$scope.selfUsername]) {
- permissionsRemoved.userPermissions[$scope.user.username] = permissionsRemoved.userPermissions[$scope.selfUsername];
- delete permissionsRemoved.userPermissions[$scope.selfUsername];
+ if ($scope.permissionsRemoved.userPermissions[$scope.selfUsername]) {
+ $scope.permissionsRemoved.userPermissions[$scope.user.username] = $scope.permissionsRemoved.userPermissions[$scope.selfUsername];
+ delete $scope.permissionsRemoved.userPermissions[$scope.selfUsername];
}
}
// Upon success, save any changed permissions
- permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved)
+ return permissionService.patchPermissions($scope.dataSource, $scope.user.username, $scope.permissionsAdded, $scope.permissionsRemoved)
.then(function patchedUserPermissions() {
$location.url('/settings/users');
- }, guacNotification.SHOW_REQUEST_ERROR);
+ });
- }, guacNotification.SHOW_REQUEST_ERROR);
+ });
};
/**
- * An action to be provided along with the object sent to showStatus which
- * immediately deletes the current user.
- */
- var DELETE_ACTION = {
- name : "MANAGE_USER.ACTION_DELETE",
- className : "danger",
- // Handle action
- callback : function deleteCallback() {
- deleteUserImmediately();
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * An action to be provided along with the object sent to showStatus which
- * closes the currently-shown status dialog.
- */
- var CANCEL_ACTION = {
- name : "MANAGE_USER.ACTION_CANCEL",
- // Handle action
- callback : function cancelCallback() {
- guacNotification.showStatus(false);
- }
- };
-
- /**
- * Immediately deletes the current user, without prompting the user for
- * confirmation.
- */
- var deleteUserImmediately = function deleteUserImmediately() {
-
- // Delete the user
- userService.deleteUser(selectedDataSource, $scope.user)
- .then(function deletedUser() {
- $location.path('/settings/users');
- }, guacNotification.SHOW_REQUEST_ERROR);
-
- };
-
- /**
- * Deletes the user, prompting the user first to confirm that deletion is
- * desired.
+ * Deletes the current user, 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.deleteUser = function deleteUser() {
-
- // Confirm deletion request
- guacNotification.showStatus({
- 'title' : 'MANAGE_USER.DIALOG_HEADER_CONFIRM_DELETE',
- 'text' : {
- key : 'MANAGE_USER.TEXT_CONFIRM_DELETE'
- },
- 'actions' : [ DELETE_ACTION, CANCEL_ACTION]
- });
-
+ return userService.deleteUser($scope.dataSource, $scope.user);
};
}]);
diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
new file mode 100644
index 000000000..43e80c2db
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
@@ -0,0 +1,407 @@
+/*
+ * 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 the connection permissions granted within a
+ * given {@link PermissionFlagSet}, tracking the specific permissions added or
+ * removed within a separate pair of {@link PermissionSet} objects.
+ */
+angular.module('manage').directive('connectionPermissionEditor', ['$injector',
+ function connectionPermissionEditor($injector) {
+
+ // Required types
+ var ConnectionGroup = $injector.get('ConnectionGroup');
+ var GroupListItem = $injector.get('GroupListItem');
+ var PermissionSet = $injector.get('PermissionSet');
+
+ // Required services
+ var connectionGroupService = $injector.get('connectionGroupService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var requestService = $injector.get('requestService');
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The unique identifier of the data source associated with the
+ * permissions being manipulated.
+ *
+ * @type String
+ */
+ dataSource : '=',
+
+ /**
+ * The current state of the permissions being manipulated. This
+ * {@link PemissionFlagSet} will be modified as changes are made
+ * through this permission editor.
+ *
+ * @type PermissionFlagSet
+ */
+ permissionFlags : '=',
+
+ /**
+ * The set of permissions that have been added, relative to the
+ * initial state of the permissions being manipulated.
+ *
+ * @type PermissionSet
+ */
+ permissionsAdded : '=',
+
+ /**
+ * The set of permissions that have been removed, relative to the
+ * initial state of the permissions being manipulated.
+ *
+ * @type PermissionSet
+ */
+ permissionsRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/connectionPermissionEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function connectionPermissionEditorController($scope) {
+
+ /**
+ * Array of all connection properties that are filterable.
+ *
+ * @type String[]
+ */
+ $scope.filteredConnectionProperties = [
+ 'name',
+ 'protocol'
+ ];
+
+ /**
+ * Array of all connection group properties that are filterable.
+ *
+ * @type String[]
+ */
+ $scope.filteredConnectionGroupProperties = [
+ 'name'
+ ];
+
+ /**
+ * A map of data source identifiers to the root connection groups within
+ * thost data sources. As only one data source is applicable to any
+ * particular permission set being edited/created, this will only
+ * contain a single key.
+ *
+ * @type Object.
+ */
+ $scope.rootGroups = null;
+
+ // Retrieve all connections for which we have ADMINISTER permission
+ dataSourceService.apply(
+ connectionGroupService.getConnectionGroupTree,
+ [$scope.dataSource],
+ ConnectionGroup.ROOT_IDENTIFIER,
+ [PermissionSet.ObjectPermissionType.ADMINISTER]
+ )
+ .then(function connectionGroupReceived(rootGroups) {
+
+ // Convert all received ConnectionGroup objects into GroupListItems
+ $scope.rootGroups = {};
+ angular.forEach(rootGroups, function addGroupListItem(rootGroup, dataSource) {
+ $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup);
+ });
+
+ }, requestService.WARN);
+
+ /**
+ * Expands all items within the tree descending from the given
+ * GroupListItem which have at least one descendant for which explicit
+ * READ permission is granted. The expanded state of all other items is
+ * left untouched.
+ *
+ * @param {GroupListItem} item
+ * The GroupListItem which should be conditionally expanded
+ * depending on whether READ permission is granted for any of its
+ * descendants.
+ *
+ * @param {PemissionFlagSet} flags
+ * The set of permissions which should be used to determine whether
+ * the given item and its descendants are expanded.
+ */
+ var expandReadable = function expandReadable(item, flags) {
+
+ // If the current item is expandable and has defined children,
+ // determine whether it should be expanded
+ if (item.expandable && item.children) {
+ angular.forEach(item.children, function expandReadableChild(child) {
+
+ // Determine whether the permission set contains READ
+ // permission for the current child object
+ var readable = false;
+ switch (child.type) {
+
+ case GroupListItem.Type.CONNECTION:
+ readable = flags.connectionPermissions.READ[child.identifier];
+ break;
+
+ case GroupListItem.Type.CONNECTION_GROUP:
+ readable = flags.connectionGroupPermissions.READ[child.identifier];
+ break;
+
+ case GroupListItem.Type.SHARING_PROFILE:
+ readable = flags.sharingProfilePermissions.READ[child.identifier];
+ break;
+
+ }
+
+ // The parent should be expanded by default if the child is
+ // expanded by default OR the permission set contains READ
+ // permission on the child
+ item.expanded |= expandReadable(child, flags) || readable;
+
+ });
+ }
+
+ return item.expanded;
+
+ };
+
+ // Update default expanded state whenever connection groups and
+ // associated permissions change
+ $scope.$watchGroup(['rootGroups', 'permissionFlags'], function updateDefaultExpandedStates() {
+
+ if (!$scope.rootGroups || !$scope.permissionFlags)
+ return;
+
+ angular.forEach($scope.rootGroups, function updateExpandedStates(rootGroup) {
+
+ // Automatically expand all objects with any descendants for
+ // which the permission set contains READ permission
+ expandReadable(rootGroup, $scope.permissionFlags);
+
+ });
+
+ });
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the addition of the given connection permission.
+ *
+ * @param {String} identifier
+ * The identifier of the connection to add READ permission for.
+ */
+ var addConnectionPermission = function addConnectionPermission(identifier) {
+
+ // If permission was previously removed, simply un-remove it
+ if (PermissionSet.hasConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly add the permission
+ else
+ PermissionSet.addConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the removal of the given connection permission.
+ *
+ * @param {String} identifier
+ * The identifier of the connection to remove READ permission for.
+ */
+ var removeConnectionPermission = function removeConnectionPermission(identifier) {
+
+ // If permission was previously added, simply un-add it
+ if (PermissionSet.hasConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly remove the permission
+ else
+ PermissionSet.addConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the addition of the given connection group permission.
+ *
+ * @param {String} identifier
+ * The identifier of the connection group to add READ permission
+ * for.
+ */
+ var addConnectionGroupPermission = function addConnectionGroupPermission(identifier) {
+
+ // If permission was previously removed, simply un-remove it
+ if (PermissionSet.hasConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly add the permission
+ else
+ PermissionSet.addConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the removal of the given connection group permission.
+ *
+ * @param {String} identifier
+ * The identifier of the connection group to remove READ permission
+ * for.
+ */
+ var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) {
+
+ // If permission was previously added, simply un-add it
+ if (PermissionSet.hasConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly remove the permission
+ else
+ PermissionSet.addConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the addition of the given sharing profile permission.
+ *
+ * @param {String} identifier
+ * The identifier of the sharing profile to add READ permission for.
+ */
+ var addSharingProfilePermission = function addSharingProfilePermission(identifier) {
+
+ // If permission was previously removed, simply un-remove it
+ if (PermissionSet.hasSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly add the permission
+ else
+ PermissionSet.addSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the removal of the given sharing profile permission.
+ *
+ * @param {String} identifier
+ * The identifier of the sharing profile to remove READ permission
+ * for.
+ */
+ var removeSharingProfilePermission = function removeSharingProfilePermission(identifier) {
+
+ // If permission was previously added, simply un-add it
+ if (PermissionSet.hasSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+ PermissionSet.removeSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ // Otherwise, explicitly remove the permission
+ else
+ PermissionSet.addSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+ };
+
+ // Expose permission query and modification functions to group list template
+ $scope.groupListContext = {
+
+ /**
+ * Returns the PermissionFlagSet that contains the current state of
+ * granted permissions.
+ *
+ * @returns {PermissionFlagSet}
+ * The PermissionFlagSet describing the current state of granted
+ * permissions for the permission set being edited.
+ */
+ getPermissionFlags : function getPermissionFlags() {
+ return $scope.permissionFlags;
+ },
+
+ /**
+ * Notifies the controller that a change has been made to the given
+ * connection permission for the permission set being edited. This
+ * only applies to READ permissions.
+ *
+ * @param {String} identifier
+ * The identifier of the connection affected by the changed
+ * permission.
+ */
+ connectionPermissionChanged : function connectionPermissionChanged(identifier) {
+
+ // Determine current permission setting
+ var granted = $scope.permissionFlags.connectionPermissions.READ[identifier];
+
+ // Add/remove permission depending on flag state
+ if (granted)
+ addConnectionPermission(identifier);
+ else
+ removeConnectionPermission(identifier);
+
+ },
+
+ /**
+ * Notifies the controller that a change has been made to the given
+ * connection group permission for the permission set being edited.
+ * This only applies to READ permissions.
+ *
+ * @param {String} identifier
+ * The identifier of the connection group affected by the
+ * changed permission.
+ */
+ connectionGroupPermissionChanged : function connectionGroupPermissionChanged(identifier) {
+
+ // Determine current permission setting
+ var granted = $scope.permissionFlags.connectionGroupPermissions.READ[identifier];
+
+ // Add/remove permission depending on flag state
+ if (granted)
+ addConnectionGroupPermission(identifier);
+ else
+ removeConnectionGroupPermission(identifier);
+
+ },
+
+ /**
+ * Notifies the controller that a change has been made to the given
+ * sharing profile permission for the permission set being edited.
+ * This only applies to READ permissions.
+ *
+ * @param {String} identifier
+ * The identifier of the sharing profile affected by the changed
+ * permission.
+ */
+ sharingProfilePermissionChanged : function sharingProfilePermissionChanged(identifier) {
+
+ // Determine current permission setting
+ var granted = $scope.permissionFlags.sharingProfilePermissions.READ[identifier];
+
+ // Add/remove permission depending on flag state
+ if (granted)
+ addSharingProfilePermission(identifier);
+ else
+ removeSharingProfilePermission(identifier);
+
+ }
+
+ };
+
+ }];
+
+ return directive;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js b/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js
new file mode 100644
index 000000000..cf7068f27
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+/**
+ * Directive which displays a set of tabs pointing to the same object within
+ * different data sources, such as user accounts which span multiple data
+ * sources.
+ */
+angular.module('manage').directive('dataSourceTabs', ['$injector',
+ function dataSourceTabs($injector) {
+
+ // Required types
+ var PageDefinition = $injector.get('PageDefinition');
+
+ // Required services
+ var translationStringService = $injector.get('translationStringService');
+
+ var directive = {
+
+ restrict : 'E',
+ replace : true,
+ templateUrl : 'app/manage/templates/dataSourceTabs.html',
+
+ scope : {
+
+ /**
+ * The permissions which dictate the management actions available
+ * to the current user.
+ *
+ * @type Object.
+ */
+ permissions : '=',
+
+ /**
+ * A function which returns the URL of the object within a given
+ * data source. The relevant data source will be made available to
+ * the Angular expression defining this function as the
+ * "dataSource" variable. No other values will be made available,
+ * including values from the scope.
+ *
+ * @type Function
+ */
+ url : '&'
+
+ }
+
+ };
+
+ directive.controller = ['$scope', function dataSourceTabsController($scope) {
+
+ /**
+ * The set of pages which each manage the same object within different
+ * data sources.
+ *
+ * @type PageDefinition[]
+ */
+ $scope.pages = null;
+
+ // Keep pages synchronized with permissions
+ $scope.$watch('permissions', function permissionsChanged(permissions) {
+
+ $scope.pages = [];
+ angular.forEach(permissions, function addDataSourcePage(managementPermissions, dataSource) {
+
+ // Determine whether data source contains this object
+ var exists = !!managementPermissions.identifier;
+
+ // Data source is not relevant if the associated object does not
+ // exist and cannot be created
+ var readOnly = !managementPermissions.canSaveObject;
+ if (!exists && readOnly)
+ return;
+
+ // Determine class name based on read-only / linked status
+ var className;
+ if (readOnly) className = 'read-only';
+ else if (exists) className = 'linked';
+ else className = 'unlinked';
+
+ // Add page entry
+ $scope.pages.push(new PageDefinition({
+ name : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
+ url : $scope.url({ dataSource : dataSource }),
+ className : className
+ }));
+
+ });
+
+ });
+
+ }];
+
+ return directive;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
new file mode 100644
index 000000000..f44515aa6
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+/**
+ * Directive which displays a set of object management buttons (save, delete,
+ * clone, etc.) representing the actions available to the current user in
+ * context of the object being edited/created.
+ */
+angular.module('manage').directive('managementButtons', ['$injector',
+ function managementButtons($injector) {
+
+ // Required services
+ var guacNotification = $injector.get('guacNotification');
+
+ var directive = {
+
+ restrict : 'E',
+ replace : true,
+ templateUrl : 'app/manage/templates/managementButtons.html',
+
+ scope : {
+
+ /**
+ * The translation namespace associated with all applicable
+ * translation strings. This directive requires at least the
+ * following translation strings within the given namespace:
+ *
+ * - ACTION_CANCEL
+ * - ACTION_CLONE
+ * - ACTION_DELETE
+ * - ACTION_SAVE
+ * - DIALOG_HEADER_CONFIRM_DELETE
+ * - TEXT_CONFIRM_DELETE
+ *
+ * @type String
+ */
+ namespace : '@',
+
+ /**
+ * The permissions which dictate the management actions available
+ * to the current user.
+ *
+ * @type ManagementPermissions
+ */
+ permissions : '=',
+
+ /**
+ * The function to invoke to save the arbitrary object being edited
+ * if the current user has permission to do so. The provided
+ * function MUST return a promise which is resolved if the save
+ * operation succeeds and is rejected with an {@link Error} if the
+ * save operation fails.
+ *
+ * @type Function
+ */
+ save : '&',
+
+ /**
+ * The function to invoke when the current user chooses to clone
+ * the object being edited. The provided function MUST perform the
+ * actions necessary to produce an interface which will clone the
+ * object.
+ *
+ * @type Function
+ */
+ clone : '&',
+
+ /**
+ * The function to invoke to delete the arbitrary object being edited
+ * if the current user has permission to do so. The provided
+ * function MUST return a promise which is resolved if the delete
+ * operation succeeds and is rejected with an {@link Error} if the
+ * delete operation fails.
+ *
+ * @type Function
+ */
+ delete : '&',
+
+ /**
+ * The function to invoke when the current user chooses to cancel
+ * the edit in progress, or when a save/delete operation has
+ * succeeded. The provided function MUST perform the actions
+ * necessary to return the user to a reasonable starting point.
+ *
+ * @type Function
+ */
+ return : '&'
+
+ }
+
+ };
+
+ directive.controller = ['$scope', function managementButtonsController($scope) {
+
+ /**
+ * An action to be provided along with the object sent to showStatus which
+ * immediately deletes the current connection.
+ */
+ var DELETE_ACTION = {
+ name : $scope.namespace + '.ACTION_DELETE',
+ className : 'danger',
+ callback : function deleteCallback() {
+ deleteObjectImmediately();
+ guacNotification.showStatus(false);
+ }
+ };
+
+ /**
+ * An action to be provided along with the object sent to showStatus which
+ * closes the currently-shown status dialog.
+ */
+ var CANCEL_ACTION = {
+ name : $scope.namespace + '.ACTION_CANCEL',
+ callback : function cancelCallback() {
+ guacNotification.showStatus(false);
+ }
+ };
+
+ /**
+ * Invokes the provided return function to navigate the user back to
+ * the page they started from.
+ */
+ var navigateBack = function navigateBack() {
+ $scope['return']($scope.$parent);
+ };
+
+ /**
+ * Invokes the provided delete function, immediately deleting the
+ * current object without prompting the user for confirmation. If
+ * deletion is successful, the user is navigated back to the page they
+ * started from. If the deletion fails, an error notification is
+ * displayed.
+ */
+ var deleteObjectImmediately = function deleteObjectImmediately() {
+ $scope['delete']($scope.$parent).then(navigateBack, guacNotification.SHOW_REQUEST_ERROR);
+ };
+
+ /**
+ * Cancels all pending edits, returning to the page the user started
+ * from.
+ */
+ $scope.cancel = navigateBack;
+
+ /**
+ * Cancels all pending edits, invoking the provided clone function to
+ * open an edit page for a new object which is prepopulated with the
+ * data from the current object.
+ */
+ $scope.cloneObject = function cloneObject () {
+ $scope.clone($scope.$parent);
+ };
+
+ /**
+ * Invokes the provided save function to save the current object. If
+ * saving is successful, the user is navigated back to the page they
+ * started from. If saving fails, an error notification is displayed.
+ */
+ $scope.saveObject = function saveObject() {
+ $scope.save($scope.$parent).then(navigateBack, guacNotification.SHOW_REQUEST_ERROR);
+ };
+
+ /**
+ * Deletes the current object, prompting the user first to confirm that
+ * deletion is desired. If the user confirms that deletion is desired,
+ * the object is deleted through invoking the provided delete function.
+ * The user is automatically navigated back to the page they started
+ * from or given an error notification depending on whether deletion
+ * succeeds.
+ */
+ $scope.deleteObject = function deleteObject() {
+
+ // Confirm deletion request
+ guacNotification.showStatus({
+ title : $scope.namespace + '.DIALOG_HEADER_CONFIRM_DELETE',
+ text : { key : $scope.namespace + '.TEXT_CONFIRM_DELETE' },
+ actions : [ DELETE_ACTION, CANCEL_ACTION]
+ });
+
+ };
+
+ }];
+
+ return directive;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js
new file mode 100644
index 000000000..ec4187256
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js
@@ -0,0 +1,308 @@
+/*
+ * 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 the system permissions granted within a given
+ * {@link PermissionFlagSet}, tracking the specific permissions added or
+ * removed within a separate pair of {@link PermissionSet} objects. Optionally,
+ * the permission for a particular user to update themselves (change their own
+ * password/attributes) may also be manipulated.
+ */
+angular.module('manage').directive('systemPermissionEditor', ['$injector',
+ function systemPermissionEditor($injector) {
+
+ // Required services
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+
+ // Required types
+ var PermissionSet = $injector.get('PermissionSet');
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The unique identifier of the data source associated with the
+ * permissions being manipulated.
+ *
+ * @type String
+ */
+ dataSource : '=',
+
+ /**
+ * The username of the user whose self-update permission (whether
+ * the user has permission to update their own user account) should
+ * be additionally controlled by this editor. If no such user
+ * permissions should be controlled, this should be left undefined.
+ *
+ * @type String
+ */
+ username : '=',
+
+ /**
+ * The current state of the permissions being manipulated. This
+ * {@link PemissionFlagSet} will be modified as changes are made
+ * through this permission editor.
+ *
+ * @type PermissionFlagSet
+ */
+ permissionFlags : '=',
+
+ /**
+ * The set of permissions that have been added, relative to the
+ * initial state of the permissions being manipulated.
+ *
+ * @type PermissionSet
+ */
+ permissionsAdded : '=',
+
+ /**
+ * The set of permissions that have been removed, relative to the
+ * initial state of the permissions being manipulated.
+ *
+ * @type PermissionSet
+ */
+ permissionsRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/systemPermissionEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function systemPermissionEditorController($scope) {
+
+ /**
+ * 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();
+
+ /**
+ * Available system permission types, as translation string / internal
+ * value pairs.
+ *
+ * @type Object[]
+ */
+ $scope.systemPermissionTypes = [
+ {
+ label: "MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM",
+ value: PermissionSet.SystemPermissionType.ADMINISTER
+ },
+ {
+ label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
+ value: PermissionSet.SystemPermissionType.CREATE_USER
+ },
+ {
+ label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
+ value: PermissionSet.SystemPermissionType.CREATE_CONNECTION
+ },
+ {
+ label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS",
+ value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP
+ },
+ {
+ label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES",
+ value: PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE
+ }
+ ];
+
+ // Query the permissions granted to the currently-authenticated user
+ dataSourceService.apply(
+ permissionService.getEffectivePermissions,
+ dataSources,
+ currentUsername
+ )
+ .then(function permissionsReceived(permissions) {
+ $scope.permissions = permissions;
+ }, requestService.WARN);
+
+ /**
+ * Returns whether the current user has permission to change the system
+ * permissions granted to users.
+ *
+ * @returns {Boolean}
+ * true if the current user can grant or revoke system permissions
+ * to the permission set being edited, false otherwise.
+ */
+ $scope.canChangeSystemPermissions = function canChangeSystemPermissions() {
+
+ // Do not check if permissions are not yet loaded
+ if (!$scope.permissions)
+ return false;
+
+ // Only the administrator can modify system permissions
+ return PermissionSet.hasSystemPermission($scope.permissions[$scope.dataSource],
+ PermissionSet.SystemPermissionType.ADMINISTER);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the addition of the given system permission.
+ *
+ * @param {String} type
+ * The system permission to add, as defined by
+ * PermissionSet.SystemPermissionType.
+ */
+ var addSystemPermission = function addSystemPermission(type) {
+
+ // If permission was previously removed, simply un-remove it
+ if (PermissionSet.hasSystemPermission($scope.permissionsRemoved, type))
+ PermissionSet.removeSystemPermission($scope.permissionsRemoved, type);
+
+ // Otherwise, explicitly add the permission
+ else
+ PermissionSet.addSystemPermission($scope.permissionsAdded, type);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the removal of the given system permission.
+ *
+ * @param {String} type
+ * The system permission to remove, as defined by
+ * PermissionSet.SystemPermissionType.
+ */
+ var removeSystemPermission = function removeSystemPermission(type) {
+
+ // If permission was previously added, simply un-add it
+ if (PermissionSet.hasSystemPermission($scope.permissionsAdded, type))
+ PermissionSet.removeSystemPermission($scope.permissionsAdded, type);
+
+ // Otherwise, explicitly remove the permission
+ else
+ PermissionSet.addSystemPermission($scope.permissionsRemoved, type);
+
+ };
+
+ /**
+ * Notifies the controller that a change has been made to the given
+ * system permission for the permission set being edited.
+ *
+ * @param {String} type
+ * The system permission that was changed, as defined by
+ * PermissionSet.SystemPermissionType.
+ */
+ $scope.systemPermissionChanged = function systemPermissionChanged(type) {
+
+ // Determine current permission setting
+ var granted = $scope.permissionFlags.systemPermissions[type];
+
+ // Add/remove permission depending on flag state
+ if (granted)
+ addSystemPermission(type);
+ else
+ removeSystemPermission(type);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the addition of the given user permission.
+ *
+ * @param {String} type
+ * The user permission to add, as defined by
+ * PermissionSet.ObjectPermissionType.
+ *
+ * @param {String} identifier
+ * The identifier of the user affected by the permission being added.
+ */
+ var addUserPermission = function addUserPermission(type, identifier) {
+
+ // If permission was previously removed, simply un-remove it
+ if (PermissionSet.hasUserPermission($scope.permissionsRemoved, type, identifier))
+ PermissionSet.removeUserPermission($scope.permissionsRemoved, type, identifier);
+
+ // Otherwise, explicitly add the permission
+ else
+ PermissionSet.addUserPermission($scope.permissionsAdded, type, identifier);
+
+ };
+
+ /**
+ * Updates the permissionsAdded and permissionsRemoved permission sets
+ * to reflect the removal of the given user permission.
+ *
+ * @param {String} type
+ * The user permission to remove, as defined by
+ * PermissionSet.ObjectPermissionType.
+ *
+ * @param {String} identifier
+ * The identifier of the user affected by the permission being
+ * removed.
+ */
+ var removeUserPermission = function removeUserPermission(type, identifier) {
+
+ // If permission was previously added, simply un-add it
+ if (PermissionSet.hasUserPermission($scope.permissionsAdded, type, identifier))
+ PermissionSet.removeUserPermission($scope.permissionsAdded, type, identifier);
+
+ // Otherwise, explicitly remove the permission
+ else
+ PermissionSet.addUserPermission($scope.permissionsRemoved, type, identifier);
+
+ };
+
+ /**
+ * Notifies the controller that a change has been made to the given user
+ * permission for the permission set being edited.
+ *
+ * @param {String} type
+ * The user permission that was changed, as defined by
+ * PermissionSet.ObjectPermissionType.
+ *
+ * @param {String} identifier
+ * The identifier of the user affected by the changed permission.
+ */
+ $scope.userPermissionChanged = function userPermissionChanged(type, identifier) {
+
+ // Determine current permission setting
+ var granted = $scope.permissionFlags.userPermissions[type][identifier];
+
+ // Add/remove permission depending on flag state
+ if (granted)
+ addUserPermission(type, identifier);
+ else
+ removeUserPermission(type, identifier);
+
+ };
+
+ }];
+
+ return directive;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html
new file mode 100644
index 000000000..61d380408
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html b/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html
new file mode 100644
index 000000000..a8a08431b
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
index ed103351d..17a1108c6 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
@@ -41,7 +41,7 @@
+ model="connection.attributes" model-only="!managementPermissions.canChangeAllAttributes">
@@ -53,12 +53,13 @@
-
+
+
{{'MANAGE_CONNECTION.SECTION_HEADER_HISTORY' | translate}}
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
index d4c6613a7..0f121803e 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
@@ -41,14 +41,16 @@
+ model="connectionGroup.attributes" model-only="!managementPermissions.canChangeAllAttributes">
-
+
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
index d6c704332..d242e591d 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
@@ -22,7 +22,7 @@
+ model="sharingProfile.attributes" model-only="!managementPermissions.canChangeAllAttributes">
@@ -34,11 +34,12 @@
-
+
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 55b6d3098..660554b2a 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -6,17 +6,18 @@
{{'MANAGE_USER.SECTION_HEADER_EDIT_USER' | translate}}
-
-
-
+
+
-
+
{{'MANAGE_USER.INFO_READ_ONLY' | translate}}
-
+
@@ -40,61 +41,37 @@
-
+
+ model="user.attributes"
+ model-only="!managementPermissions[dataSource].canChangeAllAttributes">
-
+
+
-
+
+
-
+
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/managementButtons.html b/guacamole/src/main/webapp/app/manage/templates/managementButtons.html
new file mode 100644
index 000000000..d43209a45
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/managementButtons.html
@@ -0,0 +1,6 @@
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html b/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html
new file mode 100644
index 000000000..47fec6630
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
new file mode 100644
index 000000000..5d02a76f0
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
@@ -0,0 +1,182 @@
+/*
+ * 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 ManagementPermissions class.
+ */
+angular.module('manage').factory('ManagementPermissions', ['$injector',
+ function defineManagementPermissions($injector) {
+
+ // Required types
+ var PermissionSet = $injector.get('PermissionSet');
+
+ /**
+ * Higher-level representation of the management-related permissions
+ * available to the current user on a particular, arbitrary object.
+ *
+ * @constructor
+ * @param {ManagementPermissions|Object} template
+ * An object whose properties should be copied into the new
+ * ManagementPermissions object.
+ */
+ var ManagementPermissions = function ManagementPermissions(template) {
+
+ /**
+ * The identifier of the associated object, or null if the object does
+ * not yet exist.
+ *
+ * @type String
+ */
+ this.identifier = template.identifier || null;
+
+ /**
+ * Whether the user can save the associated object. This could be
+ * updating an existing object, or creating a new object.
+ *
+ * @type Boolean
+ */
+ this.canSaveObject = template.canSaveObject;
+
+ /**
+ * Whether the user can clone the associated object.
+ *
+ * @type Boolean
+ */
+ this.canCloneObject = template.canCloneObject;
+
+ /**
+ * Whether the user can delete the associated object.
+ *
+ * @type Boolean
+ */
+ this.canDeleteObject = template.canDeleteObject;
+
+ /**
+ * Whether the user can change attributes which are currently
+ * associated with the object.
+ *
+ * @type Boolean
+ */
+ this.canChangeAttributes = template.canChangeAttributes;
+
+ /**
+ * Whether the user can change absolutely all attributes associated
+ * with the object, including those which are not already present.
+ *
+ * @type Boolean
+ */
+ this.canChangeAllAttributes = template.canChangeAllAttributes;
+
+ /**
+ * Whether the user can change permissions which are assigned to the
+ * associated object, if the object is capable of being assigned
+ * permissions.
+ *
+ * @type Boolean
+ */
+ this.canChangePermissions = template.canChangePermissions;
+
+ };
+
+ /**
+ * Creates a new {@link ManagementPermissions} which defines the high-level
+ * actions the current user may take for the given object.
+ *
+ * @param {PermissionSet} permissions
+ * The effective permissions granted to the current user within the
+ * data source associated with the object being managed.
+ *
+ * @param {String} createPermission
+ * The system permission required to create objects of the same type as
+ * the object being managed, as defined by
+ * {@link PermissionSet.SystemPermissionTypes}.
+ *
+ * @param {Function} hasObjectPermission
+ * The function to invoke to test whether a {@link PermissionSet}
+ * contains a particular object permission. The parameters accepted
+ * by this function must be identical to those accepted by
+ * {@link PermissionSet.hasUserPermission()},
+ * {@link PermissionSet.hasConnectionPermission()}, etc.
+ *
+ * @param {String} [identifier]
+ * The identifier of the object being managed. If the object does not
+ * yet exist, this parameter should be omitted or set to null.
+ *
+ * @returns {ManagementPermissions}
+ * A new {@link ManagementPermissions} which defines the high-level
+ * actions the current user may take for the given object.
+ */
+ ManagementPermissions.fromPermissionSet = function fromPermissionSet(
+ permissions, createPermission, hasObjectPermission, identifier) {
+
+ var isAdmin = PermissionSet.hasSystemPermission(permissions,
+ PermissionSet.SystemPermissionType.ADMINISTER);
+
+ var canCreate = PermissionSet.hasSystemPermission(permissions, createPermission);
+ var canAdminister = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER, identifier);
+ var canUpdate = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
+ var canDelete = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier);
+
+ var exists = !!identifier;
+
+ return new ManagementPermissions({
+
+ identifier : identifier,
+
+ // A user can save (create or update) an object if they are a
+ // system-level administrator, OR the object does not yet exist and
+ // the user has explicit permission to create such objects, OR the
+ // object does already exist and the user has explicit UPDATE
+ // permission on the object
+ canSaveObject : isAdmin || (!exists && canCreate) || canUpdate,
+
+ // A user can clone an object only if the object exists, and
+ // only if they are a system-level administrator OR they have
+ // explicit permission to create such objects
+ canCloneObject : exists && (isAdmin || canCreate),
+
+ // A user can delete an object only if the object exists, and
+ // only if they are a system-level administrator OR they have
+ // explicit DELETE permission on the object
+ canDeleteObject : exists && (isAdmin || canDelete),
+
+ // Attributes in general (with or without existing values) can only
+ // be changed if the object is being created, OR the user is a
+ // system-level administrator, OR the user has explicit UPDATE
+ // permission on the object
+ canChangeAttributes : !exists || isAdmin || canUpdate,
+
+ // A user can change the attributes of an object which are not
+ // explicitly defined on that object when the object is being
+ // created
+ canChangeAllAttributes : !exists,
+
+ // A user can change the system permissions related to an object
+ // if they are a system-level admin, OR they are creating the
+ // object, OR they have explicit ADMINISTER permission on the
+ // existing object
+ canChangePermissions : isAdmin || !exists || canAdminister
+
+ });
+
+ };
+
+ return ManagementPermissions;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/rest/restModule.js b/guacamole/src/main/webapp/app/rest/restModule.js
index f409e9545..23901c07c 100644
--- a/guacamole/src/main/webapp/app/rest/restModule.js
+++ b/guacamole/src/main/webapp/app/rest/restModule.js
@@ -22,5 +22,6 @@
* Guacamole web application.
*/
angular.module('rest', [
- 'auth'
+ 'auth',
+ 'locale'
]);
diff --git a/guacamole/src/main/webapp/app/rest/types/Protocol.js b/guacamole/src/main/webapp/app/rest/types/Protocol.js
index cfb26f02d..0b86d5b2d 100644
--- a/guacamole/src/main/webapp/app/rest/types/Protocol.js
+++ b/guacamole/src/main/webapp/app/rest/types/Protocol.js
@@ -20,8 +20,11 @@
/**
* Service which defines the Protocol class.
*/
-angular.module('rest').factory('Protocol', [function defineProtocol() {
-
+angular.module('rest').factory('Protocol', ['$injector', function defineProtocol($injector) {
+
+ // Required services
+ var translationStringService = $injector.get('translationStringService');
+
/**
* The object returned by REST API calls when representing the data
* associated with a supported remote desktop protocol.
@@ -64,6 +67,53 @@ angular.module('rest').factory('Protocol', [function defineProtocol() {
};
+ /**
+ * Returns the translation string namespace for the protocol having the
+ * given name. The namespace will be of the form:
+ *
+ * PROTOCOL_NAME
+ *
+ * where NAME is the protocol name transformed via
+ * translationStringService.canonicalize().
+ *
+ * @param {String} protocolName
+ * The name of the protocol.
+ *
+ * @returns {String}
+ * The translation namespace for the protocol specified, or null if no
+ * namespace could be generated.
+ */
+ Protocol.getNamespace = function getNamespace(protocolName) {
+
+ // Do not generate a namespace if no protocol is selected
+ if (!protocolName)
+ return null;
+
+ return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
+
+ };
+
+ /**
+ * Given the internal name of a protocol, produces the translation string
+ * for the localized version of that protocol's name. The translation
+ * string will be of the form:
+ *
+ * NAMESPACE.NAME
+ *
+ * where NAMESPACE is the namespace generated from
+ * $scope.getNamespace().
+ *
+ * @param {String} protocolName
+ * The name of the protocol.
+ *
+ * @returns {String}
+ * The translation string which produces the localized name of the
+ * protocol specified.
+ */
+ Protocol.getName = function getProtocolName(protocolName) {
+ return Protocol.getNamespace(protocolName) + '.NAME';
+ };
+
return Protocol;
}]);
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json
index a97406eb2..ca8acb8e1 100644
--- a/guacamole/src/main/webapp/translations/en.json
+++ b/guacamole/src/main/webapp/translations/en.json
@@ -234,6 +234,7 @@
"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",