From d5c49ff287d01d0afcebe94f18f5889dee79dfce Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Mon, 29 Jul 2013 23:10:56 -0700 Subject: [PATCH] Ticket #263: Clarified permissioning. --- .../schema/001-create-schema.sql | 3 +- .../schema/001a-update-schema.sql | 4 +- .../net/auth/mysql/MySQLConnectionGroup.java | 35 ++--- .../mysql/service/ConnectionGroupService.java | 87 ++++++++++- .../mysql/service/PermissionCheckService.java | 140 ++++++++++++++++++ .../src/main/resources/generatorConfig.xml | 8 + .../permission/ConnectionGroupPermission.java | 121 +++++++++++++++ 7 files changed, 376 insertions(+), 22 deletions(-) create mode 100644 guacamole-ext/src/main/java/net/sourceforge/guacamole/net/auth/permission/ConnectionGroupPermission.java diff --git a/extensions/guacamole-auth-mysql/schema/001-create-schema.sql b/extensions/guacamole-auth-mysql/schema/001-create-schema.sql index 047c44ed1..ae827b308 100644 --- a/extensions/guacamole-auth-mysql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-mysql/schema/001-create-schema.sql @@ -8,6 +8,8 @@ CREATE TABLE `guacamole_connection_group` ( `connection_group_id` int(11) NOT NULL AUTO_INCREMENT, `parent_group_id` int(11), `connection_group_name` varchar(128) NOT NULL, + `type` enum('ORGANIZATIONAL', + 'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL', PRIMARY KEY (`connection_group_id`), UNIQUE KEY `connection_group_name` (`connection_group_name`), @@ -112,7 +114,6 @@ CREATE TABLE `guacamole_connection_group_permission` ( `user_id` int(11) NOT NULL, `connection_group_id` int(11) NOT NULL, `permission` enum('READ', - 'EXECUTE', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, diff --git a/extensions/guacamole-auth-mysql/schema/001a-update-schema.sql b/extensions/guacamole-auth-mysql/schema/001a-update-schema.sql index 5b72f9dd6..6bef54af3 100644 --- a/extensions/guacamole-auth-mysql/schema/001a-update-schema.sql +++ b/extensions/guacamole-auth-mysql/schema/001a-update-schema.sql @@ -8,6 +8,9 @@ CREATE TABLE `guacamole_connection_group` ( `connection_group_id` int(11) NOT NULL AUTO_INCREMENT, `parent_group_id` int(11), `connection_group_name` varchar(128) NOT NULL, + `type` enum('ORGANIZATIONAL', + 'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL', + PRIMARY KEY (`connection_group_id`), UNIQUE KEY `connection_group_name` (`connection_group_name`), @@ -39,7 +42,6 @@ CREATE TABLE `guacamole_connection_group_permission` ( `user_id` int(11) NOT NULL, `connection_group_id` int(11) NOT NULL, `permission` enum('READ', - 'EXECUTE', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionGroup.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionGroup.java index f4ec4be7c..cef7f514a 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionGroup.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionGroup.java @@ -66,24 +66,18 @@ public class MySQLConnectionGroup extends AbstractConnectionGroup { /** * A Directory of connections that have this connection group as a parent. */ - private Directory connectionDirectory; + private Directory connectionDirectory = null; /** * A Directory of connection groups that have this connection group as a parent. */ - private Directory connectionGroupDirectory; + private Directory connectionGroupDirectory = null; /** * Service managing connection groups. */ @Inject private ConnectionGroupService connectionGroupService; - - /** - * Service for managing connection groups. - */ - //@Inject - //private ConnectionGroupService connectionGroupService; /** * Create a default, empty connection group. @@ -112,35 +106,38 @@ public class MySQLConnectionGroup extends AbstractConnectionGroup { * * @param connectionGroupID The ID of the associated database record, if any. * @param identifier The unique identifier associated with this connection group. - * @param config The GuacamoleConfiguration associated with this connection. - * @param history All ConnectionRecords associated with this connection. * @param userID The IID of the user who queried this connection. */ - public void init(Integer connectionGroupID, String identifier, - Directory connectionDirectory, - Directory connectionGroupDirectory, - int userID) { - + public void init(Integer connectionGroupID, String identifier, int userID) { this.connectionGroupID = connectionGroupID; setIdentifier(identifier); - this.connectionDirectory = connectionDirectory; - this.connectionGroupDirectory = connectionGroupDirectory; this.userID = userID; - } @Override public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { return connectionGroupService.connect(this, info, userID); } - + + private void loadConnectionDirectory() { + + } + @Override public Directory getConnectionDirectory() throws GuacamoleException { + if(connectionDirectory == null) + loadConnectionDirectory(); return connectionDirectory; } + + private void loadConnectionGroupDirectory() { + + } @Override public Directory getConnectionGroupDirectory() throws GuacamoleException { + if(connectionGroupDirectory == null) + loadConnectionGroupDirectory(); return connectionGroupDirectory; } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java index c969d9459..a44ab06e2 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java @@ -37,11 +37,24 @@ package net.sourceforge.guacamole.net.auth.mysql.service; * * ***** END LICENSE BLOCK ***** */ +import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.Provider; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import net.sourceforge.guacamole.net.GuacamoleSocket; import net.sourceforge.guacamole.net.auth.mysql.MySQLConnectionGroup; import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionGroupMapper; +import net.sourceforge.guacamole.net.auth.mysql.model.Connection; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionExample; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroup; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroupExample; import net.sourceforge.guacamole.protocol.GuacamoleClientInformation; /** @@ -76,10 +89,82 @@ public class ConnectionGroupService { @Inject private Provider mysqlConnectionGroupProvider; - public GuacamoleSocket connect(MySQLConnectionGroup aThis, + public GuacamoleSocket connect(MySQLConnectionGroup group, GuacamoleClientInformation info, int userID) { throw new UnsupportedOperationException("Not yet implemented"); } + //public Collection getConnectionGroupConnections() + + + /** + * Retrieves a map of all connection group names for the given IDs. + * + * @param ids The IDs of the connection groups to retrieve the names of. + * @return A map containing the names of all connection groups and their + * corresponding IDs. + */ + public Map retrieveNames(Collection ids) { + + // If no IDs given, just return empty map + if (ids.isEmpty()) + return Collections.EMPTY_MAP; + + // Map of all names onto their corresponding IDs. + Map names = new HashMap(); + + // Get all connection groups having the given IDs + ConnectionGroupExample example = new ConnectionGroupExample(); + example.createCriteria().andConnection_group_idIn(Lists.newArrayList(ids)); + List connectionGroups = connectionGroupDAO.selectByExample(example); + + // Produce set of names + for (ConnectionGroup connectionGroup : connectionGroups) + names.put(connectionGroup.getConnection_group_id(), + connectionGroup.getConnection_group_name()); + + return names; + + } + + /** + * Get the names of all the connection groups defined in the system. + * + * @return A Set of names of all the connection groups defined in the system. + */ + public Set getAllConnectionGroupNames() { + + // Set of all present connection group names + Set names = new HashSet(); + + // Query all connection group names + List connectionGroups = + connectionGroupDAO.selectByExample(new ConnectionGroupExample()); + for (ConnectionGroup connectionGroup : connectionGroups) + names.add(connectionGroup.getConnection_group_name()); + + return names; + + } + + /** + * Get the connection group IDs of all the connection groups defined in the system. + * + * @return A list of connection group IDs of all the connection groups defined in the system. + */ + public List getAllConnectionGroupIDs() { + + // Set of all present connection group IDs + List connectionGroupIDs = new ArrayList(); + + // Query all connection IDs + List connections = + connectionGroupDAO.selectByExample(new ConnectionGroupExample()); + for (ConnectionGroup connection : connections) + connectionGroupIDs.add(connection.getConnection_group_id()); + + return connectionGroupIDs; + + } } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PermissionCheckService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PermissionCheckService.java index ae3a11387..d2a1f023c 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PermissionCheckService.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PermissionCheckService.java @@ -43,15 +43,19 @@ import java.util.Map; import java.util.Set; import net.sourceforge.guacamole.GuacamoleSecurityException; import net.sourceforge.guacamole.net.auth.mysql.MySQLConstants; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionGroupPermissionMapper; import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionPermissionMapper; import net.sourceforge.guacamole.net.auth.mysql.dao.SystemPermissionMapper; import net.sourceforge.guacamole.net.auth.mysql.dao.UserPermissionMapper; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroupPermissionExample; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroupPermissionKey; import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionPermissionExample; import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionPermissionKey; import net.sourceforge.guacamole.net.auth.mysql.model.SystemPermissionExample; import net.sourceforge.guacamole.net.auth.mysql.model.SystemPermissionKey; import net.sourceforge.guacamole.net.auth.mysql.model.UserPermissionExample; import net.sourceforge.guacamole.net.auth.mysql.model.UserPermissionKey; +import net.sourceforge.guacamole.net.auth.permission.ConnectionGroupPermission; import net.sourceforge.guacamole.net.auth.permission.ConnectionPermission; import net.sourceforge.guacamole.net.auth.permission.Permission; import net.sourceforge.guacamole.net.auth.permission.SystemPermission; @@ -75,6 +79,12 @@ public class PermissionCheckService { @Inject private ConnectionService connectionService; + /** + * Service for accessing connection groups. + */ + @Inject + private ConnectionGroupService connectionGroupService; + /** * DAO for accessing permissions related to users. */ @@ -86,6 +96,12 @@ public class PermissionCheckService { */ @Inject private ConnectionPermissionMapper connectionPermissionDAO; + + /** + * DAO for accessing permissions related to connection groups. + */ + @Inject + private ConnectionGroupPermissionMapper connectionGroupPermissionDAO; /** * DAO for accessing permissions related to the system as a whole. @@ -131,6 +147,26 @@ public class PermissionCheckService { throw new GuacamoleSecurityException("Permission denied."); } + + /** + * Verifies that the user has the specified access to the given connection group. + * If permission is denied, a GuacamoleSecurityException is thrown. + * + * @param userID The ID of the user to check. + * @param affectedConnectionGroupID The connection group that would be affected by the + * operation if permission is granted. + * @param permissionType The type of permission to check for. + * @throws GuacamoleSecurityException If the specified permission is not + * granted. + */ + public void verifyConnectionGroupAccess(int userID, int affectedConnectionGroupID, String permissionType) throws GuacamoleSecurityException { + + // If permission does not exist, throw exception + if(!checkConnectionGroupAccess(userID, affectedConnectionGroupID, permissionType)) + throw new GuacamoleSecurityException("Permission denied."); + + } + /** * Verifies that the user has the specified access to the system. If * permission is denied, a GuacamoleSecurityException is thrown. @@ -195,6 +231,29 @@ public class PermissionCheckService { } + /** + * Checks whether a user has the specified type of access to the affected + * connection group. + * + * @param userID The ID of the user to check. + * @param affectedConnectionGroupID The connection group that would be affected by the + * operation if permission is granted. + * @param permissionType The type of permission to check for. + * @return true if the specified permission is granted, false otherwise. + */ + public boolean checkConnectionGroupAccess(int userID, Integer affectedConnectionGroupID, String permissionType) { + + // A system administrator has full access to everything. + if(checkSystemAdministratorAccess(userID)) + return true; + + // Check existence of requested permission + ConnectionGroupPermissionExample example = new ConnectionGroupPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andConnection_group_idEqualTo(affectedConnectionGroupID).andPermissionEqualTo(permissionType); + return connectionGroupPermissionDAO.countByExample(example) > 0; + + } + /** * Checks whether a user has the specified type of access to the system. * @@ -293,6 +352,38 @@ public class PermissionCheckService { } + /** + * Find the list of the IDs of all connection groups a user has permission to. + * The access type is defined by permissionType. + * + * @param userID The ID of the user to check. + * @param permissionType The type of permission to check for. + * @return A list of all connection group IDs this user has the specified access + * to. + */ + public List retrieveConnectionGroupIDs(int userID, + String permissionType) { + + // A system administrator has access to all connections. + if(checkSystemAdministratorAccess(userID)) + return connectionGroupService.getAllConnectionGroupIDs(); + + // Query all connection permissions for the given user and permission type + ConnectionGroupPermissionExample example = new ConnectionGroupPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andPermissionEqualTo(permissionType); + example.setDistinct(true); + List connectionGroupPermissions = + connectionGroupPermissionDAO.selectByExample(example); + + // Convert result into list of IDs + List connectionGroupIDs = new ArrayList(connectionGroupPermissions.size()); + for(ConnectionGroupPermissionKey permission : connectionGroupPermissions) + connectionGroupIDs.add(permission.getConnection_group_id()); + + return connectionGroupIDs; + + } + /** * Retrieve all existing usernames that the given user has permission to * perform the given operation upon. @@ -432,6 +523,52 @@ public class PermissionCheckService { } + /** + * Retrieves all connection group permissions granted to the user having the + * given ID. + * + * @param userID The ID of the user to retrieve permissions of. + * @return A set of all connection group permissions granted to the user having + * the given ID. + */ + public Set retrieveConnectionGroupPermissions(int userID) { + + // Set of all permissions + Set permissions = new HashSet(); + + // Query all connection permissions + ConnectionGroupPermissionExample connectionGroupPermissionExample = new ConnectionGroupPermissionExample(); + connectionGroupPermissionExample.createCriteria().andUser_idEqualTo(userID); + List connectionGroupPermissions = + connectionGroupPermissionDAO.selectByExample(connectionGroupPermissionExample); + + // Get list of affected connection IDs + List connectionGroupIDs = new ArrayList(); + for(ConnectionGroupPermissionKey connectionGroupPermission : connectionGroupPermissions) + connectionGroupIDs.add(connectionGroupPermission.getConnection_group_id()); + + // Get corresponding names + Map affectedConnectionGroups = + connectionGroupService.retrieveNames(connectionGroupIDs); + + // Add connection permissions + for(ConnectionGroupPermissionKey connectionGroupPermission : connectionGroupPermissions) { + + // Construct permission from data + ConnectionGroupPermission permission = new ConnectionGroupPermission( + ConnectionGroupPermission.Type.valueOf(connectionGroupPermission.getPermission()), + affectedConnectionGroups.get(connectionGroupPermission.getConnection_group_id()) + ); + + // Add to set + permissions.add(permission); + + } + + return permissions; + + } + /** * Retrieves all system permissions granted to the user having the * given ID. @@ -487,6 +624,9 @@ public class PermissionCheckService { // Add connection permissions allPermissions.addAll(retrieveConnectionPermissions(userID)); + + // add connection group permissions + allPermissions.addAll(retrieveConnectionGroupPermissions(userID)); // Add system permissions allPermissions.addAll(retrieveSystemPermissions(userID)); diff --git a/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml b/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml index 9ea226a7d..a23260369 100644 --- a/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml +++ b/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml @@ -65,6 +65,14 @@ + + + +
+ { + + /** + * The identifier of the GuacamoleConfiguration associated with the + * operation affected by this permission. + */ + private String identifier; + + /** + * The type of operation affected by this permission. + */ + private ObjectPermission.Type type; + + /** + * Creates a new ConnectionGroupPermission having the given type + * and identifier. The identifier must be the unique identifier assigned + * to the ConnectionGroup by the AuthenticationProvider in use. + * + * @param type The type of operation affected by this permission. + * @param identifier The identifier of the ConnectionGroup associated + * with the operation affected by this permission. + */ + public ConnectionGroupPermission(ObjectPermission.Type type, String identifier) { + + this.identifier = identifier; + this.type = type; + + } + + @Override + public String getObjectIdentifier() { + return identifier; + } + + @Override + public ObjectPermission.Type getType() { + return type; + } + + @Override + public int hashCode() { + int hash = 5; + if (identifier != null) hash = 47 * hash + identifier.hashCode(); + if (type != null) hash = 47 * hash + type.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + + // Not equal if null or wrong type + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + final ConnectionGroupPermission other = + (ConnectionGroupPermission) obj; + + // Not equal if different type + if (this.type != other.type) + return false; + + // If null identifier, equality depends on whether other identifier + // is null + if (identifier == null) + return other.identifier == null; + + // Otherwise, equality depends entirely on identifier + return identifier.equals(other.identifier); + + } + +}