diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/DirectoryObjectService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/DirectoryObjectService.java index fc2bdb331..5a67b24b6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/DirectoryObjectService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/DirectoryObjectService.java @@ -37,8 +37,8 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; /** * Service which provides convenience methods for creating, retrieving, and - * manipulating users. This service will automatically enforce the - * permissions of the current user. + * manipulating objects within directories. This service will automatically + * enforce the permissions of the current user. * * @author Michael Jumper * @param @@ -215,52 +215,77 @@ public abstract class DirectoryObjectService implicitPermissions = + new ArrayList(IMPLICIT_OBJECT_PERMISSIONS.length); - // Create object - getObjectMapper().insert(model); + UserModel userModel = user.getUser().getModel(); + for (ObjectPermission.Type permission : IMPLICIT_OBJECT_PERMISSIONS) { - // Build list of implicit permissions - Collection implicitPermissions = - new ArrayList(IMPLICIT_OBJECT_PERMISSIONS.length); + // Create model which grants this permission to the current user + ObjectPermissionModel permissionModel = new ObjectPermissionModel(); + permissionModel.setUserID(userModel.getObjectID()); + permissionModel.setUsername(userModel.getIdentifier()); + permissionModel.setType(permission); + permissionModel.setObjectIdentifier(model.getIdentifier()); - UserModel userModel = user.getUser().getModel(); - for (ObjectPermission.Type permission : IMPLICIT_OBJECT_PERMISSIONS) { - - // Create model which grants this permission to the current user - ObjectPermissionModel permissionModel = new ObjectPermissionModel(); - permissionModel.setUserID(userModel.getObjectID()); - permissionModel.setUsername(userModel.getIdentifier()); - permissionModel.setType(permission); - permissionModel.setObjectIdentifier(model.getIdentifier()); - - // Add permission - implicitPermissions.add(permissionModel); - - } - - // Add implicit permissions - getPermissionMapper().insert(implicitPermissions); - - return getObjectInstance(user, model); + // Add permission + implicitPermissions.add(permissionModel); + } - // User lacks permission to create - throw new GuacamoleSecurityException("Permission denied."); + // Add implicit permissions + getPermissionMapper().insert(implicitPermissions); + + return getObjectInstance(user, model); } @@ -416,14 +433,10 @@ public abstract class DirectoryObjectService + * The specific internal implementation of the type of object this service + * provides access to. + * + * @param + * The external interface or implementation of the type of object this + * service provides access to, as defined by the guacamole-ext API. + * + * @param + * The underlying model object used to represent InternalType in the + * database. + */ +public abstract class GroupedDirectoryObjectService, + ExternalType, ModelType extends GroupedObjectModel> + extends DirectoryObjectService { + + /** + * Returns the set of parent connection groups that are modified by the + * given model object (by virtue of the object changing parent groups). If + * the model is not changing parents, the resulting collection will be + * empty. + * + * @param user + * The user making the given changes to the model. + * + * @param identifier + * The identifier of the object that has been modified, if it exists. + * If the object is being created, this will be null. + * + * @param model + * The model that has been modified, if any. If the object is being + * deleted, this will be null. + * + * @return + * A collection of the identifiers of all parent connection groups + * that will be affected (updated) by the change. + * + * @throws GuacamoleException + * If an error occurs while determining which parent connection groups + * are affected. + */ + protected Collection getModifiedGroups(AuthenticatedUser user, + String identifier, ModelType model) throws GuacamoleException { + + // Get old parent identifier + String oldParentIdentifier = null; + if (identifier != null) { + ModelType current = retrieveObject(user, identifier).getModel(); + oldParentIdentifier = current.getParentIdentifier(); + } + + // Get new parent identifier + String parentIdentifier = null; + if (model != null) { + + parentIdentifier = model.getParentIdentifier(); + + // If both parents have the same identifier, nothing has changed + if (parentIdentifier != null && parentIdentifier.equals(oldParentIdentifier)) + return Collections.EMPTY_LIST; + + } + + // Return collection of all non-root groups involved + Collection groups = new ArrayList(2); + if (oldParentIdentifier != null) groups.add(oldParentIdentifier); + if (parentIdentifier != null) groups.add(parentIdentifier); + return groups; + + } + + /** + * Returns whether the given user has permission to modify the parent + * connection groups affected by the modifications made to the given model + * object. + * + * @param user + * The user who changed the model object. + * + * @param identifier + * The identifier of the object that has been modified, if it exists. + * If the object is being created, this will be null. + * + * @param model + * The model that has been modified, if any. If the object is being + * deleted, this will be null. + * + * @return + * true if the user has update permission for all modified groups, + * false otherwise. + * + * @throws GuacamoleException + * If an error occurs while determining which parent connection groups + * are affected. + */ + protected boolean canUpdateModifiedGroups(AuthenticatedUser user, + String identifier, ModelType model) throws GuacamoleException { + + // If user is an administrator, no need to check + if (user.getUser().isAdministrator()) + return true; + + // Verify that we have permission to modify any modified groups + Collection modifiedGroups = getModifiedGroups(user, identifier, model); + if (!modifiedGroups.isEmpty()) { + + ObjectPermissionSet permissionSet = user.getUser().getConnectionGroupPermissions(); + Collection updateableGroups = permissionSet.getAccessibleObjects( + Collections.singleton(ObjectPermission.Type.UPDATE), + modifiedGroups + ); + + return updateableGroups.size() == modifiedGroups.size(); + + } + + return true; + + } + + @Override + protected void beforeCreate(AuthenticatedUser user, + ModelType model) throws GuacamoleException { + + super.beforeCreate(user, model); + + // Validate that we can update all applicable parent groups + if (!canUpdateModifiedGroups(user, null, model)) + throw new GuacamoleSecurityException("Permission denied."); + + } + + @Override + protected void beforeUpdate(AuthenticatedUser user, + ModelType model) throws GuacamoleException { + + super.beforeUpdate(user, model); + + // Validate that we can update all applicable parent groups + if (!canUpdateModifiedGroups(user, model.getIdentifier(), model)) + throw new GuacamoleSecurityException("Permission denied."); + + } + + @Override + protected void beforeDelete(AuthenticatedUser user, + String identifier) throws GuacamoleException { + + super.beforeDelete(user, identifier); + + // Validate that we can update all applicable parent groups + if (!canUpdateModifiedGroups(user, identifier, null)) + throw new GuacamoleSecurityException("Permission denied."); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionService.java index 9bf566770..8ac5d5da7 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionService.java @@ -33,11 +33,11 @@ import java.util.Map; import java.util.Set; import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectMapper; -import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectService; import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService; import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.glyptodon.guacamole.auth.jdbc.base.GroupedDirectoryObjectService; import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionPermissionMapper; import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper; import org.glyptodon.guacamole.net.GuacamoleSocket; @@ -55,7 +55,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; * * @author Michael Jumper, James Muehlner */ -public class ConnectionService extends DirectoryObjectService { +public class ConnectionService extends GroupedDirectoryObjectService { /** * Mapper for accessing connections. @@ -148,9 +148,11 @@ public class ConnectionService extends DirectoryObjectService { /** @@ -130,9 +130,11 @@ public class ConnectionGroupService extends DirectoryObjectService