diff --git a/extensions/guacamole-auth-mysql/.gitignore b/extensions/guacamole-auth-mysql/.gitignore new file mode 100644 index 000000000..42f4a1a64 --- /dev/null +++ b/extensions/guacamole-auth-mysql/.gitignore @@ -0,0 +1,2 @@ +target/ +*~ diff --git a/extensions/guacamole-auth-mysql/README b/extensions/guacamole-auth-mysql/README new file mode 100644 index 000000000..5543c124f --- /dev/null +++ b/extensions/guacamole-auth-mysql/README @@ -0,0 +1,171 @@ + +------------------------------------------------------------ + About this README +------------------------------------------------------------ + +This README is intended to provide quick and to-the-point documentation for +technical users intending to compile parts of Guacamole themselves. + +Distribution-specific packages are available from the files section of the main +project page: + + http://sourceforge.net/projects/guacamole/files/ + +Distribution-specific documentation is provided on the Guacamole wiki: + + http://guac-dev.org/ + + +------------------------------------------------------------ + What is guacamole-auth-mysql? +------------------------------------------------------------ + +guacamole-auth-ldap is a Java library for use with the Guacamole web +application to provide MySQL based authentication. + +guacamole-auth-mysql provides an authentication provider which can be +set in guacamole.properties to allow MySQL authentication of Guacamole +users. Additional properties are required to configure the mysql +connection parameters. + +A schema file are provided to create the required tables in your +mysql database. + + +------------------------------------------------------------ + Compiling and installing guacamole-auth-mysql +------------------------------------------------------------ + +guacamole-auth-mysql is built using Maven. Building guacamole-auth-mysql +compiles all classes and packages them into a redistributable .jar file. This +.jar file can be installed in the library directory configured in +guacamole.properties such that the authentication provider is available. + +1) Set up a MySQL database with the Guacamole schema. + + When guacamole-auth-mysql is compiling, it needs to generate source + based on a database schema. Because the source generator uses a + connection to an actual database to do this, you must have a MySQL + database running with the Guacamole schema set up. + + First, create a database. For the sake of these instructions, we will + call the database "guacamole", and will run all scripts as the root user: + + $ mysql -u root -p + Enter password: + mysql> CREATE DATABASE guacamole; + Query OK, 1 row affected (0.00 sec) + + mysql> exit + Bye + + The schema files are in the schema/ subdirectory of the source. If run + in order, they will create the schema and a default user: + + $ cat schema/*.sql | mysql -u root -p guacamole + +2) Set up your ~/.m2/settings.xml + + Once the database is set up, Maven will need to have the credentials + required to connect to it and query the schema. This information is + specified in properties inside your ~/.m2/settings.xml file. If this + file does not exist yet, simply create it. + + For ease of compilation, we've included an example settings.xml + defining the required properties in doc/example/settings.xml. You can + simply copy this file into ~/.m2 and edit as necessary. + + If you wish to write the file yourself, the file should look like this in + general: + + + + ...profiles... + + + + We need to add a profile which defines the required properties by + placing a section like the following within the "profiles" section of your + settings.xml: + + + guacamole-mybatis + + DATABASE + USERNAME + PASSWORD + + + + Obviously, the DATABASE, USERNAME, and PASSWORD placeholders above must + be replaced with the appropriate values for your system. + + Finally, to make the profile available to the build, it must be activated. + Place a section like the following at the bottom of your settings.xml, + right after the profiles section: + + + guacamole-mybatis + + + Maven's documentation has more details on writing the settings.xml file + if you have different needs or the above directions are not clear. + +3) Run mvn package + + $ mvn package + + Maven will download any needed dependencies for building the .jar file. + Once all dependencies have been downloaded, the .jar file will be + created in the target/ subdirectory of the current directory. + + If this process fails, check the build errors, and verify that the + contents of your settings.xml file is correct. + +4) Extract the .tar.gz file now present in the target/ directory, and + place the .jar files in the extracted lib/ subdirectory in the library + directory specified in guacamole.properties. + + You will likely need to do this as root. + + If you do not have a library directory configured in your + guacamole.properties, you will need to specify one. The directory + is specified using the "lib-directory" property. + +5) Set up your MySQL database to authenticate Guacamole users + + A schema file is provided in the schema directory for creating + the guacamole authentication tables in your MySQL database. + + Additionally, a script is provided to create a default admin user + with username 'guacadmin' and password 'guacadmin'. This user can + be used to set up any other connections and users. + +6) Configure guacamole.properties for MySQL + + There are additional properties required by the MySQL JDBC driver + which must be added/changed in your guacamole.properties: + + # Configuration for MySQL connection + mysql-hostname: mysql.host.name + mysql-port: 3306 + mysql-database: guacamole.database.name + mysql-username: user + mysql-password: pass + + Optionally, the authentication provider can be configured + not to allow multiple users to use the same connection + at the same time: + + mysql-disallow-simultaneous-connections: true + + +------------------------------------------------------------ + Reporting problems +------------------------------------------------------------ + +Please report any bugs encountered by opening a new ticket at the Trac system +hosted at: + + http://guac-dev.org/trac/ + diff --git a/extensions/guacamole-auth-mysql/doc/example/settings.xml b/extensions/guacamole-auth-mysql/doc/example/settings.xml new file mode 100644 index 000000000..d0fb6d5bd --- /dev/null +++ b/extensions/guacamole-auth-mysql/doc/example/settings.xml @@ -0,0 +1,21 @@ + + + + + + guacamole-mybatis + + SCHEMA + DATABASE + USER + PASS + + + + + + + guacamole-mybatis + + + diff --git a/extensions/guacamole-auth-mysql/pom.xml b/extensions/guacamole-auth-mysql/pom.xml new file mode 100644 index 000000000..4938d42f4 --- /dev/null +++ b/extensions/guacamole-auth-mysql/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + net.sourceforge.guacamole + guacamole-auth-mysql + jar + 0.8.0 + guacamole-auth-mysql + http://guac-dev.org/ + + + UTF-8 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + maven-assembly-plugin + 2.2-beta-5 + + ${project.artifactId}-${project.version} + false + + src/main/assembly/dist.xml + + + + + make-dist-archive + package + + single + + + + + + + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.3.2 + + + + Generate MyBatis Artifacts + + generate + + + + + + + + mysql + mysql-connector-java + 5.1.23 + + + + + + + + + + + + + net.sourceforge.guacamole + guacamole-common + 0.8.0 + + + + + net.sourceforge.guacamole + guacamole-ext + 0.8.0 + + + + + org.slf4j + slf4j-api + 1.6.1 + + + org.slf4j + slf4j-jcl + 1.6.1 + runtime + + + + + org.mybatis + mybatis + 3.1.1 + + + + + org.mybatis + mybatis-guice + 3.2 + + + + + com.google.collections + google-collections + 1.0 + + + + + + + + guac-dev + http://guac-dev.org/repo + + + + + diff --git a/extensions/guacamole-auth-mysql/schema/001-create-schema.sql b/extensions/guacamole-auth-mysql/schema/001-create-schema.sql new file mode 100644 index 000000000..6351dd6ff --- /dev/null +++ b/extensions/guacamole-auth-mysql/schema/001-create-schema.sql @@ -0,0 +1,153 @@ + +-- +-- Table of connections. Each connection has a name, protocol, and +-- associated set of parameters. +-- + +CREATE TABLE `guacamole_connection` ( + + `connection_id` int(11) NOT NULL AUTO_INCREMENT, + `connection_name` varchar(128) NOT NULL, + `protocol` varchar(32) NOT NULL, + + PRIMARY KEY (`connection_id`), + UNIQUE KEY `connection_name` (`connection_name`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of users. Each user has a unique username and a hashed password +-- with corresponding salt. +-- + +CREATE TABLE `guacamole_user` ( + + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(128) NOT NULL, + `password_hash` binary(32) NOT NULL, + `password_salt` binary(32) NOT NULL, + + PRIMARY KEY (`user_id`), + UNIQUE KEY `username` (`username`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of connection parameters. Each parameter is simply a name/value pair +-- associated with a connection. +-- + +CREATE TABLE `guacamole_connection_parameter` ( + + `connection_id` int(11) NOT NULL, + `parameter_name` varchar(128) NOT NULL, + `parameter_value` varchar(4096) NOT NULL, + + PRIMARY KEY (`connection_id`,`parameter_name`), + + CONSTRAINT `guacamole_connection_parameter_ibfk_1` + FOREIGN KEY (`connection_id`) + REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of connection permissions. Each connection permission grants a user +-- specific access to a connection. +-- + +CREATE TABLE `guacamole_connection_permission` ( + + `user_id` int(11) NOT NULL, + `connection_id` int(11) NOT NULL, + `permission` enum('READ', + 'UPDATE', + 'DELETE', + 'ADMINISTER') NOT NULL, + + PRIMARY KEY (`user_id`,`connection_id`,`permission`), + + CONSTRAINT `guacamole_connection_permission_ibfk_1` + FOREIGN KEY (`connection_id`) + REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE, + + CONSTRAINT `guacamole_connection_permission_ibfk_2` + FOREIGN KEY (`user_id`) + REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of system permissions. Each system permission grants a user a +-- system-level privilege of some kind. +-- + +CREATE TABLE `guacamole_system_permission` ( + + `user_id` int(11) NOT NULL, + `permission` enum('CREATE_CONNECTION', + 'CREATE_USER', + 'ADMINISTER') NOT NULL, + + PRIMARY KEY (`user_id`,`permission`), + + CONSTRAINT `guacamole_system_permission_ibfk_1` + FOREIGN KEY (`user_id`) + REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of user permissions. Each user permission grants a user access to +-- another user (the "affected" user) for a specific type of operation. +-- + +CREATE TABLE `guacamole_user_permission` ( + + `user_id` int(11) NOT NULL, + `affected_user_id` int(11) NOT NULL, + `permission` enum('READ', + 'UPDATE', + 'DELETE', + 'ADMINISTER') NOT NULL, + + PRIMARY KEY (`user_id`,`affected_user_id`,`permission`), + + CONSTRAINT `guacamole_user_permission_ibfk_1` + FOREIGN KEY (`affected_user_id`) + REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE, + + CONSTRAINT `guacamole_user_permission_ibfk_2` + FOREIGN KEY (`user_id`) + REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table of connection history records. Each record defines a specific user's +-- session, including the connection used, the start time, and the end time +-- (if any). +-- + +CREATE TABLE `guacamole_connection_history` ( + + `history_id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `connection_id` int(11) NOT NULL, + `start_date` datetime NOT NULL, + `end_date` datetime DEFAULT NULL, + + PRIMARY KEY (`history_id`), + KEY `user_id` (`user_id`), + KEY `connection_id` (`connection_id`), + + CONSTRAINT `guacamole_connection_history_ibfk_1` + FOREIGN KEY (`user_id`) + REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE, + + CONSTRAINT `guacamole_connection_history_ibfk_2` + FOREIGN KEY (`connection_id`) + REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + diff --git a/extensions/guacamole-auth-mysql/schema/002-create-admin-user.sql b/extensions/guacamole-auth-mysql/schema/002-create-admin-user.sql new file mode 100644 index 000000000..1efed1ccb --- /dev/null +++ b/extensions/guacamole-auth-mysql/schema/002-create-admin-user.sql @@ -0,0 +1,16 @@ + +-- Create default user "guacadmin" with password "guacadmin" +insert into guacamole_user values(1, 'guacadmin', + x'CA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', -- 'guacadmin' + x'FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264'); + +-- Grant this user create permissions +insert into guacamole_system_permission values(1, 'CREATE_CONNECTION'); +insert into guacamole_system_permission values(1, 'CREATE_USER'); +insert into guacamole_system_permission values(1, 'ADMINISTER'); + +-- Grant admin permission to read/update/administer self +insert into guacamole_user_permission values(1, 1, 'READ'); +insert into guacamole_user_permission values(1, 1, 'UPDATE'); +insert into guacamole_user_permission values(1, 1, 'ADMINISTER'); + diff --git a/extensions/guacamole-auth-mysql/src/main/assembly/dist.xml b/extensions/guacamole-auth-mysql/src/main/assembly/dist.xml new file mode 100644 index 000000000..a13bce3e2 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/assembly/dist.xml @@ -0,0 +1,54 @@ + + + dist + ${project.artifactId}-${project.version} + + + + tar.gz + + + + + + + + / + doc + + + + + /schema + schema + + + + + + + + + /lib + runtime + false + true + true + + + + + net.sourceforge.guacamole:guacamole-common + + + net.sourceforge.guacamole:guacamole-ext + + + + + + diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionSet.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionSet.java new file mode 100644 index 000000000..4e5ba4937 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionSet.java @@ -0,0 +1,121 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Inject; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionHistoryMapper; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionHistory; + +/** + * Represents the set of currently active Connections. Whenever a socket is + * opened, the connection ID should be added to this set, and whenever a socket + * is closed, the connection ID should be removed from this set. + * + * @author James Muehlner + */ +public class ActiveConnectionSet { + + /** + * DAO for accessing connection history. + */ + @Inject + private ConnectionHistoryMapper connectionHistoryDAO; + + /** + * Set of all the connections that are currently active. + */ + private Set activeConnectionSet = new HashSet(); + + /** + * Check if a connection is currently in use. + * @param connectionID The connection to check the status of. + * @return true if the connection is currently in use. + */ + public boolean isActive(int connectionID) { + return activeConnectionSet.contains(connectionID); + } + + /** + * Set a connection as open. + * @param connectionID The ID of the connection that is being opened. + * @param userID The ID of the user who is opening the connection. + * @return The ID of the history record created for this open connection. + */ + public int openConnection(int connectionID, int userID) { + + // Create the connection history record + ConnectionHistory connectionHistory = new ConnectionHistory(); + connectionHistory.setConnection_id(connectionID); + connectionHistory.setUser_id(userID); + connectionHistory.setStart_date(new Date()); + connectionHistoryDAO.insert(connectionHistory); + + // Mark the connection as active + activeConnectionSet.add(connectionID); + + return connectionHistory.getHistory_id(); + } + + /** + * Set a connection as closed. + * @param connectionID The ID of the connection that is being opened. + * @param historyID The ID of the history record about the open connection. + * @throws GuacamoleException If the open connection history is not found. + */ + public void closeConnection(int connectionID, int historyID) + throws GuacamoleException { + + // Get the existing history record + ConnectionHistory connectionHistory = + connectionHistoryDAO.selectByPrimaryKey(historyID); + + if(connectionHistory == null) + throw new GuacamoleException("History record not found."); + + // Update the connection history record to mark that it is now closed + connectionHistory.setEnd_date(new Date()); + connectionHistoryDAO.updateByPrimaryKey(connectionHistory); + + // Remove the connection from the set of active connections. + activeConnectionSet.remove(connectionID); + } +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java new file mode 100644 index 000000000..11398ba12 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java @@ -0,0 +1,254 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Inject; +import java.util.Set; +import net.sourceforge.guacamole.GuacamoleClientException; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.auth.Connection; +import net.sourceforge.guacamole.net.auth.Directory; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionParameterMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionPermissionMapper; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameter; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameterExample; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionPermissionKey; +import net.sourceforge.guacamole.net.auth.mysql.service.ConnectionService; +import net.sourceforge.guacamole.net.auth.mysql.service.PermissionCheckService; +import net.sourceforge.guacamole.protocol.GuacamoleConfiguration; +import org.mybatis.guice.transactional.Transactional; + +/** + * A MySQL-based implementation of the connection directory. + * + * @author James Muehlner + */ +public class ConnectionDirectory implements Directory{ + + /** + * The ID of the user who this connection directory belongs to. + * Access is based on his/her permission settings. + */ + private int user_id; + + /** + * Service for checking permissions. + */ + @Inject + private PermissionCheckService permissionCheckService; + + /** + * Service managing connections. + */ + @Inject + private ConnectionService connectionService; + + /** + * Service for manipulating connection permissions in the database. + */ + @Inject + private ConnectionPermissionMapper connectionPermissionDAO; + + /** + * Service for manipulating connection parameters in the database. + */ + @Inject + private ConnectionParameterMapper connectionParameterDAO; + + /** + * Set the user for this directory. + * + * @param user_id The ID of the user owning this connection directory. + */ + public void init(int user_id) { + this.user_id = user_id; + } + + @Transactional + @Override + public Connection get(String identifier) throws GuacamoleException { + + // Get connection + MySQLConnection connection = + connectionService.retrieveConnection(identifier, user_id); + + // Verify access is granted + permissionCheckService.verifyConnectionAccess( + this.user_id, + connection.getConnectionID(), + MySQLConstants.CONNECTION_READ); + + // Return connection + return connection; + + } + + @Transactional + @Override + public Set getIdentifiers() throws GuacamoleException { + return permissionCheckService.retrieveConnectionNames(user_id, + MySQLConstants.CONNECTION_READ); + } + + @Transactional + @Override + public void add(Connection object) throws GuacamoleException { + + String identifier = object.getIdentifier().trim(); + if(identifier.isEmpty()) + throw new GuacamoleClientException("The connection identifier cannot be blank."); + + // Verify permission to create + permissionCheckService.verifySystemAccess(this.user_id, + MySQLConstants.SYSTEM_CONNECTION_CREATE); + + // Verify that no connection already exists with this identifier. + MySQLConnection previousConnection = + connectionService.retrieveConnection(identifier, user_id); + if(previousConnection != null) + throw new GuacamoleClientException("That connection identifier is already in use."); + + // Create connection + MySQLConnection connection = connectionService.createConnection( + identifier, object.getConfiguration().getProtocol(), + user_id); + + // Add connection parameters + createConfigurationValues(connection.getConnectionID(), + object.getConfiguration()); + + // Finally, give the current user full access to the newly created + // connection. + ConnectionPermissionKey newConnectionPermission = new ConnectionPermissionKey(); + newConnectionPermission.setUser_id(this.user_id); + newConnectionPermission.setConnection_id(connection.getConnectionID()); + + // Read permission + newConnectionPermission.setPermission(MySQLConstants.CONNECTION_READ); + connectionPermissionDAO.insert(newConnectionPermission); + + // Update permission + newConnectionPermission.setPermission(MySQLConstants.CONNECTION_UPDATE); + connectionPermissionDAO.insert(newConnectionPermission); + + // Delete permission + newConnectionPermission.setPermission(MySQLConstants.CONNECTION_DELETE); + connectionPermissionDAO.insert(newConnectionPermission); + + // Administer permission + newConnectionPermission.setPermission(MySQLConstants.CONNECTION_ADMINISTER); + connectionPermissionDAO.insert(newConnectionPermission); + + } + + /** + * Inserts all parameter values from the given configuration into the + * database, associating them with the connection having the givenID. + * + * @param connection_id The ID of the connection to associate all + * parameters with. + * @param config The GuacamoleConfiguration to read parameters from. + */ + private void createConfigurationValues(int connection_id, + GuacamoleConfiguration config) { + + // Insert new parameters for each parameter in the config + for (String name : config.getParameterNames()) { + + // Create a ConnectionParameter based on the current parameter + ConnectionParameter parameter = new ConnectionParameter(); + parameter.setConnection_id(connection_id); + parameter.setParameter_name(name); + parameter.setParameter_value(config.getParameter(name)); + + // Insert connection parameter + connectionParameterDAO.insert(parameter); + + } + + } + + @Transactional + @Override + public void update(Connection object) throws GuacamoleException { + + // If connection not actually from this auth provider, we can't handle + // the update + if (!(object instanceof MySQLConnection)) + throw new GuacamoleException("Connection not from database."); + + MySQLConnection mySQLConnection = (MySQLConnection) object; + + // Verify permission to update + permissionCheckService.verifyConnectionAccess(this.user_id, + mySQLConnection.getConnectionID(), + MySQLConstants.CONNECTION_UPDATE); + + // Perform update + connectionService.updateConnection(mySQLConnection); + + // Delete old connection parameters + ConnectionParameterExample parameterExample = new ConnectionParameterExample(); + parameterExample.createCriteria().andConnection_idEqualTo(mySQLConnection.getConnectionID()); + connectionParameterDAO.deleteByExample(parameterExample); + + // Add connection parameters + createConfigurationValues(mySQLConnection.getConnectionID(), + object.getConfiguration()); + + } + + @Transactional + @Override + public void remove(String identifier) throws GuacamoleException { + + // Get connection + MySQLConnection mySQLConnection = + connectionService.retrieveConnection(identifier, user_id); + + // Verify permission to delete + permissionCheckService.verifyConnectionAccess(this.user_id, + mySQLConnection.getConnectionID(), + MySQLConstants.CONNECTION_DELETE); + + // Delete the connection itself + connectionService.deleteConnection(mySQLConnection.getConnectionID()); + + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java new file mode 100644 index 000000000..e872df0a0 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java @@ -0,0 +1,177 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.name.Names; +import java.util.Properties; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.auth.AuthenticationProvider; +import net.sourceforge.guacamole.net.auth.Credentials; +import net.sourceforge.guacamole.net.auth.UserContext; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionHistoryMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionParameterMapper; +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.UserMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.UserPermissionMapper; +import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties; +import net.sourceforge.guacamole.net.auth.mysql.service.ConnectionService; +import net.sourceforge.guacamole.net.auth.mysql.service.PasswordEncryptionService; +import net.sourceforge.guacamole.net.auth.mysql.service.PermissionCheckService; +import net.sourceforge.guacamole.net.auth.mysql.service.SaltService; +import net.sourceforge.guacamole.net.auth.mysql.service.SecureRandomSaltService; +import net.sourceforge.guacamole.net.auth.mysql.service.SHA256PasswordEncryptionService; +import net.sourceforge.guacamole.net.auth.mysql.service.UserService; +import net.sourceforge.guacamole.properties.GuacamoleProperties; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.mybatis.guice.MyBatisModule; +import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider; +import org.mybatis.guice.datasource.helper.JdbcHelper; + +/** + * Provides a MySQL based implementation of the AuthenticationProvider + * functionality. + * + * @author James Muehlner + */ +public class MySQLAuthenticationProvider implements AuthenticationProvider { + + /** + * Set of all active connections. + */ + private ActiveConnectionSet activeConnectionSet = new ActiveConnectionSet(); + + /** + * Injector which will manage the object graph of this authentication + * provider. + */ + private Injector injector; + + @Override + public UserContext getUserContext(Credentials credentials) throws GuacamoleException { + + // Get user service + UserService userService = injector.getInstance(UserService.class); + + // Get user + MySQLUser authenticatedUser = userService.retrieveUser(credentials); + if (authenticatedUser != null) { + MySQLUserContext context = injector.getInstance(MySQLUserContext.class); + context.init(authenticatedUser.getUserID()); + return context; + } + + // Otherwise, unauthorized + return null; + + } + + /** + * Creates a new MySQLAuthenticationProvider that reads and writes + * authentication data to a MySQL database defined by properties in + * guacamole.properties. + * + * @throws GuacamoleException If a required property is missing, or + * an error occurs while parsing a property. + */ + public MySQLAuthenticationProvider() throws GuacamoleException { + + final Properties myBatisProperties = new Properties(); + + // Set the mysql properties for MyBatis. + myBatisProperties.setProperty("mybatis.environment.id", "guacamole"); + myBatisProperties.setProperty("JDBC.host", GuacamoleProperties.getRequiredProperty(MySQLGuacamoleProperties.MYSQL_HOSTNAME)); + myBatisProperties.setProperty("JDBC.port", String.valueOf(GuacamoleProperties.getRequiredProperty(MySQLGuacamoleProperties.MYSQL_PORT))); + myBatisProperties.setProperty("JDBC.schema", GuacamoleProperties.getRequiredProperty(MySQLGuacamoleProperties.MYSQL_DATABASE)); + myBatisProperties.setProperty("JDBC.username", GuacamoleProperties.getRequiredProperty(MySQLGuacamoleProperties.MYSQL_USERNAME)); + myBatisProperties.setProperty("JDBC.password", GuacamoleProperties.getRequiredProperty(MySQLGuacamoleProperties.MYSQL_PASSWORD)); + myBatisProperties.setProperty("JDBC.autoCommit", "false"); + + // Set up Guice injector. + injector = Guice.createInjector( + JdbcHelper.MySQL, + + new Module() { + @Override + public void configure(Binder binder) { + Names.bindProperties(binder, myBatisProperties); + } + }, + + new MyBatisModule() { + @Override + protected void initialize() { + + // Datasource + bindDataSourceProviderType(PooledDataSourceProvider.class); + + // Transaction factory + bindTransactionFactoryType(JdbcTransactionFactory.class); + + // Add MyBatis mappers + addMapperClass(ConnectionHistoryMapper.class); + addMapperClass(ConnectionMapper.class); + addMapperClass(ConnectionParameterMapper.class); + addMapperClass(ConnectionPermissionMapper.class); + addMapperClass(SystemPermissionMapper.class); + addMapperClass(UserMapper.class); + addMapperClass(UserPermissionMapper.class); + + // Bind interfaces + bind(MySQLUserContext.class); + bind(UserDirectory.class); + bind(MySQLUser.class); + bind(SaltService.class).to(SecureRandomSaltService.class); + bind(PasswordEncryptionService.class).to(SHA256PasswordEncryptionService.class); + bind(PermissionCheckService.class); + bind(ConnectionService.class); + bind(UserService.class); + bind(ActiveConnectionSet.class).toInstance(activeConnectionSet); + + } + } // end of mybatis module + + ); + } // end of constructor + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java new file mode 100644 index 000000000..dc68cfcb4 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java @@ -0,0 +1,132 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSocket; +import net.sourceforge.guacamole.net.auth.AbstractConnection; +import net.sourceforge.guacamole.net.auth.ConnectionRecord; +import net.sourceforge.guacamole.net.auth.mysql.service.ConnectionService; +import net.sourceforge.guacamole.protocol.GuacamoleClientInformation; +import net.sourceforge.guacamole.protocol.GuacamoleConfiguration; + +/** + * A MySQL based implementation of the Connection object. + * @author James Muehlner + */ +public class MySQLConnection extends AbstractConnection { + + /** + * The ID associated with this connection in the database. + */ + private Integer connectionID; + + /** + * The ID of the user who queried or created this connection. + */ + private int userID; + + /** + * History of this connection. + */ + private List history = new ArrayList(); + + /** + * Service for managing connections. + */ + @Inject + private ConnectionService connectionService; + + /** + * Create a default, empty connection. + */ + public MySQLConnection() { + } + + /** + * Get the ID of the corresponding connection record. + * @return The ID of the corresponding connection, if any. + */ + public Integer getConnectionID() { + return connectionID; + } + + /** + * Sets the ID of the corresponding connection record. + * @param connectionID The ID to assign to this connection. + */ + public void setConnectionID(Integer connectionID) { + this.connectionID = connectionID; + } + + /** + * Initialize from explicit values. + * + * @param connectionID The ID of the associated database record, if any. + * @param identifier The unique identifier associated with this connection. + * @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 connectionID, String identifier, + GuacamoleConfiguration config, + List history, int userID) { + + this.connectionID = connectionID; + setIdentifier(identifier); + setConfiguration(config); + this.history.addAll(history); + this.userID = userID; + + } + + @Override + public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { + return connectionService.connect(this, info, userID); + } + + @Override + public List getHistory() throws GuacamoleException { + return Collections.unmodifiableList(history); + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionRecord.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionRecord.java new file mode 100644 index 000000000..bee0917a0 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnectionRecord.java @@ -0,0 +1,103 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import java.util.Date; +import net.sourceforge.guacamole.net.auth.ConnectionRecord; + +/** + * A ConnectionRecord which is based on data stored in MySQL. + * + * @author James Muehlner + */ +public class MySQLConnectionRecord implements ConnectionRecord { + + /** + * The start date of the ConnectionRecord. + */ + private Date startDate; + + /** + * The end date of the ConnectionRecord. + */ + private Date endDate; + + /** + * The name of the user that is associated with this ConnectionRecord. + */ + private String username; + + /** + * Initialize this MySQLConnectionRecord with the start/end dates, + * and the name of the user it represents. + * + * @param startDate The start date of the connection history. + * @param endDate The end date of the connection history. + * @param username The name of the user that used the connection. + */ + public MySQLConnectionRecord(Date startDate, Date endDate, + String username) { + if (startDate != null) this.startDate = new Date(startDate.getTime()); + if (endDate != null) this.endDate = new Date(endDate.getTime()); + this.username = username; + } + + @Override + public Date getStartDate() { + if (startDate == null) return null; + return new Date(startDate.getTime()); + } + + @Override + public Date getEndDate() { + if (endDate == null) return null; + return new Date(endDate.getTime()); + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isActive() { + // If the end date hasn't been stored yet, the connection is still open. + return endDate == null; + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConstants.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConstants.java new file mode 100644 index 000000000..9b72c3f57 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConstants.java @@ -0,0 +1,186 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +import net.sourceforge.guacamole.net.auth.permission.ObjectPermission; +import net.sourceforge.guacamole.net.auth.permission.SystemPermission; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * A set of constants that are useful for the MySQL-based authentication provider. + * @author James Muehlner + */ +public final class MySQLConstants { + + /** + * This class should not be instantiated. + */ + private MySQLConstants() {} + + /** + * The string stored in the database to represent READ access to a user. + */ + public static final String USER_READ = "READ"; + + /** + * The string stored in the database to represent UPDATE access to a user. + */ + public static final String USER_UPDATE = "UPDATE"; + + /** + * The string stored in the database to represent DELETE access to a user. + */ + public static final String USER_DELETE = "DELETE"; + + /** + * The string stored in the database to represent ADMINISTER access to a + * user. + */ + public static final String USER_ADMINISTER = "ADMINISTER"; + + /** + * The string stored in the database to represent READ access to a + * connection. + */ + public static final String CONNECTION_READ = "READ"; + + /** + * The string stored in the database to represent UPDATE access to a + * connection. + */ + public static final String CONNECTION_UPDATE = "UPDATE"; + + /** + * The string stored in the database to represent DELETE access to a + * connection. + */ + public static final String CONNECTION_DELETE = "DELETE"; + + /** + * The string stored in the database to represent ADMINISTER access to a + * connection. + */ + public static final String CONNECTION_ADMINISTER = "ADMINISTER"; + + /** + * The string stored in the database to represent permission to create + * users. + */ + public static final String SYSTEM_USER_CREATE = "CREATE_USER"; + + /** + * The string stored in the database to represent permission to create + * connections. + */ + public static final String SYSTEM_CONNECTION_CREATE = "CREATE_CONNECTION"; + + /** + * The string stored in the database to represent permission to administer + * the system as a whole. + */ + public static final String SYSTEM_ADMINISTER = "ADMINISTER"; + + /** + * Given the type of a permission affecting a user, returns the MySQL + * constant representing that permission type. + * + * @param type The type of permission to look up. + * @return The MySQL constant corresponding to the given permission type. + */ + public static String getUserConstant(ObjectPermission.Type type) { + + // Convert permission type to MySQL constant + switch (type) { + case READ: return USER_READ; + case UPDATE: return USER_UPDATE; + case ADMINISTER: return USER_ADMINISTER; + case DELETE: return USER_DELETE; + } + + // If we get here, permission support was not properly implemented + throw new UnsupportedOperationException( + "Unsupported permission type: " + type); + + } + + /** + * Given the type of a permission affecting a connection, returns the MySQL + * constant representing that permission type. + * + * @param type The type of permission to look up. + * @return The MySQL constant corresponding to the given permission type. + */ + public static String getConnectionConstant(ObjectPermission.Type type) { + + // Convert permission type to MySQL constant + switch (type) { + case READ: return CONNECTION_READ; + case UPDATE: return CONNECTION_UPDATE; + case ADMINISTER: return CONNECTION_ADMINISTER; + case DELETE: return CONNECTION_DELETE; + } + + // If we get here, permission support was not properly implemented + throw new UnsupportedOperationException( + "Unsupported permission type: " + type); + + } + + + /** + * Given the type of a permission affecting the system, returns the MySQL + * constant representing that permission type. + * + * @param type The type of permission to look up. + * @return The MySQL constant corresponding to the given permission type. + */ + public static String getSystemConstant(SystemPermission.Type type) { + + // Convert permission type to MySQL constant + switch (type) { + case CREATE_USER: return SYSTEM_USER_CREATE; + case CREATE_CONNECTION: return SYSTEM_CONNECTION_CREATE; + case ADMINISTER: return SYSTEM_ADMINISTER; + } + + // If we get here, permission support was not properly implemented + throw new UnsupportedOperationException( + "Unsupported permission type: " + type); + + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java new file mode 100644 index 000000000..8b2b6aade --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java @@ -0,0 +1,114 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Inject; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.io.GuacamoleReader; +import net.sourceforge.guacamole.io.GuacamoleWriter; +import net.sourceforge.guacamole.net.GuacamoleSocket; + +/** + * A MySQL specific wrapper around a ConfiguredGuacamoleSocket. + * @author James Muehlner + */ +public class MySQLGuacamoleSocket implements GuacamoleSocket { + + /** + * Injected ActiveConnectionSet which will contain all active connections. + */ + @Inject + private ActiveConnectionSet activeConnectionSet; + + /** + * The wrapped socket. + */ + private GuacamoleSocket socket; + + /** + * The ID associated with the connection associated with the wrapped + * socket. + */ + private int connectionID; + + /** + * The ID of the history record associated with this instance of the + * connection. + */ + private int historyID; + + /** + * Initialize this MySQLGuacamoleSocket with the provided GuacamoleSocket. + * + * @param socket The ConfiguredGuacamoleSocket to wrap. + * @param connectionID The ID of the connection associated with the given + * socket. + * @param historyID The ID of the history record associated with this + * instance of the connection. + */ + public void init(GuacamoleSocket socket, int connectionID, int historyID) { + this.socket = socket; + this.connectionID = connectionID; + this.historyID = historyID; + } + + @Override + public GuacamoleReader getReader() { + return socket.getReader(); + } + + @Override + public GuacamoleWriter getWriter() { + return socket.getWriter(); + } + + @Override + public void close() throws GuacamoleException { + + // Close socket + socket.close(); + + // Mark this connection as inactive + activeConnectionSet.closeConnection(connectionID, historyID); + } + + @Override + public boolean isOpen() { + return socket.isOpen(); + } +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java new file mode 100644 index 000000000..e969a61b2 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java @@ -0,0 +1,193 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +package net.sourceforge.guacamole.net.auth.mysql; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.auth.AbstractUser; +import net.sourceforge.guacamole.net.auth.User; +import net.sourceforge.guacamole.net.auth.permission.Permission; + +/** + * A MySQL based implementation of the User object. + * @author James Muehlner + */ +public class MySQLUser extends AbstractUser { + + /** + * The ID of this user in the database, if any. + */ + private Integer userID; + + /** + * The set of current permissions a user has. + */ + private Set permissions = new HashSet(); + + /** + * Any newly added permissions that have yet to be committed. + */ + private Set newPermissions = new HashSet(); + + /** + * Any newly deleted permissions that have yet to be deleted. + */ + private Set removedPermissions = new HashSet(); + + /** + * Creates a new, empty MySQLUser. + */ + public MySQLUser() { + } + + /** + * Initializes a new MySQLUser having the given username. + * + * @param name The name to assign to this MySQLUser. + */ + public void init(String name) { + init(null, name, null, Collections.EMPTY_SET); + } + + /** + * Initializes a new MySQLUser, copying all data from the given user + * object. + * + * @param user The user object to copy. + * @throws GuacamoleException If an error occurs while reading the user + * data in the given object. + */ + public void init(User user) throws GuacamoleException { + init(null, user.getUsername(), user.getPassword(), user.getPermissions()); + } + + /** + * Initializes a new MySQLUser initialized from the given data from the + * database. + * + * @param userID The ID of the user in the database, if any. + * @param username The username of this user. + * @param password The password to assign to this user. + * @param permissions The permissions to assign to this user, as + * retrieved from the database. + */ + public void init(Integer userID, String username, String password, + Set permissions) { + this.userID = userID; + setUsername(username); + setPassword(password); + this.permissions.addAll(permissions); + } + + /** + * Get the current set of permissions this user has. + * @return the current set of permissions. + */ + public Set getCurrentPermissions() { + return permissions; + } + + /** + * Get any new permissions that have yet to be inserted. + * @return the new set of permissions. + */ + public Set getNewPermissions() { + return newPermissions; + } + + /** + * Get any permissions that have not yet been deleted. + * @return the permissions that need to be deleted. + */ + public Set getRemovedPermissions() { + return removedPermissions; + } + + /** + * Reset the new and removed permission sets after they are + * no longer needed. + */ + public void resetPermissions() { + newPermissions.clear(); + removedPermissions.clear(); + } + + /** + * Returns the ID of this user in the database, if it exists. + * + * @return The ID of this user in the database, or null if this user + * was not retrieved from the database. + */ + public Integer getUserID() { + return userID; + } + + /** + * Sets the ID of this user to the given value. + * + * @param userID The ID to assign to this user. + */ + public void setUserID(Integer userID) { + this.userID = userID; + } + + @Override + public Set getPermissions() throws GuacamoleException { + return Collections.unmodifiableSet(permissions); + } + + @Override + public boolean hasPermission(Permission permission) throws GuacamoleException { + return permissions.contains(permission); + } + + @Override + public void addPermission(Permission permission) throws GuacamoleException { + permissions.add(permission); + newPermissions.add(permission); + removedPermissions.remove(permission); + } + + @Override + public void removePermission(Permission permission) throws GuacamoleException { + permissions.remove(permission); + newPermissions.remove(permission); + removedPermissions.add(permission); + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java new file mode 100644 index 000000000..94cc3991e --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java @@ -0,0 +1,106 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.inject.Inject; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.auth.Connection; +import net.sourceforge.guacamole.net.auth.Directory; +import net.sourceforge.guacamole.net.auth.User; +import net.sourceforge.guacamole.net.auth.UserContext; +import net.sourceforge.guacamole.net.auth.mysql.service.UserService; + +/** + * The MySQL representation of a UserContext. + * @author James Muehlner + */ +public class MySQLUserContext implements UserContext { + + /** + * The ID of the user owning this context. The permissions of this user + * dictate the access given via the user and connection directories. + */ + private int user_id; + + /** + * User directory restricted by the permissions of the user associated + * with this context. + */ + @Inject + private UserDirectory userDirectory; + + /** + * Connection directory restricted by the permissions of the user associated + * with this context. + */ + @Inject + private ConnectionDirectory connectionDirectory; + + /** + * Service for accessing users. + */ + @Inject + private UserService userService; + + /** + * Initializes the user and directories associated with this context. + * + * @param user_id The ID of the user owning this context. + */ + public void init(int user_id) { + this.user_id = user_id; + userDirectory.init(user_id); + connectionDirectory.init(user_id); + } + + @Override + public User self() { + return userService.retrieveUser(user_id); + } + + @Override + public Directory getUserDirectory() throws GuacamoleException { + return userDirectory; + } + + @Override + public Directory getConnectionDirectory() throws GuacamoleException { + return connectionDirectory; + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java new file mode 100644 index 000000000..4a432cf45 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java @@ -0,0 +1,601 @@ + +package net.sourceforge.guacamole.net.auth.mysql; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import com.google.common.base.Preconditions; +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.sourceforge.guacamole.GuacamoleClientException; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.GuacamoleSecurityException; +import net.sourceforge.guacamole.net.auth.Directory; +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.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.mysql.service.ConnectionService; +import net.sourceforge.guacamole.net.auth.mysql.service.PermissionCheckService; +import net.sourceforge.guacamole.net.auth.mysql.service.UserService; +import net.sourceforge.guacamole.net.auth.permission.ConnectionPermission; +import net.sourceforge.guacamole.net.auth.permission.Permission; +import net.sourceforge.guacamole.net.auth.permission.SystemPermission; +import net.sourceforge.guacamole.net.auth.permission.UserPermission; +import org.mybatis.guice.transactional.Transactional; + +/** + * A MySQL based implementation of the User Directory. + * @author James Muehlner + */ +public class UserDirectory implements Directory { + + /** + * The ID of the user who this user directory belongs to. + * Access is based on his/her permission settings. + */ + private int user_id; + + /** + * Service for accessing users. + */ + @Inject + private UserService userService; + + /** + * Service for accessing connections. + */ + @Inject + private ConnectionService connectionService; + + /** + * DAO for accessing user permissions, which will be injected. + */ + @Inject + private UserPermissionMapper userPermissionDAO; + + /** + * DAO for accessing connection permissions, which will be injected. + */ + @Inject + private ConnectionPermissionMapper connectionPermissionDAO; + + /** + * DAO for accessing system permissions, which will be injected. + */ + @Inject + private SystemPermissionMapper systemPermissionDAO; + + /** + * Service for checking various permissions, which will be injected. + */ + @Inject + private PermissionCheckService permissionCheckService; + + /** + * Set the user for this directory. + * + * @param user_id The ID of the user whose permissions define the visibility + * of other users in this directory. + */ + public void init(int user_id) { + this.user_id = user_id; + } + + @Transactional + @Override + public net.sourceforge.guacamole.net.auth.User get(String identifier) + throws GuacamoleException { + + // Get user + MySQLUser user = userService.retrieveUser(identifier); + + // Verify access is granted + permissionCheckService.verifyUserAccess(this.user_id, + user.getUserID(), + MySQLConstants.USER_READ); + + // Return user + return userService.retrieveUser(identifier); + + } + + @Transactional + @Override + public Set getIdentifiers() throws GuacamoleException { + return permissionCheckService.retrieveUsernames(user_id, + MySQLConstants.USER_READ); + } + + @Override + @Transactional + public void add(net.sourceforge.guacamole.net.auth.User object) + throws GuacamoleException { + + String username = object.getUsername().trim(); + if(username.isEmpty()) + throw new GuacamoleClientException("The username cannot be blank."); + + // Verify current user has permission to create users + permissionCheckService.verifySystemAccess(this.user_id, + MySQLConstants.SYSTEM_USER_CREATE); + Preconditions.checkNotNull(object); + + // Verify that no user already exists with this username. + MySQLUser previousUser = userService.retrieveUser(username); + if(previousUser != null) + throw new GuacamoleClientException("That username is already in use."); + + // Create new user + MySQLUser user = userService.createUser(username, object.getPassword()); + + // Create permissions of new user in database + createPermissions(user.getUserID(), object.getPermissions()); + + // Give the current user full access to the newly created user. + UserPermissionKey newUserPermission = new UserPermissionKey(); + newUserPermission.setUser_id(this.user_id); + newUserPermission.setAffected_user_id(user.getUserID()); + + // READ permission on new user + newUserPermission.setPermission(MySQLConstants.USER_READ); + userPermissionDAO.insert(newUserPermission); + + // UPDATE permission on new user + newUserPermission.setPermission(MySQLConstants.USER_UPDATE); + userPermissionDAO.insert(newUserPermission); + + // DELETE permission on new user + newUserPermission.setPermission(MySQLConstants.USER_DELETE); + userPermissionDAO.insert(newUserPermission); + + // ADMINISTER permission on new user + newUserPermission.setPermission(MySQLConstants.USER_ADMINISTER); + userPermissionDAO.insert(newUserPermission); + + } + + /** + * Add the given permissions to the given user. + * + * @param user_id The ID of the user whose permissions should be updated. + * @param permissions The permissions to add. + * @throws GuacamoleException If an error occurs while updating the + * permissions of the given user. + */ + private void createPermissions(int user_id, Set permissions) throws GuacamoleException { + + // Partition given permissions by permission type + List newUserPermissions = new ArrayList(); + List newConnectionPermissions = new ArrayList(); + List newSystemPermissions = new ArrayList(); + + for (Permission permission : permissions) { + + if (permission instanceof UserPermission) + newUserPermissions.add((UserPermission) permission); + + else if (permission instanceof ConnectionPermission) + newConnectionPermissions.add((ConnectionPermission) permission); + + else if (permission instanceof SystemPermission) + newSystemPermissions.add((SystemPermission) permission); + } + + // Create the new permissions + createUserPermissions(user_id, newUserPermissions); + createConnectionPermissions(user_id, newConnectionPermissions); + createSystemPermissions(user_id, newSystemPermissions); + + } + + + + /** + * Remove the given permissions from the given user. + * + * @param user_id The ID of the user whose permissions should be updated. + * @param permissions The permissions to remove. + * @throws GuacamoleException If an error occurs while updating the + * permissions of the given user. + */ + private void removePermissions(int user_id, Set permissions) + throws GuacamoleException { + + // Partition given permissions by permission type + List removedUserPermissions = new ArrayList(); + List removedConnectionPermissions = new ArrayList(); + List removedSystemPermissions = new ArrayList(); + + for (Permission permission : permissions) { + + if (permission instanceof UserPermission) + removedUserPermissions.add((UserPermission) permission); + + else if (permission instanceof ConnectionPermission) + removedConnectionPermissions.add((ConnectionPermission) permission); + + else if (permission instanceof SystemPermission) + removedSystemPermissions.add((SystemPermission) permission); + } + + // Delete the removed permissions. + deleteUserPermissions(user_id, removedUserPermissions); + deleteConnectionPermissions(user_id, removedConnectionPermissions); + deleteSystemPermissions(user_id, removedSystemPermissions); + + } + + /** + * Create the given user permissions for the given user. + * + * @param user_id The ID of the user to change the permissions of. + * @param permissions The new permissions the given user should have when + * this operation completes. + * @throws GuacamoleException If permission to alter the access permissions + * of affected objects is denied. + */ + private void createUserPermissions(int user_id, + Collection permissions) + throws GuacamoleException { + + // If no permissions given, stop now + if(permissions.isEmpty()) + return; + + // Get list of administerable user IDs + List administerableUserIDs = + permissionCheckService.retrieveUserIDs(this.user_id, + MySQLConstants.USER_ADMINISTER); + + // Get set of usernames corresponding to administerable users + Map administerableUsers = + userService.translateUsernames(administerableUserIDs); + + // Insert all given permissions + for (UserPermission permission : permissions) { + + // Get original ID + Integer affected_id = + administerableUsers.get(permission.getObjectIdentifier()); + + // Verify that the user actually has permission to administrate + // every one of these users + if (affected_id == null) + throw new GuacamoleSecurityException( + "User #" + this.user_id + + " does not have permission to administrate user " + + permission.getObjectIdentifier()); + + // Create new permission + UserPermissionKey newPermission = new UserPermissionKey(); + newPermission.setUser_id(user_id); + newPermission.setPermission(MySQLConstants.getUserConstant(permission.getType())); + newPermission.setAffected_user_id(affected_id); + userPermissionDAO.insert(newPermission); + + } + + } + + /** + * Delete permissions having to do with users for a given user. + * + * @param user_id The ID of the user to change the permissions of. + * @param permissions The permissions the given user should no longer have + * when this operation completes. + * @throws GuacamoleException If permission to alter the access permissions + * of affected objects is denied. + */ + private void deleteUserPermissions(int user_id, + Collection permissions) + throws GuacamoleException { + + // If no permissions given, stop now + if(permissions.isEmpty()) + return; + + // Get list of administerable user IDs + List administerableUserIDs = + permissionCheckService.retrieveUserIDs(this.user_id, + MySQLConstants.USER_ADMINISTER); + + // Get set of usernames corresponding to administerable users + Map administerableUsers = + userService.translateUsernames(administerableUserIDs); + + // Delete requested permissions + for (UserPermission permission : permissions) { + + // Get original ID + Integer affected_id = + administerableUsers.get(permission.getObjectIdentifier()); + + // Verify that the user actually has permission to administrate + // every one of these users + if (affected_id == null) + throw new GuacamoleSecurityException( + "User #" + this.user_id + + " does not have permission to administrate user " + + permission.getObjectIdentifier()); + + // Delete requested permission + UserPermissionExample userPermissionExample = new UserPermissionExample(); + userPermissionExample.createCriteria() + .andUser_idEqualTo(user_id) + .andPermissionEqualTo(MySQLConstants.getUserConstant(permission.getType())) + .andAffected_user_idEqualTo(affected_id); + userPermissionDAO.deleteByExample(userPermissionExample); + + } + + } + + /** + * Create any new permissions having to do with connections for a given + * user. + * + * @param user_id The ID of the user to assign or remove permissions from. + * @param permissions The new permissions the user should have after this + * operation completes. + * @throws GuacamoleException If permission to alter the access permissions + * of affected objects is deniedD + */ + private void createConnectionPermissions(int user_id, + Collection permissions) + throws GuacamoleException { + + // If no permissions given, stop now + if(permissions.isEmpty()) + return; + + // Get list of administerable connection IDs + List administerableConnectionIDs = + permissionCheckService.retrieveConnectionIDs(this.user_id, + MySQLConstants.CONNECTION_ADMINISTER); + + // Get set of names corresponding to administerable connections + Map administerableConnections = + connectionService.translateNames(administerableConnectionIDs); + + // Insert all given permissions + for (ConnectionPermission permission : permissions) { + + // Get original ID + Integer connection_id = + administerableConnections.get(permission.getObjectIdentifier()); + + // Throw exception if permission to administer this connection + // is not granted + if (connection_id == null) + throw new GuacamoleSecurityException( + "User #" + this.user_id + + " does not have permission to administrate connection " + + permission.getObjectIdentifier()); + + + // Create new permission + ConnectionPermissionKey newPermission = new ConnectionPermissionKey(); + newPermission.setUser_id(user_id); + newPermission.setPermission(MySQLConstants.getConnectionConstant(permission.getType())); + newPermission.setConnection_id(connection_id); + connectionPermissionDAO.insert(newPermission); + + } + } + + /** + * Delete permissions having to do with connections for a given user. + * + * @param user_id The ID of the user to change the permissions of. + * @param permissions The permissions the given user should no longer have + * when this operation completes. + * @throws GuacamoleException If permission to alter the access permissions + * of affected objects is denied. + */ + private void deleteConnectionPermissions(int user_id, + Collection permissions) + throws GuacamoleException { + + // If no permissions given, stop now + if(permissions.isEmpty()) + return; + + // Get list of administerable connection IDs + List administerableConnectionIDs = + permissionCheckService.retrieveConnectionIDs(this.user_id, + MySQLConstants.CONNECTION_ADMINISTER); + + // Get set of names corresponding to administerable connections + Map administerableConnections = + connectionService.translateNames(administerableConnectionIDs); + + // Delete requested permissions + for (ConnectionPermission permission : permissions) { + + // Get original ID + Integer connection_id = + administerableConnections.get(permission.getObjectIdentifier()); + + // Verify that the user actually has permission to administrate + // every one of these connections + if (connection_id == null) + throw new GuacamoleSecurityException( + "User #" + this.user_id + + " does not have permission to administrate connection " + + permission.getObjectIdentifier()); + + ConnectionPermissionExample connectionPermissionExample = new ConnectionPermissionExample(); + connectionPermissionExample.createCriteria() + .andUser_idEqualTo(user_id) + .andPermissionEqualTo(MySQLConstants.getConnectionConstant(permission.getType())) + .andConnection_idEqualTo(connection_id); + connectionPermissionDAO.deleteByExample(connectionPermissionExample); + + } + + } + + /** + * Create any new system permissions for a given user. All permissions in + * the given list will be inserted. + * + * @param user_id The ID of the user whose permissions should be updated. + * @param permissions The new system permissions that the given user should + * have when this operation completes. + * @throws GuacamoleException If permission to administer system permissions + * is denied. + */ + private void createSystemPermissions(int user_id, + Collection permissions) throws GuacamoleException { + + // If no permissions given, stop now + if(permissions.isEmpty()) + return; + + // Only a system administrator can add system permissions. + permissionCheckService.verifySystemAccess( + this.user_id, SystemPermission.Type.ADMINISTER.name()); + + // Insert all requested permissions + for (SystemPermission permission : permissions) { + + // Insert permission + SystemPermissionKey newSystemPermission = new SystemPermissionKey(); + newSystemPermission.setUser_id(user_id); + newSystemPermission.setPermission(MySQLConstants.getSystemConstant(permission.getType())); + systemPermissionDAO.insert(newSystemPermission); + + } + + } + + /** + * Delete system permissions for a given user. All permissions in + * the given list will be removed from the user. + * + * @param user_id The ID of the user whose permissions should be updated. + * @param permissions The permissions the given user should no longer have + * when this operation completes. + * @throws GuacamoleException If the permissions specified could not be + * removed due to system restrictions. + */ + private void deleteSystemPermissions(int user_id, + Collection permissions) + throws GuacamoleException { + + // If no permissions given, stop now + if (permissions.isEmpty()) + return; + + // Prevent self-de-adminifying + if (user_id == this.user_id) + throw new GuacamoleClientException("Removing your own administrative permissions is not allowed."); + + // Build list of requested system permissions + List systemPermissionTypes = new ArrayList(); + for (SystemPermission permission : permissions) + systemPermissionTypes.add(MySQLConstants.getSystemConstant(permission.getType())); + + // Delete the requested system permissions for this user + SystemPermissionExample systemPermissionExample = new SystemPermissionExample(); + systemPermissionExample.createCriteria().andUser_idEqualTo(user_id) + .andPermissionIn(systemPermissionTypes); + systemPermissionDAO.deleteByExample(systemPermissionExample); + + } + + @Override + @Transactional + public void update(net.sourceforge.guacamole.net.auth.User object) + throws GuacamoleException { + + // If user not actually from this auth provider, we can't handle updated + // permissions. + if (!(object instanceof MySQLUser)) + throw new GuacamoleException("User not from database."); + + MySQLUser mySQLUser = (MySQLUser) object; + + // Validate permission to update this user is granted + permissionCheckService.verifyUserAccess(this.user_id, + mySQLUser.getUserID(), + MySQLConstants.USER_UPDATE); + + // Update the user in the database + userService.updateUser(mySQLUser); + + // Update permissions in database + createPermissions(mySQLUser.getUserID(), mySQLUser.getNewPermissions()); + removePermissions(mySQLUser.getUserID(), mySQLUser.getRemovedPermissions()); + + // The appropriate permissions have been inserted and deleted, so + // reset the new and removed permission sets. + mySQLUser.resetPermissions(); + + } + + @Override + @Transactional + public void remove(String identifier) throws GuacamoleException { + + // Get user pending deletion + MySQLUser user = userService.retrieveUser(identifier); + + // Prevent self-deletion + if (user.getUserID() == this.user_id) + throw new GuacamoleClientException("Deleting your own user is not allowed."); + + // Validate current user has permission to remove the specified user + permissionCheckService.verifyUserAccess(this.user_id, + user.getUserID(), + MySQLConstants.USER_DELETE); + + // Delete specified user + userService.deleteUser(user.getUserID()); + + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/package-info.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/package-info.java new file mode 100644 index 000000000..81f802bbb --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/package-info.java @@ -0,0 +1,7 @@ + +/** + * Base classes which support the MySQL authentication provider, including + * the authentication provider itself. + */ +package net.sourceforge.guacamole.net.auth.mysql; + diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java new file mode 100644 index 000000000..10ca5525f --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java @@ -0,0 +1,112 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +package net.sourceforge.guacamole.net.auth.mysql.properties; + +import net.sourceforge.guacamole.properties.BooleanGuacamoleProperty; +import net.sourceforge.guacamole.properties.IntegerGuacamoleProperty; +import net.sourceforge.guacamole.properties.StringGuacamoleProperty; + +/** + * Properties used by the MySQL Authentication plugin. + * @author James Muehlner + */ +public class MySQLGuacamoleProperties { + + /** + * This class should not be instantiated. + */ + private MySQLGuacamoleProperties() {} + + /** + * The URL of the MySQL server hosting the guacamole authentication tables. + */ + public static final StringGuacamoleProperty MYSQL_HOSTNAME = new StringGuacamoleProperty() { + + @Override + public String getName() { return "mysql-hostname"; } + + }; + + /** + * The port of the MySQL server hosting the guacamole authentication tables. + */ + public static final IntegerGuacamoleProperty MYSQL_PORT = new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "mysql-port"; } + + }; + + /** + * The name of the MySQL database containing the guacamole authentication tables. + */ + public static final StringGuacamoleProperty MYSQL_DATABASE = new StringGuacamoleProperty() { + + @Override + public String getName() { return "mysql-database"; } + + }; + + /** + * The username used to authenticate to the MySQL database containing the guacamole authentication tables. + */ + public static final StringGuacamoleProperty MYSQL_USERNAME = new StringGuacamoleProperty() { + + @Override + public String getName() { return "mysql-username"; } + + }; + + /** + * The password used to authenticate to the MySQL database containing the guacamole authentication tables. + */ + public static final StringGuacamoleProperty MYSQL_PASSWORD = new StringGuacamoleProperty() { + + @Override + public String getName() { return "mysql-password"; } + + }; + + /** + * Whether or not multiple users accessing the same connection at the same time should be disallowed. + */ + public static final BooleanGuacamoleProperty MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS = new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "mysql-disallow-simultaneous-connections"; } + + }; +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/package-info.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/package-info.java new file mode 100644 index 000000000..d327a33bc --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/package-info.java @@ -0,0 +1,7 @@ + +/** + * Properties which control the configuration of the MySQL authentication + * provider. + */ +package net.sourceforge.guacamole.net.auth.mysql.properties; + diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java new file mode 100644 index 000000000..71e26944a --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java @@ -0,0 +1,456 @@ + +package net.sourceforge.guacamole.net.auth.mysql.service; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** 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.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.sourceforge.guacamole.GuacamoleClientException; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSocket; +import net.sourceforge.guacamole.net.InetGuacamoleSocket; +import net.sourceforge.guacamole.net.auth.mysql.ActiveConnectionSet; +import net.sourceforge.guacamole.net.auth.mysql.MySQLConnection; +import net.sourceforge.guacamole.net.auth.mysql.MySQLConnectionRecord; +import net.sourceforge.guacamole.net.auth.mysql.MySQLGuacamoleSocket; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionHistoryMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionMapper; +import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionParameterMapper; +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.ConnectionHistory; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionHistoryExample; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameter; +import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameterExample; +import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties; +import net.sourceforge.guacamole.properties.GuacamoleProperties; +import net.sourceforge.guacamole.protocol.ConfiguredGuacamoleSocket; +import net.sourceforge.guacamole.protocol.GuacamoleClientInformation; +import net.sourceforge.guacamole.protocol.GuacamoleConfiguration; +import org.apache.ibatis.session.RowBounds; + +/** + * Service which provides convenience methods for creating, retrieving, and + * manipulating connections. + * + * @author Michael Jumper, James Muehlner + */ +public class ConnectionService { + + /** + * DAO for accessing connections. + */ + @Inject + private ConnectionMapper connectionDAO; + + /** + * DAO for accessing connection parameters. + */ + @Inject + private ConnectionParameterMapper connectionParameterDAO; + + /** + * DAO for accessing connection history. + */ + @Inject + private ConnectionHistoryMapper connectionHistoryDAO; + + /** + * Provider which creates MySQLConnections. + */ + @Inject + private Provider mySQLConnectionProvider; + + /** + * Provider which creates MySQLGuacamoleSockets. + */ + @Inject + private Provider mySQLGuacamoleSocketProvider; + + /** + * Set of all currently active connections. + */ + @Inject + private ActiveConnectionSet activeConnectionSet; + + /** + * Service managing users. + */ + @Inject + private UserService userService; + + /** + * Retrieves the connection having the given name from the database. + * + * @param name The name of the connection to return. + * @param userID The ID of the user who queried this connection. + * @return The connection having the given name, or null if no such + * connection could be found. + */ + public MySQLConnection retrieveConnection(String name, int userID) { + + // Query connection by connection identifier (name) + ConnectionExample example = new ConnectionExample(); + example.createCriteria().andConnection_nameEqualTo(name); + List connections = + connectionDAO.selectByExample(example); + + // If no connection found, return null + if(connections.isEmpty()) + return null; + + // Assert only one connection found + assert connections.size() == 1 : "Multiple connections with same name."; + + // Otherwise, return found connection + return toMySQLConnection(connections.get(0), userID); + + } + + /** + * Retrieves the connection having the given ID from the database. + * + * @param id The ID of the connection to retrieve. + * @param userID The ID of the user who queried this connection. + * @return The connection having the given ID, or null if no such + * connection was found. + */ + public MySQLConnection retrieveConnection(int id, int userID) { + + // Query connection by ID + Connection connection = connectionDAO.selectByPrimaryKey(id); + + // If no connection found, return null + if(connection == null) + return null; + + // Otherwise, return found connection + return toMySQLConnection(connection, userID); + } + + /** + * Retrieves a translation map of connection names to their corresponding + * IDs. + * + * @param ids The IDs of the connections to retrieve the names of. + * @return A map containing the names of all connections and their + * corresponding IDs. + */ + public Map translateNames(List 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 connections having the given IDs + ConnectionExample example = new ConnectionExample(); + example.createCriteria().andConnection_idIn(ids); + List connections = connectionDAO.selectByExample(example); + + // Produce set of names + for (Connection connection : connections) + names.put(connection.getConnection_name(), + connection.getConnection_id()); + + return names; + + } + + /** + * Retrieves a map of all connection names for the given IDs. + * + * @param ids The IDs of the connections to retrieve the names of. + * @return A map containing the names of all connections 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 connections having the given IDs + ConnectionExample example = new ConnectionExample(); + example.createCriteria().andConnection_idIn(Lists.newArrayList(ids)); + List connections = connectionDAO.selectByExample(example); + + // Produce set of names + for (Connection connection : connections) + names.put(connection.getConnection_id(), + connection.getConnection_name()); + + return names; + + } + + /** + * Convert the given database-retrieved Connection into a MySQLConnection. + * The parameters of the given connection will be read and added to the + * MySQLConnection in the process. + * + * @param connection The connection to convert. + * @param userID The user who queried this connection. + * @return A new MySQLConnection containing all data associated with the + * specified connection. + */ + private MySQLConnection toMySQLConnection(Connection connection, int userID) { + + // Build configuration + GuacamoleConfiguration config = new GuacamoleConfiguration(); + + // Query parameters for configuration + ConnectionParameterExample connectionParameterExample = new ConnectionParameterExample(); + connectionParameterExample.createCriteria().andConnection_idEqualTo(connection.getConnection_id()); + List connectionParameters = + connectionParameterDAO.selectByExample(connectionParameterExample); + + // Set protocol + config.setProtocol(connection.getProtocol()); + + // Set all values for all parameters + for (ConnectionParameter parameter : connectionParameters) + config.setParameter(parameter.getParameter_name(), + parameter.getParameter_value()); + + // Create new MySQLConnection from retrieved data + MySQLConnection mySQLConnection = mySQLConnectionProvider.get(); + mySQLConnection.init( + connection.getConnection_id(), + connection.getConnection_name(), + config, + retrieveHistory(connection.getConnection_id()), + userID + ); + + return mySQLConnection; + + } + + /** + * Retrieves the history of the connection having the given ID. + * + * @param connectionID The ID of the connection to retrieve the history of. + * @return A list of MySQLConnectionRecord documenting the history of this + * connection. + */ + public List retrieveHistory(int connectionID) { + + // Retrieve history records relating to given connection ID + ConnectionHistoryExample example = new ConnectionHistoryExample(); + example.createCriteria().andConnection_idEqualTo(connectionID); + + // We want to return the newest records first + example.setOrderByClause("start_date DESC"); + + // Set the maximum number of history records returned to 100 + RowBounds rowBounds = new RowBounds(0, 100); + + // Retrieve all connection history entries + List connectionHistories = + connectionHistoryDAO.selectByExampleWithRowbounds(example, rowBounds); + + // Convert history entries to connection records + List connectionRecords = new ArrayList(); + Set userIDSet = new HashSet(); + for(ConnectionHistory history : connectionHistories) { + userIDSet.add(history.getUser_id()); + } + + // Get all the usernames for the users who are in the history + Map usernameMap = userService.retrieveUsernames(userIDSet); + + // Create the new ConnectionRecords + for(ConnectionHistory history : connectionHistories) { + Date startDate = history.getStart_date(); + Date endDate = history.getEnd_date(); + String username = usernameMap.get(history.getUser_id()); + MySQLConnectionRecord connectionRecord = new MySQLConnectionRecord(startDate, endDate, username); + connectionRecords.add(connectionRecord); + } + + return connectionRecords; + } + + /** + * Create a MySQLGuacamoleSocket using the provided connection. + * + * @param connection The connection to use when connecting the socket. + * @param info The information to use when performing the connection + * handshake. + * @param userID The ID of the user who is connecting to the socket. + * @return The connected socket. + * @throws GuacamoleException If an error occurs while connecting the + * socket. + */ + public MySQLGuacamoleSocket connect(MySQLConnection connection, + GuacamoleClientInformation info, int userID) + throws GuacamoleException { + + // If the given connection is active, and multiple simultaneous + // connections are not allowed, disallow connection + if(GuacamoleProperties.getProperty( + MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false) + && activeConnectionSet.isActive(connection.getConnectionID())) + throw new GuacamoleClientException("Cannot connect. This connection is in use."); + + // Get guacd connection information + String host = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME); + int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT); + + // Get socket + GuacamoleSocket socket = new ConfiguredGuacamoleSocket( + new InetGuacamoleSocket(host, port), + connection.getConfiguration(), info + ); + + // Mark this connection as active + int historyID = activeConnectionSet.openConnection(connection.getConnectionID(), userID); + + // Return new MySQLGuacamoleSocket + MySQLGuacamoleSocket mySQLGuacamoleSocket = mySQLGuacamoleSocketProvider.get(); + mySQLGuacamoleSocket.init(socket, connection.getConnectionID(), historyID); + return mySQLGuacamoleSocket; + + } + + /** + * Creates a new connection having the given name and protocol. + * + * @param name The name to assign to the new connection. + * @param protocol The protocol to assign to the new connection. + * @param userID The ID of the user who created this connection. + * @return A new MySQLConnection containing the data of the newly created + * connection. + */ + public MySQLConnection createConnection(String name, String protocol, int userID) { + + // Initialize database connection + Connection connection = new Connection(); + connection.setConnection_name(name); + connection.setProtocol(protocol); + + // Create connection + connectionDAO.insert(connection); + return toMySQLConnection(connection, userID); + + } + + /** + * Deletes the connection having the given ID from the database. + * @param id The ID of the connection to delete. + */ + public void deleteConnection(int id) { + connectionDAO.deleteByPrimaryKey(id); + } + + /** + * Updates the connection in the database corresponding to the given + * MySQLConnection. + * + * @param mySQLConnection The MySQLConnection to update (save) to the + * database. This connection must already exist. + */ + public void updateConnection(MySQLConnection mySQLConnection) { + + // Populate connection + Connection connection = new Connection(); + connection.setConnection_id(mySQLConnection.getConnectionID()); + connection.setConnection_name(mySQLConnection.getIdentifier()); + connection.setProtocol(mySQLConnection.getConfiguration().getProtocol()); + + // Update the connection in the database + connectionDAO.updateByPrimaryKeySelective(connection); + + } + + /** + * Get the names of all the connections defined in the system. + * + * @return A Set of names of all the connections defined in the system. + */ + public Set getAllConnectionNames() { + + // Set of all present connection names + Set names = new HashSet(); + + // Query all connection names + List connections = + connectionDAO.selectByExample(new ConnectionExample()); + for (Connection connection : connections) + names.add(connection.getConnection_name()); + + return names; + + } + + /** + * Get the connection IDs of all the connections defined in the system. + * + * @return A list of connection IDs of all the connections defined in the system. + */ + public List getAllConnectionIDs() { + + // Set of all present connection IDs + List connectionIDs = new ArrayList(); + + // Query all connection IDs + List connections = + connectionDAO.selectByExample(new ConnectionExample()); + for (Connection connection : connections) + connectionIDs.add(connection.getConnection_id()); + + return connectionIDs; + + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PasswordEncryptionService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PasswordEncryptionService.java new file mode 100644 index 000000000..0989a1bfd --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PasswordEncryptionService.java @@ -0,0 +1,69 @@ + +package net.sourceforge.guacamole.net.auth.mysql.service; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * A service to perform password encryption and checking. + * @author James Muehlner + */ +public interface PasswordEncryptionService { + + /** + * Checks whether the provided, unhashed password matches the given + * hash/salt pair. + * + * @param password The unhashed password to validate. + * @param hashedPassword The hashed password to compare the given password + * against. + * @param salt The salt used when the hashed password given was created. + * @return true if the provided credentials match the values given, false + * otherwise. + */ + public boolean checkPassword(String password, byte[] hashedPassword, + byte[] salt); + + /** + * Creates a password hash based on the provided username, password, and + * salt. + * + * @param password The password to hash. + * @param salt The salt to use when hashing the password. + * @return The generated password hash. + */ + public byte[] createPasswordHash(String password, byte[] salt); +} 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 new file mode 100644 index 000000000..ae3a11387 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/PermissionCheckService.java @@ -0,0 +1,497 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +package net.sourceforge.guacamole.net.auth.mysql.service; + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +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.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.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.ConnectionPermission; +import net.sourceforge.guacamole.net.auth.permission.Permission; +import net.sourceforge.guacamole.net.auth.permission.SystemPermission; +import net.sourceforge.guacamole.net.auth.permission.UserPermission; + +/** + * A service to retrieve information about what objects a user has permission to. + * @author James Muehlner + */ +public class PermissionCheckService { + + /** + * Service for accessing users. + */ + @Inject + private UserService userService; + + /** + * Service for accessing connections. + */ + @Inject + private ConnectionService connectionService; + + /** + * DAO for accessing permissions related to users. + */ + @Inject + private UserPermissionMapper userPermissionDAO; + + /** + * DAO for accessing permissions related to connections. + */ + @Inject + private ConnectionPermissionMapper connectionPermissionDAO; + + /** + * DAO for accessing permissions related to the system as a whole. + */ + @Inject + private SystemPermissionMapper systemPermissionDAO; + + /** + * Verifies that the user has the specified access to the given other + * user. If permission is denied, a GuacamoleSecurityException is thrown. + * + * @param userID The ID of the user to check. + * @param affectedUserID The user 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 verifyUserAccess(int userID, int affectedUserID, + String permissionType) throws GuacamoleSecurityException { + + // If permission does not exist, throw exception + if(!checkUserAccess(userID, affectedUserID, permissionType)) + throw new GuacamoleSecurityException("Permission denied."); + + } + + /** + * Verifies that the user has the specified access to the given connection. + * If permission is denied, a GuacamoleSecurityException is thrown. + * + * @param userID The ID of the user to check. + * @param affectedConnectionID The connection 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 verifyConnectionAccess(int userID, int affectedConnectionID, String permissionType) throws GuacamoleSecurityException { + + // If permission does not exist, throw exception + if(!checkConnectionAccess(userID, affectedConnectionID, 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. + * + * @param userID The ID of the user to check. + * @param systemPermissionType The type of permission to check for. + * @throws GuacamoleSecurityException If the specified permission is not + * granted. + */ + public void verifySystemAccess(int userID, String systemPermissionType) + throws GuacamoleSecurityException { + + // If permission does not exist, throw exception + if(!checkSystemAccess(userID, systemPermissionType)) + throw new GuacamoleSecurityException("Permission denied."); + + } + + /** + * Checks whether a user has the specified type of access to the affected + * user. + * + * @param userID The ID of the user to check. + * @param affectedUserID The user 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 checkUserAccess(int userID, Integer affectedUserID, String permissionType) { + + // A system administrator has full access to everything. + if(checkSystemAdministratorAccess(userID)) + return true; + + // Check existence of requested permission + UserPermissionExample example = new UserPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andAffected_user_idEqualTo(affectedUserID).andPermissionEqualTo(permissionType); + return userPermissionDAO.countByExample(example) > 0; + + } + + /** + * Checks whether a user has the specified type of access to the affected + * connection. + * + * @param userID The ID of the user to check. + * @param affectedConnectionID The connection 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 checkConnectionAccess(int userID, Integer affectedConnectionID, String permissionType) { + + // A system administrator has full access to everything. + if(checkSystemAdministratorAccess(userID)) + return true; + + // Check existence of requested permission + ConnectionPermissionExample example = new ConnectionPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andConnection_idEqualTo(affectedConnectionID).andPermissionEqualTo(permissionType); + return connectionPermissionDAO.countByExample(example) > 0; + + } + + /** + * Checks whether a user has the specified type of access to the system. + * + * @param userID The ID of the user to check. + * @param systemPermissionType The type of permission to check for. + * @return true if the specified permission is granted, false otherwise. + */ + private boolean checkSystemAccess(int userID, String systemPermissionType) { + + // A system administrator has full access to everything. + if(checkSystemAdministratorAccess(userID)) + return true; + + // Check existence of requested permission + SystemPermissionExample example = new SystemPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andPermissionEqualTo(systemPermissionType); + return systemPermissionDAO.countByExample(example) > 0; + + } + + + /** + * Checks whether a user has system administrator access to the system. + * + * @param userID The ID of the user to check. + * @return true if the system administrator access exists, false otherwise. + */ + private boolean checkSystemAdministratorAccess(int userID) { + + // Check existence of system administrator permission + SystemPermissionExample example = new SystemPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID). + andPermissionEqualTo(MySQLConstants.SYSTEM_ADMINISTER); + return systemPermissionDAO.countByExample(example) > 0; + } + + /** + * Find the list of the IDs of all users 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 user IDs this user has the specified access to. + */ + public List retrieveUserIDs(int userID, String permissionType) { + + // A system administrator has access to all users. + if(checkSystemAdministratorAccess(userID)) + return userService.getAllUserIDs(); + + // Query all user permissions for the given user and permission type + UserPermissionExample example = new UserPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andPermissionEqualTo(permissionType); + example.setDistinct(true); + List userPermissions = + userPermissionDAO.selectByExample(example); + + // Convert result into list of IDs + List userIDs = new ArrayList(userPermissions.size()); + for(UserPermissionKey permission : userPermissions) + userIDs.add(permission.getAffected_user_id()); + + return userIDs; + + } + + /** + * Find the list of the IDs of all connections 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 IDs this user has the specified access + * to. + */ + public List retrieveConnectionIDs(int userID, + String permissionType) { + + // A system administrator has access to all connections. + if(checkSystemAdministratorAccess(userID)) + return connectionService.getAllConnectionIDs(); + + // Query all connection permissions for the given user and permission type + ConnectionPermissionExample example = new ConnectionPermissionExample(); + example.createCriteria().andUser_idEqualTo(userID).andPermissionEqualTo(permissionType); + example.setDistinct(true); + List connectionPermissions = + connectionPermissionDAO.selectByExample(example); + + // Convert result into list of IDs + List connectionIDs = new ArrayList(connectionPermissions.size()); + for(ConnectionPermissionKey permission : connectionPermissions) + connectionIDs.add(permission.getConnection_id()); + + return connectionIDs; + + } + + /** + * Retrieve all existing usernames that the given user has permission to + * perform the given operation upon. + * + * @param userID The user whose permissions should be checked. + * @param permissionType The permission to check. + * @return A set of all usernames for which the given user has the given + * permission. + */ + public Set retrieveUsernames(int userID, String permissionType) { + + // A system administrator has access to all users. + if(checkSystemAdministratorAccess(userID)) + return userService.getAllUsernames(); + + // List of all user IDs for which this user has read access + List userIDs = + retrieveUserIDs(userID, MySQLConstants.USER_READ); + + // Query all associated users + return userService.translateUsernames(userIDs).keySet(); + + } + + /** + * Retrieve all existing connection names that the given user has permission + * to perform the given operation upon. + * + * @param userID The user whose permissions should be checked. + * @param permissionType The permission to check. + * @return A set of all connection names for which the given user has the + * given permission. + */ + public Set retrieveConnectionNames(int userID, String permissionType) { + + // A system administrator has access to all connections. + if(checkSystemAdministratorAccess(userID)) + return connectionService.getAllConnectionNames(); + + // List of all connection IDs for which this connection has read access + List connectionIDs = + retrieveConnectionIDs(userID, MySQLConstants.CONNECTION_READ); + + // Query all associated connections + return connectionService.translateNames(connectionIDs).keySet(); + + } + + /** + * Retrieves all user 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 user permissions granted to the user having the + * given ID. + */ + public Set retrieveUserPermissions(int userID) { + + // Set of all permissions + Set permissions = new HashSet(); + + // Query all user permissions + UserPermissionExample userPermissionExample = new UserPermissionExample(); + userPermissionExample.createCriteria().andUser_idEqualTo(userID); + List userPermissions = + userPermissionDAO.selectByExample(userPermissionExample); + + // Get list of affected user IDs + List affectedUserIDs = new ArrayList(); + for(UserPermissionKey userPermission : userPermissions) + affectedUserIDs.add(userPermission.getAffected_user_id()); + + // Get corresponding usernames + Map affectedUsers = + userService.retrieveUsernames(affectedUserIDs); + + // Add user permissions + for(UserPermissionKey userPermission : userPermissions) { + + // Construct permission from data + UserPermission permission = new UserPermission( + UserPermission.Type.valueOf(userPermission.getPermission()), + affectedUsers.get(userPermission.getAffected_user_id()) + ); + + // Add to set + permissions.add(permission); + + } + + return permissions; + + } + + /** + * Retrieves all connection 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 permissions granted to the user having + * the given ID. + */ + public Set retrieveConnectionPermissions(int userID) { + + // Set of all permissions + Set permissions = new HashSet(); + + // Query all connection permissions + ConnectionPermissionExample connectionPermissionExample = new ConnectionPermissionExample(); + connectionPermissionExample.createCriteria().andUser_idEqualTo(userID); + List connectionPermissions = + connectionPermissionDAO.selectByExample(connectionPermissionExample); + + // Get list of affected connection IDs + List connectionIDs = new ArrayList(); + for(ConnectionPermissionKey connectionPermission : connectionPermissions) + connectionIDs.add(connectionPermission.getConnection_id()); + + // Get corresponding names + Map affectedConnections = + connectionService.retrieveNames(connectionIDs); + + // Add connection permissions + for(ConnectionPermissionKey connectionPermission : connectionPermissions) { + + // Construct permission from data + ConnectionPermission permission = new ConnectionPermission( + ConnectionPermission.Type.valueOf(connectionPermission.getPermission()), + affectedConnections.get(connectionPermission.getConnection_id()) + ); + + // Add to set + permissions.add(permission); + + } + + return permissions; + + } + + /** + * Retrieves all system 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 system permissions granted to the user having the + * given ID. + */ + public Set retrieveSystemPermissions(int userID) { + + // Set of all permissions + Set permissions = new HashSet(); + + // And finally, system permissions + SystemPermissionExample systemPermissionExample = new SystemPermissionExample(); + systemPermissionExample.createCriteria().andUser_idEqualTo(userID); + List systemPermissions = + systemPermissionDAO.selectByExample(systemPermissionExample); + for(SystemPermissionKey systemPermission : systemPermissions) { + + // User creation permission + if(systemPermission.getPermission().equals(MySQLConstants.SYSTEM_USER_CREATE)) + permissions.add(new SystemPermission(SystemPermission.Type.CREATE_USER)); + + // System creation permission + else if(systemPermission.getPermission().equals(MySQLConstants.SYSTEM_CONNECTION_CREATE)) + permissions.add(new SystemPermission(SystemPermission.Type.CREATE_CONNECTION)); + + // System administration permission + else if(systemPermission.getPermission().equals(MySQLConstants.SYSTEM_ADMINISTER)) + permissions.add(new SystemPermission(SystemPermission.Type.ADMINISTER)); + + } + + return permissions; + + } + + /** + * Retrieves all 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 permissions granted to the user having the given + * ID. + */ + public Set retrieveAllPermissions(int userID) { + + // Set which will contain all permissions + Set allPermissions = new HashSet(); + + // Add user permissions + allPermissions.addAll(retrieveUserPermissions(userID)); + + // Add connection permissions + allPermissions.addAll(retrieveConnectionPermissions(userID)); + + // Add system permissions + allPermissions.addAll(retrieveSystemPermissions(userID)); + + return allPermissions; + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SHA256PasswordEncryptionService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SHA256PasswordEncryptionService.java new file mode 100644 index 000000000..04fea1ae7 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SHA256PasswordEncryptionService.java @@ -0,0 +1,90 @@ + +package net.sourceforge.guacamole.net.auth.mysql.service; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import javax.xml.bind.DatatypeConverter; + +/** + * Provides a SHA-256 based implementation of the password encryption functionality. + * @author James Muehlner + */ +public class SHA256PasswordEncryptionService implements PasswordEncryptionService { + + @Override + public boolean checkPassword(String password, byte[] hashedPassword, + byte[] salt) { + + // Compare bytes of password in credentials against hashed password + byte[] passwordBytes = createPasswordHash(password, salt); + return Arrays.equals(passwordBytes, hashedPassword); + + } + + @Override + public byte[] createPasswordHash(String password, byte[] salt) { + + try { + + // Build salted password + StringBuilder builder = new StringBuilder(); + builder.append(password); + builder.append(DatatypeConverter.printHexBinary(salt)); + + // Hash UTF-8 bytes of salted password + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(builder.toString().getBytes("UTF-8")); + return md.digest(); + + } + + // Should not happen + catch (UnsupportedEncodingException ex) { + throw new RuntimeException(ex); + } + + // Should not happen + catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + + } +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SaltService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SaltService.java new file mode 100644 index 000000000..0d194c9a0 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SaltService.java @@ -0,0 +1,48 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +package net.sourceforge.guacamole.net.auth.mysql.service; + +/** + * A service to generate password salts. + * @author James Muehlner + */ +public interface SaltService { + /** + * Generates a new String that can be used as a password salt. + * @return a new salt for password encryption. + */ + public byte[] generateSalt(); +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SecureRandomSaltService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SecureRandomSaltService.java new file mode 100644 index 000000000..35caba240 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/SecureRandomSaltService.java @@ -0,0 +1,60 @@ + +package net.sourceforge.guacamole.net.auth.mysql.service; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import java.security.SecureRandom; + +/** + * Generates password salts via SecureRandom. + * @author James Muehlner + */ +public class SecureRandomSaltService implements SaltService { + + /** + * Instance of SecureRandom for generating the salt. + */ + private SecureRandom secureRandom = new SecureRandom(); + + @Override + public byte[] generateSalt() { + byte[] salt = new byte[32]; + secureRandom.nextBytes(salt); + return salt; + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UserService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UserService.java new file mode 100644 index 000000000..fe3fa8b34 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UserService.java @@ -0,0 +1,381 @@ + +package net.sourceforge.guacamole.net.auth.mysql.service; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-auth-mysql. + * + * The Initial Developer of the Original Code is + * James Muehlner. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** 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.GuacamoleException; +import net.sourceforge.guacamole.net.auth.Credentials; +import net.sourceforge.guacamole.net.auth.mysql.MySQLUser; +import net.sourceforge.guacamole.net.auth.mysql.dao.UserMapper; +import net.sourceforge.guacamole.net.auth.mysql.model.User; +import net.sourceforge.guacamole.net.auth.mysql.model.UserExample; +import net.sourceforge.guacamole.net.auth.mysql.model.UserWithBLOBs; + +/** + * Service which provides convenience methods for creating, retrieving, and + * manipulating users. + * + * @author Michael Jumper, James Muehlner + */ +public class UserService { + + /** + * DAO for accessing users. + */ + @Inject + private UserMapper userDAO; + + /** + * Provider for creating users. + */ + @Inject + private Provider mySQLUserProvider; + + /** + * Service for checking permissions. + */ + @Inject + private PermissionCheckService permissionCheckService; + + /** + * Service for encrypting passwords. + */ + @Inject + private PasswordEncryptionService passwordService; + + /** + * Service for generating random salts. + */ + @Inject + private SaltService saltService; + + /** + * Create a new MySQLUser based on the provided User. + * + * @param user The User to use when populating the data of the given + * MySQLUser. + * @return A new MySQLUser object, populated with the data of the given + * user. + * + * @throws GuacamoleException If an error occurs while reading the data + * of the provided User. + */ + public MySQLUser toMySQLUser(net.sourceforge.guacamole.net.auth.User user) throws GuacamoleException { + MySQLUser mySQLUser = mySQLUserProvider.get(); + mySQLUser.init(user); + return mySQLUser; + } + + /** + * Create a new MySQLUser based on the provided database record. + * + * @param user The database record describing the user. + * @return A new MySQLUser object, populated with the data of the given + * database record. + */ + private MySQLUser toMySQLUser(UserWithBLOBs user) { + + // Retrieve user from provider + MySQLUser mySQLUser = mySQLUserProvider.get(); + + // Init with data from given database user + mySQLUser.init( + user.getUser_id(), + user.getUsername(), + null, + permissionCheckService.retrieveAllPermissions(user.getUser_id()) + ); + + // Return new user + return mySQLUser; + + } + + /** + * Retrieves the user having the given ID from the database. + * + * @param id The ID of the user to retrieve. + * @return The existing MySQLUser object if found, null otherwise. + */ + public MySQLUser retrieveUser(int id) { + + // Query user by ID + UserWithBLOBs user = userDAO.selectByPrimaryKey(id); + + // If no user found, return null + if(user == null) + return null; + + // Otherwise, return found user + return toMySQLUser(user); + + } + + /** + * Retrieves the user having the given username from the database. + * + * @param name The username of the user to retrieve. + * @return The existing MySQLUser object if found, null otherwise. + */ + public MySQLUser retrieveUser(String name) { + + // Query user by ID + UserExample example = new UserExample(); + example.createCriteria().andUsernameEqualTo(name); + List users = userDAO.selectByExampleWithBLOBs(example); + + // If no user found, return null + if(users.isEmpty()) + return null; + + // Otherwise, return found user + return toMySQLUser(users.get(0)); + + } + + /** + * Retrieves the user corresponding to the given credentials from the + * database. + * + * @param credentials The credentials to use when locating the user. + * @return The existing MySQLUser object if the credentials given are + * valid, null otherwise. + */ + public MySQLUser retrieveUser(Credentials credentials) { + + // No null users in database + if (credentials.getUsername() == null) + return null; + + // Query user + UserExample userExample = new UserExample(); + userExample.createCriteria().andUsernameEqualTo(credentials.getUsername()); + List users = userDAO.selectByExampleWithBLOBs(userExample); + + // Check that a user was found + if (users.isEmpty()) + return null; + + // Assert only one user found + assert users.size() == 1 : "Multiple users with same username."; + + // Get first (and only) user + UserWithBLOBs user = users.get(0); + + // Check password, if invalid return null + if (!passwordService.checkPassword(credentials.getPassword(), + user.getPassword_hash(), user.getPassword_salt())) + return null; + + // Return found user + return toMySQLUser(user); + + } + + /** + * Retrieves a translation map of usernames to their corresponding IDs. + * + * @param ids The IDs of the users to retrieve the usernames of. + * @return A map containing the names of all users and their corresponding + * IDs. + */ + public Map translateUsernames(List 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 users having the given IDs + UserExample example = new UserExample(); + example.createCriteria().andUser_idIn(ids); + List users = + userDAO.selectByExample(example); + + // Produce set of names + for (User user : users) + names.put(user.getUsername(), user.getUser_id()); + + return names; + + } + + /** + * Retrieves a map of all usernames for the given IDs. + * + * @param ids The IDs of the users to retrieve the usernames of. + * @return A map containing the names of all users and their corresponding + * IDs. + */ + public Map retrieveUsernames(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 users having the given IDs + UserExample example = new UserExample(); + example.createCriteria().andUser_idIn(Lists.newArrayList(ids)); + List users = + userDAO.selectByExample(example); + + // Produce set of names + for (User user : users) + names.put(user.getUser_id(), user.getUsername()); + + return names; + + } + + /** + * Creates a new user having the given username and password. + * + * @param username The username to assign to the new user. + * @param password The password to assign to the new user. + * @return A new MySQLUser containing the data of the newly created + * user. + */ + public MySQLUser createUser(String username, String password) { + + // Initialize database user + UserWithBLOBs user = new UserWithBLOBs(); + user.setUsername(username); + + // Set password if specified + if (password != null) { + byte[] salt = saltService.generateSalt(); + user.setPassword_salt(salt); + user.setPassword_hash( + passwordService.createPasswordHash(password, salt)); + } + + // Create user + userDAO.insert(user); + return toMySQLUser(user); + + } + + /** + * Deletes the user having the given ID from the database. + * @param user_id The ID of the user to delete. + */ + public void deleteUser(int user_id) { + userDAO.deleteByPrimaryKey(user_id); + } + + /** + * Updates the user in the database corresponding to the given MySQLUser. + * + * @param mySQLUser The MySQLUser to update (save) to the database. This + * user must already exist. + */ + public void updateUser(MySQLUser mySQLUser) { + + UserWithBLOBs user = new UserWithBLOBs(); + user.setUser_id(mySQLUser.getUserID()); + user.setUsername(mySQLUser.getUsername()); + + // Set password if specified + if (mySQLUser.getPassword() != null) { + byte[] salt = saltService.generateSalt(); + user.setPassword_salt(salt); + user.setPassword_hash( + passwordService.createPasswordHash(mySQLUser.getPassword(), salt)); + } + + // Update the user in the database + userDAO.updateByPrimaryKeySelective(user); + + } + + /** + * Get the usernames of all the users defined in the system. + * + * @return A Set of usernames of all the users defined in the system. + */ + public Set getAllUsernames() { + + // Set of all present usernames + Set usernames = new HashSet(); + + // Query all usernames + List users = + userDAO.selectByExample(new UserExample()); + for (User user : users) + usernames.add(user.getUsername()); + + return usernames; + + } + + /** + * Get the user IDs of all the users defined in the system. + * + * @return A list of user IDs of all the users defined in the system. + */ + public List getAllUserIDs() { + + // Set of all present user IDs + List userIDs = new ArrayList(); + + // Query all user IDs + List users = + userDAO.selectByExample(new UserExample()); + for (User user : users) + userIDs.add(user.getUser_id()); + + return userIDs; + + } + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/package-info.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/package-info.java new file mode 100644 index 000000000..4cc071f2d --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/package-info.java @@ -0,0 +1,7 @@ + +/** + * Service classes which help fill the needs of the MySQL authentication + * provider. + */ +package net.sourceforge.guacamole.net.auth.mysql.service; + diff --git a/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml b/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml new file mode 100644 index 000000000..879ed22c5 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/resources/generatorConfig.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + +
+ + + + + +
+ +
+
+