GUACAMOLE-292: Merge attribute editing permissioning.

This commit is contained in:
James Muehlner
2017-05-26 22:44:36 -07:00
13 changed files with 251 additions and 23 deletions

View File

@@ -97,9 +97,12 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
*
* @return
* An object which is backed by the given model object.
*
* @throws GuacamoleException
* If the object instance cannot be created.
*/
protected abstract InternalType getObjectInstance(ModeledAuthenticatedUser currentUser,
ModelType model);
ModelType model) throws GuacamoleException;
/**
* Returns an instance of a model object which is based on the given
@@ -113,9 +116,12 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
*
* @return
* A model object which is based on the given object.
*
* @throws GuacamoleException
* If the model object instance cannot be created.
*/
protected abstract ModelType getModelInstance(ModeledAuthenticatedUser currentUser,
ExternalType object);
ExternalType object) throws GuacamoleException;
/**
* Returns whether the given user has permission to create the type of
@@ -199,9 +205,12 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
* @return
* A collection of objects which are backed by the models in the given
* collection.
*
* @throws GuacamoleException
* If any of the object instances cannot be created.
*/
protected Collection<InternalType> getObjectInstances(ModeledAuthenticatedUser currentUser,
Collection<ModelType> models) {
Collection<ModelType> models) throws GuacamoleException {
// Create new collection of objects by manually converting each model
Collection<InternalType> objects = new ArrayList<InternalType>(models.size());

View File

@@ -176,6 +176,34 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
@Inject
private UserPermissionService userPermissionService;
/**
* Whether attributes which control access restrictions should be exposed
* via getAttributes() or allowed to be set via setAttributes().
*/
private boolean exposeRestrictedAttributes = false;
/**
* Initializes this ModeledUser, associating it with the current
* authenticated user and populating it with data from the given user
* model.
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param model
* The backing model object.
*
* @param exposeRestrictedAttributes
* Whether attributes which control access restrictions should be
* exposed via getAttributes() or allowed to be set via
* setAttributes().
*/
public void init(ModeledAuthenticatedUser currentUser, UserModel model,
boolean exposeRestrictedAttributes) {
super.init(currentUser, model);
this.exposeRestrictedAttributes = exposeRestrictedAttributes;
}
/**
* The plaintext password previously set by a call to setPassword(), if
* any. The password of a user cannot be retrieved once saved into the
@@ -309,10 +337,16 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
return userPermissionService.getPermissionSet(getCurrentUser(), this);
}
@Override
public Map<String, String> getAttributes() {
Map<String, String> attributes = new HashMap<String, String>();
/**
* Stores all restricted (privileged) attributes within the given Map,
* pulling the values of those attributes from the underlying user model.
* If no value is yet defined for an attribute, that attribute will be set
* to null.
*
* @param attributes
* The Map to store all restricted attributes within.
*/
private void putRestrictedAttributes(Map<String, String> attributes) {
// Set disabled attribute
attributes.put(DISABLED_ATTRIBUTE_NAME, getModel().isDisabled() ? "true" : null);
@@ -335,7 +369,6 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
// Set timezone attribute
attributes.put(TIMEZONE_ATTRIBUTE_NAME, getModel().getTimeZone());
return attributes;
}
/**
@@ -396,8 +429,14 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
}
@Override
public void setAttributes(Map<String, String> attributes) {
/**
* Stores all restricted (privileged) attributes within the underlying user
* model, pulling the values of those attributes from the given Map.
*
* @param attributes
* The Map to pull all restricted attributes from.
*/
private void setRestrictedAttributes(Map<String, String> attributes) {
// Translate disabled attribute
getModel().setDisabled("true".equals(attributes.get(DISABLED_ATTRIBUTE_NAME)));
@@ -438,6 +477,27 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
}
@Override
public Map<String, String> getAttributes() {
Map<String, String> attributes = new HashMap<String, String>();
// Include restricted attributes only if they should be exposed
if (exposeRestrictedAttributes)
putRestrictedAttributes(attributes);
return attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Assign restricted attributes only if they are exposed
if (exposeRestrictedAttributes)
setRestrictedAttributes(attributes);
}
/**
* Returns the time zone associated with this user. This time zone must be
* used when interpreting all date/time restrictions related to this user.

View File

@@ -147,15 +147,35 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
@Override
protected ModeledUser getObjectInstance(ModeledAuthenticatedUser currentUser,
UserModel model) {
UserModel model) throws GuacamoleException {
boolean exposeRestrictedAttributes;
// Expose restricted attributes if the user does not yet exist
if (model.getObjectID() == null)
exposeRestrictedAttributes = true;
// Otherwise, if the user permissions are available, expose restricted
// attributes only if the user has ADMINISTER permission
else if (currentUser != null)
exposeRestrictedAttributes = hasObjectPermission(currentUser,
model.getIdentifier(), ObjectPermission.Type.ADMINISTER);
// If user permissions are not available, do not expose anything
else
exposeRestrictedAttributes = false;
// Produce ModeledUser exposing only those attributes for which the
// current user has permission
ModeledUser user = userProvider.get();
user.init(currentUser, model);
user.init(currentUser, model, exposeRestrictedAttributes);
return user;
}
@Override
protected UserModel getModelInstance(ModeledAuthenticatedUser currentUser,
final User object) {
final User object) throws GuacamoleException {
// Create new ModeledUser backed by blank model
UserModel model = new UserModel();
@@ -362,9 +382,13 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
* @return
* The ModeledUser which corresponds to the given AuthenticatedUser, or
* null if no such user exists.
*
* @throws GuacamoleException
* If a ModeledUser object for the user corresponding to the given
* AuthenticatedUser cannot be created.
*/
public ModeledUser retrieveUser(AuthenticationProvider authenticationProvider,
AuthenticatedUser authenticatedUser) {
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// If we already queried this user, return that rather than querying again
if (authenticatedUser instanceof ModeledAuthenticatedUser)

View File

@@ -55,7 +55,16 @@ angular.module('form').directive('guacForm', [function form() {
*
* @type Object.<String, String>
*/
model : '='
model : '=',
/**
* Whether the contents of the form should be restricted to those
* fields/forms which match properties defined within the given
* model object. By default, all fields will be shown.
*
* @type Boolean
*/
modelOnly : '='
},
templateUrl: 'app/form/templates/form.html',
@@ -163,6 +172,55 @@ angular.module('form').directive('guacForm', [function form() {
});
/**
* Returns whether the given field should be displayed to the
* current user.
*
* @param {Field} field
* The field to check.
*
* @returns {Boolean}
* true if the given field should be visible, false otherwise.
*/
$scope.isVisible = function isVisible(field) {
// All fields are visible if contents are not restricted to
// model properties only
if (!$scope.modelOnly)
return true;
// Otherwise, fields are only visible if they are present
// within the model
return field && (field.name in $scope.values);
};
/**
* Returns whether at least one of the given fields should be
* displayed to the current user.
*
* @param {Field[]} fields
* The array of fields to check.
*
* @returns {Boolean}
* true if at least one field within the given array should be
* visible, false otherwise.
*/
$scope.containsVisible = function containsVisible(fields) {
// If fields are defined, check whether at least one is visible
if (fields) {
for (var i = 0; i < fields.length; i++) {
if ($scope.isVisible(fields[i]))
return true;
}
}
// Otherwise, there are no visible fields
return false;
};
}] // end controller
};

View File

@@ -1,5 +1,6 @@
<div class="form-group">
<div ng-repeat="form in forms" class="form">
<div ng-repeat="form in forms" class="form"
ng-show="containsVisible(form.fields)">
<!-- Form name -->
<h3 ng-show="form.name">{{getSectionHeader(form) | translate}}</h3>
@@ -7,6 +8,7 @@
<!-- All fields in form -->
<div class="fields">
<guac-form-field ng-repeat="field in form.fields" namespace="namespace"
ng-if="isVisible(field)"
field="field" model="values[field.name]"></guac-form-field>
</div>

View File

@@ -299,6 +299,23 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
$scope.parameters = {};
}
/**
* Returns whether the current user can change/set all connection
* attributes for the connection being edited, regardless of whether those
* attributes are already explicitly associated with that connection.
*
* @returns {Boolean}
* true if the current user can change all attributes for the
* connection being edited, regardless of whether those attributes are
* already explicitly associated with that connection, false otherwise.
*/
$scope.canChangeAllAttributes = function canChangeAllAttributes() {
// All attributes can be set if we are creating the connection
return !identifier;
};
/**
* Returns the translation string namespace for the protocol having the
* given name. The namespace will be of the form:

View File

@@ -196,6 +196,25 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
}
];
/**
* 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;
};
/**
* Cancels all pending edits, returning to the management page.
*/

View File

@@ -280,6 +280,25 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
});
/**
* 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;
};
/**
* Returns the translation string namespace for the protocol having the
* given name. The namespace will be of the form:

View File

@@ -225,8 +225,8 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
};
/**
* Returns whether the current user can change attributes associated with
* the user being edited within the given data source.
* 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
@@ -260,6 +260,23 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
};
/**
* 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

View File

@@ -40,7 +40,8 @@
<!-- Connection attributes section -->
<div class="attributes">
<guac-form namespace="'CONNECTION_ATTRIBUTES'" content="attributes" model="connection.attributes"></guac-form>
<guac-form namespace="'CONNECTION_ATTRIBUTES'" content="attributes"
model="connection.attributes" model-only="!canChangeAllAttributes()"></guac-form>
</div>
<!-- Connection parameters -->

View File

@@ -40,7 +40,8 @@
<!-- Connection group attributes section -->
<div class="attributes">
<guac-form namespace="'CONNECTION_GROUP_ATTRIBUTES'" content="attributes" model="connectionGroup.attributes"></guac-form>
<guac-form namespace="'CONNECTION_GROUP_ATTRIBUTES'" content="attributes"
model="connectionGroup.attributes" model-only="!canChangeAllAttributes()"></guac-form>
</div>
<!-- Form action buttons -->

View File

@@ -22,7 +22,7 @@
<!-- Sharing profile attributes section -->
<div class="attributes">
<guac-form namespace="'SHARING_PROFILE_ATTRIBUTES'" content="attributes"
model="sharingProfile.attributes"></guac-form>
model="sharingProfile.attributes" model-only="!canChangeAllAttributes()"></guac-form>
</div>
<!-- Sharing profile parameters -->

View File

@@ -41,7 +41,8 @@
<!-- User attributes section -->
<div class="attributes" ng-show="canChangeAttributes()">
<guac-form namespace="'USER_ATTRIBUTES'" content="attributes" model="user.attributes"></guac-form>
<guac-form namespace="'USER_ATTRIBUTES'" content="attributes"
model="user.attributes" model-only="!canChangeAllAttributes()"></guac-form>
</div>
<!-- System permissions section -->