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 type of model object that corresponds to this object. + */ +public abstract class GroupedDirectoryObject + extends DirectoryObject { + + /** + * Returns the identifier of the parent connection group, which cannot be + * null. If the parent is the root connection group, this will be + * RootConnectionGroup.IDENTIFIER. + * + * @return + * The identifier of the parent connection group. + */ + public String getParentIdentifier() { + + // Translate null parent to proper identifier + String parentIdentifier = getModel().getParentIdentifier(); + if (parentIdentifier == null) + return RootConnectionGroup.IDENTIFIER; + + return parentIdentifier; + + } + + /** + * Sets the identifier of the associated parent connection group. If the + * parent is the root connection group, this should be + * RootConnectionGroup.IDENTIFIER. + * + * @param parentIdentifier + * The identifier of the connection group to associate as this object's + * parent. + */ + public void setParentIdentifier(String parentIdentifier) { + + // Translate root identifier back into null + if (parentIdentifier != null + && parentIdentifier.equals(RootConnectionGroup.IDENTIFIER)) + parentIdentifier = null; + + getModel().setParentIdentifier(parentIdentifier); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/GroupedDirectoryObjectService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/GroupedDirectoryObjectService.java new file mode 100644 index 000000000..4fb169f35 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/GroupedDirectoryObjectService.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.jdbc.base; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; + +/** + * Service which provides convenience methods for creating, retrieving, and + * manipulating objects that can be within connection groups. This service will + * automatically enforce the permissions of the current user. + * + * @author Michael Jumper + * @param + * 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/base/GroupedObjectModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/GroupedObjectModel.java new file mode 100644 index 000000000..45b25598a --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/base/GroupedObjectModel.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.jdbc.base; + +/** + * Object representation of a Guacamole object, such as a user or connection, + * as represented in the database. + * + * @author Michael Jumper + */ +public abstract class GroupedObjectModel extends ObjectModel { + + /** + * The unique identifier which identifies the parent of this object. + */ + private String parentIdentifier; + + /** + * Creates a new, empty object. + */ + public GroupedObjectModel() { + } + + /** + * Returns the identifier of the parent connection group, or null if the + * parent connection group is the root connection group. + * + * @return + * The identifier of the parent connection group, or null if the parent + * connection group is the root connection group. + */ + public String getParentIdentifier() { + return parentIdentifier; + } + + /** + * Sets the identifier of the parent connection group. + * + * @param parentIdentifier + * The identifier of the parent connection group, or null if the parent + * connection group is the root connection group. + */ + public void setParentIdentifier(String parentIdentifier) { + this.parentIdentifier = parentIdentifier; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java index b4ef2e907..5b3552fe9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java @@ -22,7 +22,7 @@ package org.glyptodon.guacamole.auth.jdbc.connection; -import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel; +import org.glyptodon.guacamole.auth.jdbc.base.GroupedObjectModel; /** * Object representation of a Guacamole connection, as represented in the @@ -30,14 +30,8 @@ import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel; * * @author Michael Jumper */ -public class ConnectionModel extends ObjectModel { +public class ConnectionModel extends GroupedObjectModel { - /** - * The identifier of the parent connection group in the database, or null - * if the parent connection group is the root group. - */ - private String parentIdentifier; - /** * The human-readable name associated with this connection. */ @@ -95,29 +89,6 @@ public class ConnectionModel extends ObjectModel { this.protocol = protocol; } - /** - * Returns the identifier of the parent connection group, or null if the - * parent connection group is the root connection group. - * - * @return - * The identifier of the parent connection group, or null if the parent - * connection group is the root connection group. - */ - public String getParentIdentifier() { - return parentIdentifier; - } - - /** - * Sets the identifier of the parent connection group. - * - * @param parentIdentifier - * The identifier of the parent connection group, or null if the parent - * connection group is the root connection group. - */ - public void setParentIdentifier(String parentIdentifier) { - this.parentIdentifier = parentIdentifier; - } - @Override public String getIdentifier() { 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 +public class ModeledConnection extends GroupedDirectoryObject implements Connection { /** @@ -67,7 +66,7 @@ public class ModeledConnection extends DirectoryObject * The manually-set GuacamoleConfiguration, if any. */ private GuacamoleConfiguration config = null; - + /** * Creates a new, empty ModeledConnection. */ @@ -84,30 +83,6 @@ public class ModeledConnection extends DirectoryObject getModel().setName(name); } - @Override - public String getParentIdentifier() { - - // Translate null parent to proper identifier - String parentIdentifier = getModel().getParentIdentifier(); - if (parentIdentifier == null) - return RootConnectionGroup.IDENTIFIER; - - return parentIdentifier; - - } - - @Override - public void setParentIdentifier(String parentIdentifier) { - - // Translate root identifier back into null - if (parentIdentifier != null - && parentIdentifier.equals(RootConnectionGroup.IDENTIFIER)) - parentIdentifier = null; - - getModel().setParentIdentifier(parentIdentifier); - - } - @Override public GuacamoleConfiguration getConfiguration() { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java index 68845df9c..def543ffc 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java @@ -22,7 +22,7 @@ package org.glyptodon.guacamole.auth.jdbc.connectiongroup; -import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel; +import org.glyptodon.guacamole.auth.jdbc.base.GroupedObjectModel; import org.glyptodon.guacamole.net.auth.ConnectionGroup; /** @@ -31,14 +31,8 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup; * * @author Michael Jumper */ -public class ConnectionGroupModel extends ObjectModel { +public class ConnectionGroupModel extends GroupedObjectModel { - /** - * The identifier of the parent connection group in the database, or null - * if the parent connection group is the root group. - */ - private String parentIdentifier; - /** * The human-readable name associated with this connection group. */ @@ -75,29 +69,6 @@ public class ConnectionGroupModel extends ObjectModel { this.name = name; } - /** - * Returns the identifier of the parent connection group, or null if the - * parent connection group is the root connection group. - * - * @return - * The identifier of the parent connection group, or null if the parent - * connection group is the root connection group. - */ - public String getParentIdentifier() { - return parentIdentifier; - } - - /** - * Sets the identifier of the parent connection group. - * - * @param parentIdentifier - * The identifier of the parent connection group, or null if the parent - * connection group is the root connection group. - */ - public void setParentIdentifier(String parentIdentifier) { - this.parentIdentifier = parentIdentifier; - } - /** * Returns the type of this connection group, such as organizational or * balancing. diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java index cfed2edd0..aea294221 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java @@ -27,11 +27,11 @@ import com.google.inject.Provider; 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.ConnectionGroupPermissionMapper; import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper; import org.glyptodon.guacamole.net.GuacamoleSocket; @@ -48,7 +48,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; * * @author Michael Jumper, James Muehlner */ -public class ConnectionGroupService extends DirectoryObjectService { /** @@ -130,9 +130,11 @@ public class ConnectionGroupService extends DirectoryObjectService +public class ModeledConnectionGroup extends GroupedDirectoryObject implements ConnectionGroup { /** @@ -75,30 +75,6 @@ public class ModeledConnectionGroup extends DirectoryObject