GUAC-801 Merge master.

This commit is contained in:
James Muehlner
2015-03-10 18:52:03 -07:00
48 changed files with 3026 additions and 564 deletions

View File

@@ -51,8 +51,8 @@ in the library directory configured in guacamole.properties.
created in the target/ subdirectory of the current directory.
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.
place the .jar files from the extracted database-specific subdirectory in
the library directory specified in guacamole.properties.
You will likely need to do this as root.
@@ -60,10 +60,10 @@ in the library directory configured in guacamole.properties.
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
5) Set up your database to authenticate Guacamole users
A schema file is provided in the schema directory for creating
the guacamole authentication tables in your MySQL database.
the guacamole authentication tables in your database of choice.
Additionally, a script is provided to create a default admin user
with username 'guacadmin' and password 'guacadmin'. This user can
@@ -90,6 +90,17 @@ in the library directory configured in guacamole.properties.
mysql-disallow-simultaneous-connections: true
For PostgreSQL, the properties are the same, but have different prefixes:
# Database connection configuration
postgresql-hostname: database.host.name
postgresql-port: 5432
postgresql-database: guacamole.database.name
postgresql-username: user
postgresql-password: pass
postgresql-disallow-simultaneous-connections: true
------------------------------------------------------------
Reporting problems

View File

@@ -37,8 +37,8 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating users. This service will automatically enforce the
* permissions of the current user.
* manipulating objects within directories. This service will automatically
* enforce the permissions of the current user.
*
* @author Michael Jumper
* @param <InternalType>
@@ -215,52 +215,77 @@ public abstract class DirectoryObjectService<InternalType extends DirectoryObjec
}
/**
* Returns whether the contents of the given model are valid and can be
* used to create a new object as-is. The object does not yet exist in the
* database, but the user desires to create a new object with the given
* model. This function will be called prior to any creation operation, and
* provides a means for the implementation to abort prior to completion. The
* default implementation does nothing.
* Called before any object is created through this directory object
* service. This function serves as a final point of validation before
* the create operation occurs. In its default implementation,
* beforeCreate() performs basic permissions checks.
*
* @param user
* The user creating the object.
*
* @param model
* The model to validate.
* The model of the object being created.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void validateNewModel(AuthenticatedUser user,
protected void beforeCreate(AuthenticatedUser user,
ModelType model ) throws GuacamoleException {
// By default, do nothing.
// Verify permission to create objects
if (!user.getUser().isAdministrator() && !hasCreatePermission(user))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Returns whether the given model is valid and can be used to update an
* existing object as-is. The object already exists in the database, but the
* user desires to update the object to the given model. This function will
* be called prior to update operation, and provides a means for the
* implementation to abort prior to completion. The default implementation
* does nothing.
* Called before any object is updated through this directory object
* service. This function serves as a final point of validation before
* the update operation occurs. In its default implementation,
* beforeUpdate() performs basic permissions checks.
*
* @param user
* The user updating the existing object.
*
* @param model
* The model to validate.
* The model of the object being updated.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void validateExistingModel(AuthenticatedUser user,
protected void beforeUpdate(AuthenticatedUser user,
ModelType model) throws GuacamoleException {
// By default, do nothing.
if (!hasObjectPermission(user, model.getIdentifier(), ObjectPermission.Type.UPDATE))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Called before any object is deleted through this directory object
* service. This function serves as a final point of validation before
* the delete operation occurs. In its default implementation,
* beforeDelete() performs basic permissions checks.
*
* @param user
* The user deleting the existing object.
*
* @param identifier
* The identifier of the object being deleted.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeDelete(AuthenticatedUser user,
String identifier) throws GuacamoleException {
// Verify permission to delete objects
if (!hasObjectPermission(user, identifier, ObjectPermission.Type.DELETE))
throw new GuacamoleSecurityException("Permission denied.");
}
@@ -400,12 +425,8 @@ public abstract class DirectoryObjectService<InternalType extends DirectoryObjec
public InternalType createObject(AuthenticatedUser user, ExternalType object)
throws GuacamoleException {
// Only create object if user has permission to do so
if (user.getUser().isAdministrator() || hasCreatePermission(user)) {
// Validate object prior to creation
ModelType model = getModelInstance(user, object);
validateNewModel(user, model);
beforeCreate(user, model);
// Create object
getObjectMapper().insert(model);
@@ -414,10 +435,6 @@ public abstract class DirectoryObjectService<InternalType extends DirectoryObjec
getPermissionMapper().insert(getImplicitPermissions(user, model));
return getObjectInstance(user, model);
}
// User lacks permission to create
throw new GuacamoleSecurityException("Permission denied.");
}
@@ -438,14 +455,10 @@ public abstract class DirectoryObjectService<InternalType extends DirectoryObjec
public void deleteObject(AuthenticatedUser user, String identifier)
throws GuacamoleException {
// Only delete object if user has permission to do so
if (hasObjectPermission(user, identifier, ObjectPermission.Type.DELETE)) {
getObjectMapper().delete(identifier);
return;
}
beforeDelete(user, identifier);
// User lacks permission to delete
throw new GuacamoleSecurityException("Permission denied.");
// Delete object
getObjectMapper().delete(identifier);
}
@@ -466,20 +479,11 @@ public abstract class DirectoryObjectService<InternalType extends DirectoryObjec
public void updateObject(AuthenticatedUser user, InternalType object)
throws GuacamoleException {
// Only update object if user has permission to do so
if (hasObjectPermission(user, object.getIdentifier(), ObjectPermission.Type.UPDATE)) {
// Validate object prior to creation
ModelType model = object.getModel();
validateExistingModel(user, model);
beforeUpdate(user, model);
// Update object
getObjectMapper().update(model);
return;
}
// User lacks permission to update
throw new GuacamoleSecurityException("Permission denied.");
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.base;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
/**
* Common base class for objects that will ultimately be made available through
* the Directory class. All such objects will need the same base set of queries
* to fulfill the needs of the Directory class.
*
* @author Michael Jumper
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class GroupedDirectoryObject<ModelType extends GroupedObjectModel>
extends DirectoryObject<ModelType> {
/**
* Returns the identifier of the parent connection group, which cannot be
* null. If the parent is the root connection group, this will be
* RootConnectionGroup.IDENTIFIER.
*
* @return
* The identifier of the parent connection group.
*/
public String getParentIdentifier() {
// Translate null parent to proper identifier
String parentIdentifier = getModel().getParentIdentifier();
if (parentIdentifier == null)
return RootConnectionGroup.IDENTIFIER;
return parentIdentifier;
}
/**
* Sets the identifier of the associated parent connection group. If the
* parent is the root connection group, this should be
* RootConnectionGroup.IDENTIFIER.
*
* @param parentIdentifier
* The identifier of the connection group to associate as this object's
* parent.
*/
public void setParentIdentifier(String parentIdentifier) {
// Translate root identifier back into null
if (parentIdentifier != null
&& parentIdentifier.equals(RootConnectionGroup.IDENTIFIER))
parentIdentifier = null;
getModel().setParentIdentifier(parentIdentifier);
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects that can be within connection groups. This service will
* automatically enforce the permissions of the current user.
*
* @author Michael Jumper
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*
* @param <ModelType>
* The underlying model object used to represent InternalType in the
* database.
*/
public abstract class GroupedDirectoryObjectService<InternalType extends GroupedDirectoryObject<ModelType>,
ExternalType, ModelType extends GroupedObjectModel>
extends DirectoryObjectService<InternalType, ExternalType, ModelType> {
/**
* Returns the set of parent connection groups that are modified by the
* given model object (by virtue of the object changing parent groups). If
* the model is not changing parents, the resulting collection will be
* empty.
*
* @param user
* The user making the given changes to the model.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* A collection of the identifiers of all parent connection groups
* that will be affected (updated) by the change.
*
* @throws GuacamoleException
* If an error occurs while determining which parent connection groups
* are affected.
*/
protected Collection<String> getModifiedGroups(AuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// Get old parent identifier
String oldParentIdentifier = null;
if (identifier != null) {
ModelType current = retrieveObject(user, identifier).getModel();
oldParentIdentifier = current.getParentIdentifier();
}
// Get new parent identifier
String parentIdentifier = null;
if (model != null) {
parentIdentifier = model.getParentIdentifier();
// If both parents have the same identifier, nothing has changed
if (parentIdentifier != null && parentIdentifier.equals(oldParentIdentifier))
return Collections.EMPTY_LIST;
}
// Return collection of all non-root groups involved
Collection<String> groups = new ArrayList<String>(2);
if (oldParentIdentifier != null) groups.add(oldParentIdentifier);
if (parentIdentifier != null) groups.add(parentIdentifier);
return groups;
}
/**
* Returns whether the given user has permission to modify the parent
* connection groups affected by the modifications made to the given model
* object.
*
* @param user
* The user who changed the model object.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* true if the user has update permission for all modified groups,
* false otherwise.
*
* @throws GuacamoleException
* If an error occurs while determining which parent connection groups
* are affected.
*/
protected boolean canUpdateModifiedGroups(AuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// If user is an administrator, no need to check
if (user.getUser().isAdministrator())
return true;
// Verify that we have permission to modify any modified groups
Collection<String> modifiedGroups = getModifiedGroups(user, identifier, model);
if (!modifiedGroups.isEmpty()) {
ObjectPermissionSet permissionSet = user.getUser().getConnectionGroupPermissions();
Collection<String> updateableGroups = permissionSet.getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.UPDATE),
modifiedGroups
);
return updateableGroups.size() == modifiedGroups.size();
}
return true;
}
@Override
protected void beforeCreate(AuthenticatedUser user,
ModelType model) throws GuacamoleException {
super.beforeCreate(user, model);
// Validate that we can update all applicable parent groups
if (!canUpdateModifiedGroups(user, null, model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeUpdate(AuthenticatedUser user,
ModelType model) throws GuacamoleException {
super.beforeUpdate(user, model);
// Validate that we can update all applicable parent groups
if (!canUpdateModifiedGroups(user, model.getIdentifier(), model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeDelete(AuthenticatedUser user,
String identifier) throws GuacamoleException {
super.beforeDelete(user, identifier);
// Validate that we can update all applicable parent groups
if (!canUpdateModifiedGroups(user, identifier, null))
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.base;
/**
* Object representation of a Guacamole object, such as a user or connection,
* as represented in the database.
*
* @author Michael Jumper
*/
public abstract class GroupedObjectModel extends ObjectModel {
/**
* The unique identifier which identifies the parent of this object.
*/
private String parentIdentifier;
/**
* Creates a new, empty object.
*/
public GroupedObjectModel() {
}
/**
* Returns the identifier of the parent connection group, or null if the
* parent connection group is the root connection group.
*
* @return
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public String getParentIdentifier() {
return parentIdentifier;
}
/**
* Sets the identifier of the parent connection group.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public void setParentIdentifier(String parentIdentifier) {
this.parentIdentifier = parentIdentifier;
}
}

View File

@@ -22,7 +22,7 @@
package org.glyptodon.guacamole.auth.jdbc.connection;
import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedObjectModel;
/**
* Object representation of a Guacamole connection, as represented in the
@@ -30,13 +30,7 @@ import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel;
*
* @author Michael Jumper
*/
public class ConnectionModel extends ObjectModel {
/**
* The identifier of the parent connection group in the database, or null
* if the parent connection group is the root group.
*/
private String parentIdentifier;
public class ConnectionModel extends GroupedObjectModel {
/**
* The human-readable name associated with this connection.
@@ -95,29 +89,6 @@ public class ConnectionModel extends ObjectModel {
this.protocol = protocol;
}
/**
* Returns the identifier of the parent connection group, or null if the
* parent connection group is the root connection group.
*
* @return
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public String getParentIdentifier() {
return parentIdentifier;
}
/**
* Sets the identifier of the parent connection group.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public void setParentIdentifier(String parentIdentifier) {
this.parentIdentifier = parentIdentifier;
}
@Override
public String getIdentifier() {

View File

@@ -33,11 +33,11 @@ import java.util.Map;
import java.util.Set;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectMapper;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectService;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedDirectoryObjectService;
import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.glyptodon.guacamole.net.GuacamoleSocket;
@@ -55,7 +55,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
*
* @author Michael Jumper, James Muehlner
*/
public class ConnectionService extends DirectoryObjectService<ModeledConnection, Connection, ConnectionModel> {
public class ConnectionService extends GroupedDirectoryObjectService<ModeledConnection, Connection, ConnectionModel> {
/**
* Mapper for accessing connections.
@@ -148,9 +148,11 @@ public class ConnectionService extends DirectoryObjectService<ModeledConnection,
}
@Override
protected void validateNewModel(AuthenticatedUser user,
protected void beforeCreate(AuthenticatedUser user,
ConnectionModel model) throws GuacamoleException {
super.beforeCreate(user, model);
// Name must not be blank
if (model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection names must not be blank.");
@@ -163,9 +165,11 @@ public class ConnectionService extends DirectoryObjectService<ModeledConnection,
}
@Override
protected void validateExistingModel(AuthenticatedUser user,
protected void beforeUpdate(AuthenticatedUser user,
ConnectionModel model) throws GuacamoleException {
super.beforeUpdate(user, model);
// Name must not be blank
if (model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection names must not be blank.");

View File

@@ -25,10 +25,9 @@ package org.glyptodon.guacamole.auth.jdbc.connection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.List;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObject;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedDirectoryObject;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionRecord;
@@ -42,7 +41,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
* @author James Muehlner
* @author Michael Jumper
*/
public class ModeledConnection extends DirectoryObject<ConnectionModel>
public class ModeledConnection extends GroupedDirectoryObject<ConnectionModel>
implements Connection {
/**
@@ -84,30 +83,6 @@ public class ModeledConnection extends DirectoryObject<ConnectionModel>
getModel().setName(name);
}
@Override
public String getParentIdentifier() {
// Translate null parent to proper identifier
String parentIdentifier = getModel().getParentIdentifier();
if (parentIdentifier == null)
return RootConnectionGroup.IDENTIFIER;
return parentIdentifier;
}
@Override
public void setParentIdentifier(String parentIdentifier) {
// Translate root identifier back into null
if (parentIdentifier != null
&& parentIdentifier.equals(RootConnectionGroup.IDENTIFIER))
parentIdentifier = null;
getModel().setParentIdentifier(parentIdentifier);
}
@Override
public GuacamoleConfiguration getConfiguration() {

View File

@@ -22,7 +22,7 @@
package org.glyptodon.guacamole.auth.jdbc.connectiongroup;
import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedObjectModel;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
/**
@@ -31,13 +31,7 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup;
*
* @author Michael Jumper
*/
public class ConnectionGroupModel extends ObjectModel {
/**
* The identifier of the parent connection group in the database, or null
* if the parent connection group is the root group.
*/
private String parentIdentifier;
public class ConnectionGroupModel extends GroupedObjectModel {
/**
* The human-readable name associated with this connection group.
@@ -75,29 +69,6 @@ public class ConnectionGroupModel extends ObjectModel {
this.name = name;
}
/**
* Returns the identifier of the parent connection group, or null if the
* parent connection group is the root connection group.
*
* @return
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public String getParentIdentifier() {
return parentIdentifier;
}
/**
* Sets the identifier of the parent connection group.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public void setParentIdentifier(String parentIdentifier) {
this.parentIdentifier = parentIdentifier;
}
/**
* Returns the type of this connection group, such as organizational or
* balancing.

View File

@@ -27,11 +27,12 @@ import com.google.inject.Provider;
import java.util.Set;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectMapper;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectService;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedDirectoryObjectService;
import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper;
import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.glyptodon.guacamole.net.GuacamoleSocket;
@@ -48,7 +49,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
*
* @author Michael Jumper, James Muehlner
*/
public class ConnectionGroupService extends DirectoryObjectService<ModeledConnectionGroup,
public class ConnectionGroupService extends GroupedDirectoryObjectService<ModeledConnectionGroup,
ConnectionGroup, ConnectionGroupModel> {
/**
@@ -130,9 +131,11 @@ public class ConnectionGroupService extends DirectoryObjectService<ModeledConnec
}
@Override
protected void validateNewModel(AuthenticatedUser user,
protected void beforeCreate(AuthenticatedUser user,
ConnectionGroupModel model) throws GuacamoleException {
super.beforeCreate(user, model);
// Name must not be blank
if (model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection group names must not be blank.");
@@ -145,9 +148,11 @@ public class ConnectionGroupService extends DirectoryObjectService<ModeledConnec
}
@Override
protected void validateExistingModel(AuthenticatedUser user,
protected void beforeUpdate(AuthenticatedUser user,
ConnectionGroupModel model) throws GuacamoleException {
super.beforeUpdate(user, model);
// Name must not be blank
if (model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection group names must not be blank.");
@@ -162,6 +167,20 @@ public class ConnectionGroupService extends DirectoryObjectService<ModeledConnec
}
// Verify that this connection group's location does not create a cycle
String relativeParentIdentifier = model.getParentIdentifier();
while (relativeParentIdentifier != null) {
// Abort if cycle is detected
if (relativeParentIdentifier.equals(model.getIdentifier()))
throw new GuacamoleUnsupportedException("A connection group may not contain itself.");
// Advance to next parent
ModeledConnectionGroup relativeParentGroup = retrieveObject(user, relativeParentIdentifier);
relativeParentIdentifier = relativeParentGroup.getModel().getParentIdentifier();
}
}
/**

View File

@@ -24,10 +24,10 @@ package org.glyptodon.guacamole.auth.jdbc.connectiongroup;
import com.google.inject.Inject;
import java.util.Set;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObject;
import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionService;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.auth.jdbc.base.GroupedDirectoryObject;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
@@ -38,7 +38,7 @@ import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
*
* @author James Muehlner
*/
public class ModeledConnectionGroup extends DirectoryObject<ConnectionGroupModel>
public class ModeledConnectionGroup extends GroupedDirectoryObject<ConnectionGroupModel>
implements ConnectionGroup {
/**
@@ -75,30 +75,6 @@ public class ModeledConnectionGroup extends DirectoryObject<ConnectionGroupModel
getModel().setName(name);
}
@Override
public String getParentIdentifier() {
// Translate null parent to proper identifier
String parentIdentifier = getModel().getParentIdentifier();
if (parentIdentifier == null)
return RootConnectionGroup.IDENTIFIER;
return parentIdentifier;
}
@Override
public void setParentIdentifier(String parentIdentifier) {
// Translate root identifier back into null
if (parentIdentifier != null
&& parentIdentifier.equals(RootConnectionGroup.IDENTIFIER))
parentIdentifier = null;
getModel().setParentIdentifier(parentIdentifier);
}
@Override
public GuacamoleSocket connect(GuacamoleClientInformation info)
throws GuacamoleException {

View File

@@ -185,9 +185,8 @@ public abstract class ObjectPermissionService
ModeledUser targetUser, ObjectPermission.Type type,
String identifier) throws GuacamoleException {
// Only an admin can read permissions that aren't his own
if (user.getUser().getIdentifier().equals(targetUser.getIdentifier())
|| user.getUser().isAdministrator()) {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetUser)) {
// Read permission from database, return null if not found
ObjectPermissionModel model = getPermissionMapper().selectOne(targetUser.getModel(), type, identifier);
@@ -237,14 +236,11 @@ public abstract class ObjectPermissionService
if (identifiers.isEmpty())
return identifiers;
// Determine whether the user is an admin
boolean isAdmin = user.getUser().isAdministrator();
// Only an admin can read permissions that aren't his own
if (isAdmin || user.getUser().getIdentifier().equals(targetUser.getIdentifier())) {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetUser)) {
// If user is an admin, everything is accessible
if (isAdmin)
if (user.getUser().isAdministrator())
return identifiers;
// Otherwise, return explicitly-retrievable identifiers

View File

@@ -30,6 +30,8 @@ import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.user.ModeledUser;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.auth.permission.PermissionSet;
@@ -141,6 +143,42 @@ public abstract class PermissionService<PermissionSetType extends PermissionSet<
}
/**
* Determines whether the given user can read the permissions currently
* granted to the given target user. If the reading user and the target
* user are not the same, then explicit READ or SYSTEM_ADMINISTER access is
* required.
*
* @param user
* The user attempting to read permissions.
*
* @param targetUser
* The user whose permissions are being read.
*
* @return
* true if permission is granted, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while checking permission status, or if
* permission is denied to read the current user's permissions.
*/
protected boolean canReadPermissions(AuthenticatedUser user,
ModeledUser targetUser) throws GuacamoleException {
// A user can always read their own permissions
if (user.getUser().getIdentifier().equals(targetUser.getIdentifier()))
return true;
// A system adminstrator can do anything
if (user.getUser().isAdministrator())
return true;
// Can read permissions on target user if explicit READ is granted
ObjectPermissionSet userPermissionSet = user.getUser().getUserPermissions();
return userPermissionSet.hasPermission(ObjectPermission.Type.READ, targetUser.getIdentifier());
}
/**
* Returns a permission set that can be used to retrieve and manipulate the
* permissions of the given user.
@@ -183,9 +221,8 @@ public abstract class PermissionService<PermissionSetType extends PermissionSet<
public Set<PermissionType> retrievePermissions(AuthenticatedUser user,
ModeledUser targetUser) throws GuacamoleException {
// Only an admin can read permissions that aren't his own
if (user.getUser().getIdentifier().equals(targetUser.getIdentifier())
|| user.getUser().isAdministrator())
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetUser))
return getPermissionInstances(getPermissionMapper().select(targetUser.getModel()));
// User cannot read this user's permissions

View File

@@ -29,6 +29,7 @@ import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.user.ModeledUser;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
/**
@@ -112,6 +113,11 @@ public class SystemPermissionService
// Only an admin can delete system permissions
if (user.getUser().isAdministrator()) {
// Do not allow users to remove their own admin powers
if (user.getUser().getIdentifier().equals(targetUser.getIdentifier()))
throw new GuacamoleUnsupportedException("Removing your own administrative permissions is not allowed.");
Collection<SystemPermissionModel> models = getModelInstances(targetUser, permissions);
systemPermissionMapper.delete(models);
return;

View File

@@ -30,11 +30,16 @@ public interface PasswordEncryptionService {
/**
* Creates a password hash based on the provided username, password, and
* salt.
* salt. If the provided salt is null, only the password itself is hashed.
*
* @param password The password to hash.
* @param salt The salt to use when hashing the password.
* @return The generated password hash.
* @param password
* The password to hash.
*
* @param salt
* The salt to use when hashing the password, if any.
*
* @return
* The generated password hash.
*/
public byte[] createPasswordHash(String password, byte[] salt);

View File

@@ -38,26 +38,26 @@ public class SHA256PasswordEncryptionService implements PasswordEncryptionServic
try {
// Build salted password
// Build salted password, if a salt was provided
StringBuilder builder = new StringBuilder();
builder.append(password);
if (salt != null)
builder.append(DatatypeConverter.printHexBinary(salt));
// Hash UTF-8 bytes of salted password
// Hash UTF-8 bytes of possibly-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);
// Throw hard errors if standard pieces of Java are missing
catch (UnsupportedEncodingException e) {
throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
}
// Should not happen
catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("Unexpected lack of SHA-256 support.", e);
}
}

View File

@@ -44,7 +44,6 @@ import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionMapper;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.InetGuacamoleSocket;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.ConnectionRecord;
@@ -139,6 +138,37 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
protected abstract void release(AuthenticatedUser user,
ModeledConnection connection);
/**
* Acquires possibly-exclusive access to the given connection group on
* behalf of the given user. If access is denied for any reason, an
* exception is thrown.
*
* @param user
* The user acquiring access.
*
* @param connectionGroup
* The connection group being accessed.
*
* @throws GuacamoleException
* If access is denied to the given user for any reason.
*/
protected abstract void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException;
/**
* Releases possibly-exclusive access to the given connection group on
* behalf of the given user. If the given user did not already have access,
* the behavior of this function is undefined.
*
* @param user
* The user releasing access.
*
* @param connectionGroup
* The connection group being released.
*/
protected abstract void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup);
/**
* Returns a guacamole configuration containing the protocol and parameters
* from the given connection. If tokens are used in the connection
@@ -183,18 +213,16 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
}
/**
* Saves the given ActiveConnectionRecord to the database, associating it
* with the connection having the given identifier. The end date of the
* saved record will be populated with the current time.
*
* @param identifier
* The connection to associate the new record with.
* Saves the given ActiveConnectionRecord to the database. The end date of
* the saved record will be populated with the current time.
*
* @param record
* The record to save.
*/
private void saveConnectionRecord(String identifier,
ActiveConnectionRecord record) {
private void saveConnectionRecord(ActiveConnectionRecord record) {
// Get associated connection
ModeledConnection connection = record.getConnection();
// Get associated models
AuthenticatedUser user = record.getUser();
@@ -204,7 +232,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
// Copy user information and timestamps into new record
recordModel.setUserID(userModel.getObjectID());
recordModel.setUsername(userModel.getIdentifier());
recordModel.setConnectionIdentifier(identifier);
recordModel.setConnectionIdentifier(connection.getIdentifier());
recordModel.setStartDate(record.getStartDate());
recordModel.setEndDate(new Date());
@@ -224,24 +252,88 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
* If an error occurs while connecting to guacd, or while parsing
* guacd-related properties.
*/
private GuacamoleSocket getUnconfiguredGuacamoleSocket()
private GuacamoleSocket getUnconfiguredGuacamoleSocket(Runnable socketClosedCallback)
throws GuacamoleException {
// Use SSL if requested
if (environment.getProperty(Environment.GUACD_SSL, true))
return new InetGuacamoleSocket(
return new ManagedInetGuacamoleSocket(
environment.getRequiredProperty(Environment.GUACD_HOSTNAME),
environment.getRequiredProperty(Environment.GUACD_PORT)
environment.getRequiredProperty(Environment.GUACD_PORT),
socketClosedCallback
);
// Otherwise, just use straight TCP
return new InetGuacamoleSocket(
return new ManagedInetGuacamoleSocket(
environment.getRequiredProperty(Environment.GUACD_HOSTNAME),
environment.getRequiredProperty(Environment.GUACD_PORT)
environment.getRequiredProperty(Environment.GUACD_PORT),
socketClosedCallback
);
}
/**
* Task which handles cleanup of a connection associated with some given
* ActiveConnectionRecord.
*/
private class ConnectionCleanupTask implements Runnable {
/**
* Whether this task has run.
*/
private final AtomicBoolean hasRun = new AtomicBoolean(false);
/**
* The ActiveConnectionRecord whose connection will be cleaned up once
* this task runs.
*/
private final ActiveConnectionRecord activeConnection;
/**
* Creates a new task which automatically cleans up after the
* connection associated with the given ActiveConnectionRecord. The
* connection and parent group will be removed from the maps of active
* connections and groups, and exclusive access will be released.
*
* @param activeConnection
* The ActiveConnectionRecord whose associated connection should be
* cleaned up once this task runs.
*/
public ConnectionCleanupTask(ActiveConnectionRecord activeConnection) {
this.activeConnection = activeConnection;
}
@Override
public void run() {
// Only run once
if (!hasRun.compareAndSet(false, true))
return;
// Get original user and connection
AuthenticatedUser user = activeConnection.getUser();
ModeledConnection connection = activeConnection.getConnection();
// Get associated identifiers
String identifier = connection.getIdentifier();
String parentIdentifier = connection.getParentIdentifier();
// Release connection
activeConnections.remove(identifier, activeConnection);
activeConnectionGroups.remove(parentIdentifier, activeConnection);
release(user, connection);
// Release any associated group
if (activeConnection.hasBalancingGroup())
release(user, activeConnection.getBalancingGroup());
// Save history record to database
saveConnectionRecord(activeConnection);
}
}
/**
* Creates a socket for the given user which connects to the given
* connection, which MUST already be acquired via acquire(). The given
@@ -269,71 +361,75 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
* If an error occurs while the connection is being established, or
* while connection configuration information is being retrieved.
*/
private GuacamoleSocket connect(final AuthenticatedUser user,
final ModeledConnection connection, GuacamoleClientInformation info)
private GuacamoleSocket getGuacamoleSocket(ActiveConnectionRecord activeConnection,
GuacamoleClientInformation info)
throws GuacamoleException {
// Create record for active connection
final ActiveConnectionRecord activeConnection = new ActiveConnectionRecord(user);
ModeledConnection connection = activeConnection.getConnection();
// Get relevant identifiers
final AtomicBoolean released = new AtomicBoolean(false);
final String identifier = connection.getIdentifier();
final String parentIdentifier = connection.getParentIdentifier();
// Record new active connection
Runnable cleanupTask = new ConnectionCleanupTask(activeConnection);
activeConnections.put(connection.getIdentifier(), activeConnection);
activeConnectionGroups.put(connection.getParentIdentifier(), activeConnection);
// Return new socket
try {
// Record new active connection
activeConnections.put(identifier, activeConnection);
activeConnectionGroups.put(parentIdentifier, activeConnection);
// Return newly-reserved connection
return new ConfiguredGuacamoleSocket(
getUnconfiguredGuacamoleSocket(),
getGuacamoleConfiguration(user, connection),
getUnconfiguredGuacamoleSocket(cleanupTask),
getGuacamoleConfiguration(activeConnection.getUser(), connection),
info
) {
@Override
public void close() throws GuacamoleException {
// Attempt to close connection
super.close();
// Release connection upon close, if not already released
if (released.compareAndSet(false, true)) {
// Release connection
activeConnections.remove(identifier, activeConnection);
activeConnectionGroups.remove(parentIdentifier, activeConnection);
release(user, connection);
// Save record to database
saveConnectionRecord(identifier, activeConnection);
);
}
} // end close()
};
}
// Release connection in case of error
// Execute cleanup if socket could not be created
catch (GuacamoleException e) {
// Release connection if not already released
if (released.compareAndSet(false, true)) {
activeConnections.remove(identifier, activeConnection);
activeConnectionGroups.remove(parentIdentifier, activeConnection);
release(user, connection);
}
cleanupTask.run();
throw e;
}
}
/**
* Returns a list of all balanced connections within a given connection
* group. If the connection group is not balancing, or it contains no
* connections, an empty list is returned.
*
* @param user
* The user on whose behalf the balanced connections within the given
* connection group are being retrieved.
*
* @param connectionGroup
* The connection group to retrieve the balanced connections of.
*
* @return
* A list containing all balanced connections within the given group,
* or an empty list if there are no such connections.
*/
private List<ModeledConnection> getBalancedConnections(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
// If not a balancing group, there are no balanced connections
if (connectionGroup.getType() != ConnectionGroup.Type.BALANCING)
return Collections.EMPTY_LIST;
// If group has no children, there are no balanced connections
Collection<String> identifiers = connectionMapper.selectIdentifiersWithin(connectionGroup.getIdentifier());
if (identifiers.isEmpty())
return Collections.EMPTY_LIST;
// Retrieve all children
Collection<ConnectionModel> models = connectionMapper.select(identifiers);
List<ModeledConnection> connections = new ArrayList<ModeledConnection>(models.size());
// Convert each retrieved model to a modeled connection
for (ConnectionModel model : models) {
ModeledConnection connection = connectionProvider.get();
connection.init(user, model);
connections.add(connection);
}
return connections;
}
@Override
@@ -344,7 +440,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
// Acquire and connect to single connection
acquire(user, Collections.singletonList(connection));
return connect(user, connection, info);
return getGuacamoleSocket(new ActiveConnectionRecord(user, connection), info);
}
@@ -359,29 +455,17 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS
ModeledConnectionGroup connectionGroup,
GuacamoleClientInformation info) throws GuacamoleException {
// If not a balancing group, cannot connect
if (connectionGroup.getType() != ConnectionGroup.Type.BALANCING)
// If group has no associated balanced connections, cannot connect
List<ModeledConnection> connections = getBalancedConnections(user, connectionGroup);
if (connections.isEmpty())
throw new GuacamoleSecurityException("Permission denied.");
// If group has no children, cannot connect
Collection<String> identifiers = connectionMapper.selectIdentifiersWithin(connectionGroup.getIdentifier());
if (identifiers.isEmpty())
throw new GuacamoleSecurityException("Permission denied.");
// Otherwise, retrieve all children
Collection<ConnectionModel> models = connectionMapper.select(identifiers);
List<ModeledConnection> connections = new ArrayList<ModeledConnection>(models.size());
// Convert each retrieved model to a modeled connection
for (ConnectionModel model : models) {
ModeledConnection connection = connectionProvider.get();
connection.init(user, model);
connections.add(connection);
}
// Acquire group
acquire(user, connectionGroup);
// Acquire and connect to any child
ModeledConnection connection = acquire(user, connections);
return connect(user, connection, info);
return getGuacamoleSocket(new ActiveConnectionRecord(user, connectionGroup, connection), info);
}

View File

@@ -23,6 +23,8 @@
package org.glyptodon.guacamole.auth.jdbc.socket;
import java.util.Date;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.net.auth.ConnectionRecord;
@@ -43,21 +45,62 @@ public class ActiveConnectionRecord implements ConnectionRecord {
*/
private final AuthenticatedUser user;
/**
* The balancing group from which the associated connection was chosen, if
* any. If no balancing group was used, this will be null.
*/
private final ModeledConnectionGroup balancingGroup;
/**
* The connection associated with this connection record.
*/
private final ModeledConnection connection;
/**
* The time this connection record was created.
*/
private final Date startDate = new Date();
/**
* Creates a new connection record associated with the given user. The
* start date of this connection record will be the time of its creation.
* Creates a new connection record associated with the given user,
* connection, and balancing connection group. The given balancing
* connection group MUST be the connection group from which the given
* connection was chosen. The start date of this connection record will be
* the time of its creation.
*
* @param user
* The user that connected to the connection associated with this
* connection record.
*
* @param balancingGroup
* The balancing group from which the given connection was chosen.
*
* @param connection
* The connection to associate with this connection record.
*/
public ActiveConnectionRecord(AuthenticatedUser user) {
public ActiveConnectionRecord(AuthenticatedUser user,
ModeledConnectionGroup balancingGroup,
ModeledConnection connection) {
this.user = user;
this.balancingGroup = balancingGroup;
this.connection = connection;
}
/**
* Creates a new connection record associated with the given user and
* connection. The start date of this connection record will be the time of
* its creation.
*
* @param user
* The user that connected to the connection associated with this
* connection record.
*
* @param connection
* The connection to associate with this connection record.
*/
public ActiveConnectionRecord(AuthenticatedUser user,
ModeledConnection connection) {
this(user, null, connection);
}
/**
@@ -72,6 +115,40 @@ public class ActiveConnectionRecord implements ConnectionRecord {
return user;
}
/**
* Returns the balancing group from which the connection associated with
* this connection record was chosen.
*
* @return
* The balancing group from which the connection associated with this
* connection record was chosen.
*/
public ModeledConnectionGroup getBalancingGroup() {
return balancingGroup;
}
/**
* Returns the connection associated with this connection record.
*
* @return
* The connection associated with this connection record.
*/
public ModeledConnection getConnection() {
return connection;
}
/**
* Returns whether the connection associated with this connection record
* was chosen from a balancing group.
*
* @return
* true if the connection associated with this connection record was
* chosen from a balancing group, false otherwise.
*/
public boolean hasBalancingGroup() {
return balancingGroup != null;
}
@Override
public Date getStartDate() {
return startDate;

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.socket;
import com.google.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleSocketService implementation which allows only one user per
* connection at any time, but does not disallow concurrent use of connection
* groups. If a user attempts to use a connection group multiple times, they
* will receive different underlying connections each time until the group is
* exhausted.
*
* @author Michael Jumper
*/
@Singleton
public class BalancedGuacamoleSocketService
extends AbstractGuacamoleSocketService {
/**
* The set of all active connection identifiers.
*/
private final Set<String> activeConnections =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
// Return the first unused connection
for (ModeledConnection connection : connections) {
if (activeConnections.add(connection.getIdentifier()))
return connection;
}
// Already in use
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
activeConnections.remove(connection.getIdentifier());
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do nothing
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
// Do nothing
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.socket;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.InetGuacamoleSocket;
/**
* Implementation of GuacamoleSocket which connects via TCP to a given hostname
* and port. If the socket is closed for any reason, a given task is run.
*
* @author Michael Jumper
*/
public class ManagedInetGuacamoleSocket extends InetGuacamoleSocket {
/**
* The task to run when the socket is closed.
*/
private final Runnable socketClosedTask;
/**
* Creates a new socket which connects via TCP to a given hostname and
* port. If the socket is closed for any reason, the given task is run.
*
* @param hostname
* The hostname of the Guacamole proxy server to connect to.
*
* @param port
* The port of the Guacamole proxy server to connect to.
*
* @param socketClosedTask
* The task to run when the socket is closed. This task will NOT be
* run if an exception occurs during connection, and this
* ManagedInetGuacamoleSocket instance is ultimately not created.
*
* @throws GuacamoleException
* If an error occurs while connecting to the Guacamole proxy server.
*/
public ManagedInetGuacamoleSocket(String hostname, int port,
Runnable socketClosedTask) throws GuacamoleException {
super(hostname, port);
this.socketClosedTask = socketClosedTask;
}
@Override
public void close() throws GuacamoleException {
super.close();
socketClosedTask.run();
}
}

View File

@@ -23,20 +23,24 @@
package org.glyptodon.guacamole.auth.jdbc.socket;
import com.google.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleSocketService implementation which restricts concurrency only on a
* per-user basis. Each connection may be used concurrently any number of
* times, but each concurrent use must be associated with a different user.
* per-user basis. Each connection or group may be used concurrently any number
* of times, but each concurrent use must be associated with a different user.
*
* @author Michael Jumper
*/
@@ -44,83 +48,40 @@ import org.glyptodon.guacamole.GuacamoleResourceConflictException;
public class MultiseatGuacamoleSocketService
extends AbstractGuacamoleSocketService {
/**
* A unique pairing of user and connection.
*/
private static class Seat {
/**
* The user using this seat.
*/
private final String username;
/**
* The connection associated with this seat.
*/
private final String connectionIdentifier;
/**
* Creates a new seat which associated the given user with the given
* connection.
*
* @param username
* The username of the user using this seat.
*
* @param connectionIdentifier
* The identifier of the connection associated with this seat.
*/
public Seat(String username, String connectionIdentifier) {
this.username = username;
this.connectionIdentifier = connectionIdentifier;
}
@Override
public int hashCode() {
// The various properties will never be null
assert(username != null);
assert(connectionIdentifier != null);
// Derive hashcode from username and connection identifier
int hash = 5;
hash = 37 * hash + username.hashCode();
hash = 37 * hash + connectionIdentifier.hashCode();
return hash;
}
@Override
public boolean equals(Object object) {
// We are only comparing against other seats here
assert(object instanceof Seat);
Seat seat = (Seat) object;
// The various properties will never be null
assert(seat.username != null);
assert(seat.connectionIdentifier != null);
return username.equals(seat.username)
&& connectionIdentifier.equals(seat.connectionIdentifier);
}
}
/**
* The set of all active user/connection pairs.
*/
private final Set<Seat> activeSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
/**
* The set of all active user/connection group pairs.
*/
private final Set<Seat> activeGroupSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
String username = user.getUser().getIdentifier();
// Sort connections in ascending order of usage
ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]);
Arrays.sort(sortedConnections, new Comparator<ModeledConnection>() {
@Override
public int compare(ModeledConnection a, ModeledConnection b) {
return getActiveConnections(a).size()
- getActiveConnections(b).size();
}
});
// Return the first unreserved connection
for (ModeledConnection connection : connections) {
for (ModeledConnection connection : sortedConnections) {
if (activeSeats.add(new Seat(username, connection.getIdentifier())))
return connection;
}
@@ -135,4 +96,21 @@ public class MultiseatGuacamoleSocketService
activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier()));
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do not allow duplicate use of connection groups
Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier());
if (!activeGroupSeats.add(seat))
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()));
}
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.socket;
import com.google.inject.Singleton;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
/**
* GuacamoleSocketService implementation which allows only one user per
* connection at any time, but does not disallow concurrent use. Once
* connected, a user has effectively reserved that connection, and may
* continue to concurrently use that connection any number of times. The
* connection will remain reserved until all associated connections are closed.
* Other users will be denied access to that connection while it is reserved.
*
* @author Michael Jumper
*/
@Singleton
public class ReservedGuacamoleSocketService
extends AbstractGuacamoleSocketService {
/**
* An arbitrary number of reservations associated with a specific user.
* Initially, each Reservation instance represents exactly one reservation,
* but future calls to acquire() may increase this value. Once the
* reservation count is reduced to zero by calls to release(), a
* Reservation instance is empty and cannot be reused. It must be discarded
* and replaced with a fresh Reservation.
*
* This is necessary as each Reservation will be stored within a Map, and
* the effect of acquire() must be deterministic. If Reservations could be
* reused, the internal count could potentially increase after being
* removed from the map, resulting in a successful acquire() that really
* should have failed.
*/
private static class Reservation {
/**
* The username of the user associated with this reservation.
*/
private final String username;
/**
* The number of reservations effectively present under the associated
* username.
*/
private int count = 1;
/**
* Creates a new reservation which tracks the overall number of
* reservations for a given user.
* @param username
*/
public Reservation(String username) {
this.username = username;
}
/**
* Attempts to acquire a new reservation under the given username. If
* this reservation is for a different user, or the reservation has
* expired, this will fail.
*
* @param username
* The username of the user to acquire the reservation for.
*
* @return
* true if the reservation was successful, false otherwise.
*/
public boolean acquire(String username) {
// Acquire always fails if for the wrong user
if (!this.username.equals(username))
return false;
// Determine success/failure based on count
synchronized (this) {
// If already expired, no further reservations are allowed
if (count == 0)
return false;
// Otherwise, add another reservation, report success
count++;
return true;
}
}
/**
* Releases a previous reservation. The result of calling this function
* without a previous matching call to acquire is undefined.
*
* @return
* true if the last reservation has been released and this
* reservation is now empty, false otherwise.
*/
public boolean release() {
synchronized (this) {
// Reduce reservation count
count--;
// Empty if no reservations remain
return count == 0;
}
}
}
/**
* Map of connection identifier to associated reservations.
*/
private final ConcurrentMap<String, Reservation> reservations =
new ConcurrentHashMap<String, Reservation>();
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
String username = user.getUser().getIdentifier();
// Return the first successfully-reserved connection
for (ModeledConnection connection : connections) {
String identifier = connection.getIdentifier();
// Attempt to reserve connection, return if successful
Reservation reservation = reservations.putIfAbsent(identifier, new Reservation(username));
if (reservation == null || reservation.acquire(username))
return connection;
}
// Already in use
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
String identifier = connection.getIdentifier();
// Retrieve active reservation (which must exist)
Reservation reservation = reservations.get(identifier);
assert(reservation != null);
// Release reservation, remove from map if empty
if (reservation.release())
reservations.remove(identifier);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.socket;
/**
* A unique pairing of user and connection or connection group.
*
* @author Michael Jumper
*/
public class Seat {
/**
* The user using this seat.
*/
private final String username;
/**
* The connection or connection group associated with this seat.
*/
private final String identifier;
/**
* Creates a new seat which associated the given user with the given
* connection or connection group.
*
* @param username
* The username of the user using this seat.
*
* @param identifier
* The identifier of the connection or connection group associated with
* this seat.
*/
public Seat(String username, String identifier) {
this.username = username;
this.identifier = identifier;
}
@Override
public int hashCode() {
// The various properties will never be null
assert(username != null);
assert(identifier != null);
// Derive hashcode from username and connection identifier
int hash = 5;
hash = 37 * hash + username.hashCode();
hash = 37 * hash + identifier.hashCode();
return hash;
}
@Override
public boolean equals(Object object) {
// We are only comparing against other seats here
assert(object instanceof Seat);
Seat seat = (Seat) object;
// The various properties will never be null
assert(seat.username != null);
assert(seat.identifier != null);
return username.equals(seat.username)
&& identifier.equals(seat.identifier);
}
}

View File

@@ -27,15 +27,19 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleSocketService implementation which allows exactly one use
* of any connection at any time. Concurrent usage of any kind is not allowed.
* of any connection at any time. Concurrent usage of connections is not
* allowed, and concurrent usage of connection groups is allowed only between
* different users.
*
* @author Michael Jumper
*/
@@ -49,6 +53,12 @@ public class SingleSeatGuacamoleSocketService
private final Set<String> activeConnections =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* The set of all active user/connection group pairs.
*/
private final Set<Seat> activeGroupSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
@@ -69,4 +79,21 @@ public class SingleSeatGuacamoleSocketService
activeConnections.remove(connection.getIdentifier());
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do not allow duplicate use of connection groups
Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier());
if (!activeGroupSeats.add(seat))
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()));
}
}

View File

@@ -27,6 +27,7 @@ import java.util.List;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
@@ -66,4 +67,16 @@ public class UnrestrictedGuacamoleSocketService
// Do nothing
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do nothing
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
// Do nothing
}
}

View File

@@ -32,6 +32,7 @@ import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectMapper;
import org.glyptodon.guacamole.auth.jdbc.base.DirectoryObjectService;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel;
import org.glyptodon.guacamole.auth.jdbc.permission.UserPermissionMapper;
@@ -137,9 +138,11 @@ public class UserService extends DirectoryObjectService<ModeledUser, User, UserM
}
@Override
protected void validateNewModel(AuthenticatedUser user, UserModel model)
protected void beforeCreate(AuthenticatedUser user, UserModel model)
throws GuacamoleException {
super.beforeCreate(user, model);
// Username must not be blank
if (model.getIdentifier().trim().isEmpty())
throw new GuacamoleClientException("The username must not be blank.");
@@ -152,9 +155,11 @@ public class UserService extends DirectoryObjectService<ModeledUser, User, UserM
}
@Override
protected void validateExistingModel(AuthenticatedUser user,
protected void beforeUpdate(AuthenticatedUser user,
UserModel model) throws GuacamoleException {
super.beforeUpdate(user, model);
// Username must not be blank
if (model.getIdentifier().trim().isEmpty())
throw new GuacamoleClientException("The username must not be blank.");
@@ -193,6 +198,16 @@ public class UserService extends DirectoryObjectService<ModeledUser, User, UserM
}
return implicitPermissions;
}
@Override
protected void beforeDelete(AuthenticatedUser user, String identifier) throws GuacamoleException {
super.beforeDelete(user, identifier);
// Do not allow users to delete themselves
if (identifier.equals(user.getUser().getIdentifier()))
throw new GuacamoleUnsupportedException("Deleting your own user is not allowed.");
}

View File

@@ -65,7 +65,9 @@ CREATE TABLE `guacamole_connection` (
--
-- Table of users. Each user has a unique username and a hashed password
-- with corresponding salt.
-- with corresponding salt. Although the authentication system will always set
-- salted passwords, other systems may set unsalted passwords by simply not
-- providing the salt.
--
CREATE TABLE `guacamole_user` (
@@ -73,7 +75,7 @@ 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,
`password_salt` binary(32),
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)

View File

@@ -31,7 +31,7 @@ import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.MultiseatGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.ReservedGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.BalancedGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.SingleSeatGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.UnrestrictedGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
@@ -85,7 +85,7 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider {
// Connections are reserved for a single user when in use
else
return ReservedGuacamoleSocketService.class;
return BalancedGuacamoleSocketService.class;
}

View File

@@ -47,7 +47,7 @@
JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id
WHERE
guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'read'
AND permission = 'READ'
</select>
<!-- Select multiple users by username -->
@@ -83,7 +83,7 @@
#{identifier,jdbcType=VARCHAR}
</foreach>
AND guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'read'
AND permission = 'READ'
</select>

View File

@@ -0,0 +1,2 @@
target/
*~

View File

@@ -0,0 +1,78 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.glyptodon.guacamole</groupId>
<artifactId>guacamole-auth-jdbc-postgresql</artifactId>
<packaging>jar</packaging>
<name>guacamole-auth-jdbc-postgresql</name>
<url>http://guac-dev.org/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.glyptodon.guacamole</groupId>
<artifactId>guacamole-auth-jdbc</artifactId>
<version>0.9.5</version>
<relativePath>../../</relativePath>
</parent>
<build>
<plugins>
<!-- Written for 1.6 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Assembly plugin - for easy distribution -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
<executions>
<execution>
<id>jar-with-dependencies</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>extension/${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.glyptodon.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<scope>provided</scope>
</dependency>
<!-- Guacamole JDBC Authentication -->
<dependency>
<groupId>org.glyptodon.guacamole</groupId>
<artifactId>guacamole-auth-jdbc-base</artifactId>
<version>0.9.5</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,275 @@
--
-- Copyright (C) 2015 Glyptodon LLC
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
--
-- Connection group types
--
CREATE TYPE guacamole_connection_group_type AS ENUM(
'ORGANIZATIONAL',
'BALANCING'
);
--
-- Object permission types
--
CREATE TYPE guacamole_object_permission_type AS ENUM(
'READ',
'UPDATE',
'DELETE',
'ADMINISTER'
);
--
-- System permission types
--
CREATE TYPE guacamole_system_permission_type AS ENUM(
'CREATE_CONNECTION',
'CREATE_CONNECTION_GROUP',
'CREATE_USER',
'ADMINISTER'
);
--
-- Table of connection groups. Each connection group has a name.
--
CREATE TABLE guacamole_connection_group (
connection_group_id serial NOT NULL,
parent_id integer,
connection_group_name varchar(128) NOT NULL,
type guacamole_connection_group_type
NOT NULL DEFAULT 'ORGANIZATIONAL',
PRIMARY KEY (connection_group_id),
CONSTRAINT connection_group_name_parent
UNIQUE (connection_group_name, parent_id),
CONSTRAINT guacamole_connection_group_ibfk_1
FOREIGN KEY (parent_id)
REFERENCES guacamole_connection_group (connection_group_id)
ON DELETE CASCADE
);
CREATE INDEX ON guacamole_connection_group(parent_id);
--
-- Table of connections. Each connection has a name, protocol, and
-- associated set of parameters.
-- A connection may belong to a connection group.
--
CREATE TABLE guacamole_connection (
connection_id serial NOT NULL,
connection_name varchar(128) NOT NULL,
parent_id integer,
protocol varchar(32) NOT NULL,
PRIMARY KEY (connection_id),
CONSTRAINT connection_name_parent
UNIQUE (connection_name, parent_id),
CONSTRAINT guacamole_connection_ibfk_1
FOREIGN KEY (parent_id)
REFERENCES guacamole_connection_group (connection_group_id)
ON DELETE CASCADE
);
CREATE INDEX ON guacamole_connection(parent_id);
--
-- Table of users. Each user has a unique username and a hashed password
-- with corresponding salt. Although the authentication system will always set
-- salted passwords, other systems may set unsalted passwords by simply not
-- providing the salt.
--
CREATE TABLE guacamole_user (
user_id serial NOT NULL,
username varchar(128) NOT NULL,
password_hash bytea NOT NULL,
password_salt bytea,
PRIMARY KEY (user_id),
CONSTRAINT username
UNIQUE (username)
);
--
-- Table of connection parameters. Each parameter is simply a name/value pair
-- associated with a connection.
--
CREATE TABLE guacamole_connection_parameter (
connection_id integer 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
);
CREATE INDEX ON guacamole_connection_parameter(connection_id);
--
-- Table of connection permissions. Each connection permission grants a user
-- specific access to a connection.
--
CREATE TABLE guacamole_connection_permission (
user_id integer NOT NULL,
connection_id integer NOT NULL,
permission guacamole_object_permission_type 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
);
CREATE INDEX ON guacamole_connection_permission(connection_id);
CREATE INDEX ON guacamole_connection_permission(user_id);
--
-- Table of connection group permissions. Each group permission grants a user
-- specific access to a connection group.
--
CREATE TABLE guacamole_connection_group_permission (
user_id integer NOT NULL,
connection_group_id integer NOT NULL,
permission guacamole_object_permission_type NOT NULL,
PRIMARY KEY (user_id,connection_group_id,permission),
CONSTRAINT guacamole_connection_group_permission_ibfk_1
FOREIGN KEY (connection_group_id)
REFERENCES guacamole_connection_group (connection_group_id) ON DELETE CASCADE,
CONSTRAINT guacamole_connection_group_permission_ibfk_2
FOREIGN KEY (user_id)
REFERENCES guacamole_user (user_id) ON DELETE CASCADE
);
CREATE INDEX ON guacamole_connection_group_permission(connection_group_id);
CREATE INDEX ON guacamole_connection_group_permission(user_id);
--
-- Table of system permissions. Each system permission grants a user a
-- system-level privilege of some kind.
--
CREATE TABLE guacamole_system_permission (
user_id integer NOT NULL,
permission guacamole_system_permission_type 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
);
CREATE INDEX ON guacamole_system_permission(user_id);
--
-- 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 integer NOT NULL,
affected_user_id integer NOT NULL,
permission guacamole_object_permission_type 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
);
CREATE INDEX ON guacamole_user_permission(affected_user_id);
CREATE INDEX ON guacamole_user_permission(user_id);
--
-- 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 serial NOT NULL,
user_id integer NOT NULL,
connection_id integer NOT NULL,
start_date timestamptz NOT NULL,
end_date timestamptz DEFAULT NULL,
PRIMARY KEY (history_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
);
CREATE INDEX ON guacamole_connection_history(user_id);
CREATE INDEX ON guacamole_connection_history(connection_id);

View File

@@ -0,0 +1,53 @@
--
-- Copyright (C) 2015 Glyptodon LLC
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
-- Create default user "guacadmin" with password "guacadmin"
INSERT INTO guacamole_user (username, password_hash, password_salt)
VALUES ('guacadmin',
E'\\xCA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', -- 'guacadmin'
E'\\xFE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264');
-- Grant this user all system permissions
INSERT INTO guacamole_system_permission
SELECT user_id, permission::guacamole_system_permission_type
FROM (
VALUES
('guacadmin', 'CREATE_CONNECTION'),
('guacadmin', 'CREATE_CONNECTION_GROUP'),
('guacadmin', 'CREATE_USER'),
('guacadmin', 'ADMINISTER')
) permissions (username, permission)
JOIN guacamole_user ON permissions.username = guacamole_user.username;
-- Grant admin permission to read/update/administer self
INSERT INTO guacamole_user_permission
SELECT guacamole_user.user_id, affected.user_id, permission::guacamole_object_permission_type
FROM (
VALUES
('guacadmin', 'guacadmin', 'READ'),
('guacadmin', 'guacadmin', 'UPDATE'),
('guacadmin', 'guacadmin', 'ADMINISTER')
) permissions (username, affected_username, permission)
JOIN guacamole_user ON permissions.username = guacamole_user.username
JOIN guacamole_user affected ON permissions.affected_username = affected.username;

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.postgresql;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
import org.glyptodon.guacamole.net.auth.Credentials;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.socket.BalancedGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.GuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.MultiseatGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.SingleSeatGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.socket.UnrestrictedGuacamoleSocketService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment;
/**
* Provides a PostgreSQL-based implementation of the AuthenticationProvider
* functionality.
*
* @author James Muehlner
* @author Michael Jumper
*/
public class PostgreSQLAuthenticationProvider implements AuthenticationProvider {
/**
* Injector which will manage the object graph of this authentication
* provider.
*/
private final Injector injector;
/**
* Returns the appropriate socket service class given the Guacamole
* environment. The class is chosen based on configuration options that
* dictate concurrent usage policy.
*
* @param environment
* The environment of the Guacamole server.
*
* @return
* The socket service class that matches the concurrent usage policy
* options set in the Guacamole environment.
*
* @throws GuacamoleException
* If an error occurs while reading the configuration options.
*/
private Class<? extends GuacamoleSocketService>
getSocketServiceClass(Environment environment)
throws GuacamoleException {
// Read concurrency-related properties
boolean disallowSimultaneous = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false);
boolean disallowDuplicate = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_DUPLICATE_CONNECTIONS, true);
if (disallowSimultaneous) {
// Connections may not be used concurrently
if (disallowDuplicate)
return SingleSeatGuacamoleSocketService.class;
// Connections are reserved for a single user when in use
else
return BalancedGuacamoleSocketService.class;
}
else {
// Connections may be used concurrently, but only once per user
if (disallowDuplicate)
return MultiseatGuacamoleSocketService.class;
// Connection use is not restricted
else
return UnrestrictedGuacamoleSocketService.class;
}
}
/**
* Creates a new PostgreSQLAuthenticationProvider that reads and writes
* authentication data to a PostgreSQL database defined by properties in
* guacamole.properties.
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public PostgreSQLAuthenticationProvider() throws GuacamoleException {
// Get local environment
Environment environment = new LocalEnvironment();
// Set up Guice injector.
injector = Guice.createInjector(
// Configure PostgreSQL-specific authentication
new PostgreSQLAuthenticationProviderModule(environment),
// Configure JDBC authentication core
new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment))
);
}
@Override
public UserContext getUserContext(Credentials credentials)
throws GuacamoleException {
// Create UserContext based on credentials, if valid
UserContextService userContextService = injector.getInstance(UserContextService.class);
return userContextService.getUserContext(credentials);
}
@Override
public UserContext updateUserContext(UserContext context,
Credentials credentials) throws GuacamoleException {
// No need to update the context
return context;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.postgresql;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.name.Names;
import java.util.Properties;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.environment.Environment;
import org.mybatis.guice.datasource.helper.JdbcHelper;
/**
* Guice module which configures PostgreSQL-specific injections.
*
* @author James Muehlner
* @author Michael Jumper
*/
public class PostgreSQLAuthenticationProviderModule implements Module {
/**
* MyBatis-specific configuration properties.
*/
private final Properties myBatisProperties = new Properties();
/**
* PostgreSQL-specific driver configuration properties.
*/
private final Properties driverProperties = new Properties();
/**
* Creates a new PostgreSQL authentication provider module that configures
* driver and MyBatis properties using the given environment.
*
* @param environment
* The environment to use when configuring MyBatis and the underlying
* JDBC driver.
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public PostgreSQLAuthenticationProviderModule(Environment environment)
throws GuacamoleException {
// Set the PostgreSQL-specific properties for MyBatis.
myBatisProperties.setProperty("mybatis.environment.id", "guacamole");
myBatisProperties.setProperty("JDBC.host", environment.getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_HOSTNAME));
myBatisProperties.setProperty("JDBC.port", String.valueOf(environment.getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_PORT)));
myBatisProperties.setProperty("JDBC.schema", environment.getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DATABASE));
myBatisProperties.setProperty("JDBC.username", environment.getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_USERNAME));
myBatisProperties.setProperty("JDBC.password", environment.getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_PASSWORD));
myBatisProperties.setProperty("JDBC.autoCommit", "false");
myBatisProperties.setProperty("mybatis.pooled.pingEnabled", "true");
myBatisProperties.setProperty("mybatis.pooled.pingQuery", "SELECT 1");
// Use UTF-8 in database
driverProperties.setProperty("characterEncoding","UTF-8");
}
@Override
public void configure(Binder binder) {
// Bind PostgreSQL-specific properties
JdbcHelper.PostgreSQL.configure(binder);
// Bind MyBatis properties
Names.bindProperties(binder, myBatisProperties);
// Bing JDBC driver properties
binder.bind(Properties.class)
.annotatedWith(Names.named("JDBC.driverProperties"))
.toInstance(driverProperties);
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.postgresql;
import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty;
import org.glyptodon.guacamole.properties.IntegerGuacamoleProperty;
import org.glyptodon.guacamole.properties.StringGuacamoleProperty;
/**
* Properties used by the PostgreSQL Authentication plugin.
*
* @author James Muehlner
* @author Michael Jumper
*/
public class PostgreSQLGuacamoleProperties {
/**
* This class should not be instantiated.
*/
private PostgreSQLGuacamoleProperties() {}
/**
* The URL of the PostgreSQL server hosting the Guacamole authentication tables.
*/
public static final StringGuacamoleProperty POSTGRESQL_HOSTNAME =
new StringGuacamoleProperty() {
@Override
public String getName() { return "postgresql-hostname"; }
};
/**
* The port of the PostgreSQL server hosting the Guacamole authentication
* tables.
*/
public static final IntegerGuacamoleProperty POSTGRESQL_PORT =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "postgresql-port"; }
};
/**
* The name of the PostgreSQL database containing the Guacamole
* authentication tables.
*/
public static final StringGuacamoleProperty POSTGRESQL_DATABASE =
new StringGuacamoleProperty() {
@Override
public String getName() { return "postgresql-database"; }
};
/**
* The username used to authenticate to the PostgreSQL database containing
* the Guacamole authentication tables.
*/
public static final StringGuacamoleProperty POSTGRESQL_USERNAME =
new StringGuacamoleProperty() {
@Override
public String getName() { return "postgresql-username"; }
};
/**
* The password used to authenticate to the PostgreSQL database containing
* the Guacamole authentication tables.
*/
public static final StringGuacamoleProperty POSTGRESQL_PASSWORD =
new StringGuacamoleProperty() {
@Override
public String getName() { return "postgresql-password"; }
};
/**
* Whether or not multiple users accessing the same connection at the same
* time should be disallowed.
*/
public static final BooleanGuacamoleProperty
POSTGRESQL_DISALLOW_SIMULTANEOUS_CONNECTIONS =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "postgresql-disallow-simultaneous-connections"; }
};
/**
* Whether or not the same user accessing the same connection or connection
* group at the same time should be disallowed.
*/
public static final BooleanGuacamoleProperty
POSTGRESQL_DISALLOW_DUPLICATE_CONNECTIONS =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "postgresql-disallow-duplicate-connections"; }
};
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* The PostgreSQL authentication provider.
*/
package org.glyptodon.guacamole.auth.postgresql;

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionMapper" >
<!-- Result mapper for connection objects -->
<resultMap id="ConnectionResultMap" type="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionModel" >
<id column="connection_id" property="objectID" jdbcType="INTEGER"/>
<result column="connection_name" property="name" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="protocol" property="protocol" jdbcType="VARCHAR"/>
</resultMap>
<!-- Select all connection identifiers -->
<select id="selectIdentifiers" resultType="string">
SELECT connection_id
FROM guacamole_connection
</select>
<!-- Select identifiers of all readable connections -->
<select id="selectReadableIdentifiers" resultType="string">
SELECT connection_id
FROM guacamole_connection_permission
WHERE
user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select all connection identifiers within a particular connection group -->
<select id="selectIdentifiersWithin" resultType="string">
SELECT connection_id
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
</select>
<!-- Select identifiers of all readable connections within a particular connection group -->
<select id="selectReadableIdentifiersWithin" resultType="string">
SELECT guacamole_connection.connection_id
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
AND user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select multiple connections by identifier -->
<select id="select" resultMap="ConnectionResultMap">
SELECT
connection_id,
connection_name,
parent_id,
protocol
FROM guacamole_connection
WHERE connection_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
</select>
<!-- Select multiple connections by identifier only if readable -->
<select id="selectReadable" resultMap="ConnectionResultMap">
SELECT
guacamole_connection.connection_id,
connection_name,
parent_id,
protocol
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE guacamole_connection.connection_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
AND user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select single connection by name -->
<select id="selectOneByName" resultMap="ConnectionResultMap">
SELECT
connection_id,
connection_name,
parent_id,
protocol
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
AND connection_name = #{name,jdbcType=VARCHAR}
</select>
<!-- Delete single connection by identifier -->
<delete id="delete">
DELETE FROM guacamole_connection
WHERE connection_id = #{identifier,jdbcType=INTEGER}::integer
</delete>
<!-- Insert single connection -->
<insert id="insert" useGeneratedKeys="true" keyProperty="object.objectID"
parameterType="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionModel">
INSERT INTO guacamole_connection (
connection_name,
parent_id,
protocol
)
VALUES (
#{object.name,jdbcType=VARCHAR},
#{object.parentIdentifier,jdbcType=INTEGER}::integer,
#{object.protocol,jdbcType=VARCHAR}
)
</insert>
<!-- Update single connection -->
<update id="update" parameterType="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionModel">
UPDATE guacamole_connection
SET connection_name = #{object.name,jdbcType=VARCHAR},
parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer,
protocol = #{object.protocol,jdbcType=VARCHAR}
WHERE connection_id = #{object.objectID,jdbcType=INTEGER}::integer
</update>
</mapper>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionRecordMapper" >
<!-- Result mapper for system permissions -->
<resultMap id="ConnectionRecordResultMap" type="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionRecordModel">
<result column="connection_id" property="connectionIdentifier" jdbcType="INTEGER"/>
<result column="user_id" property="userID" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="start_date" property="startDate" jdbcType="TIMESTAMP"/>
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- Select all connection records from a given connection -->
<select id="select" resultMap="ConnectionRecordResultMap">
SELECT
connection_id,
guacamole_connection_history.user_id,
username,
start_date,
end_date
FROM guacamole_connection_history
JOIN guacamole_user ON guacamole_connection_history.user_id = guacamole_user.user_id
WHERE
connection_id = #{identifier,jdbcType=INTEGER}::integer
ORDER BY
start_date DESC,
end_date DESC
</select>
<!-- Insert the given connection record -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.connection.ConnectionRecordModel">
INSERT INTO guacamole_connection_history (
connection_id,
user_id,
start_date,
end_date
)
VALUES (
#{record.connectionIdentifier,jdbcType=INTEGER}::integer,
#{record.userID,jdbcType=INTEGER},
#{record.startDate,jdbcType=TIMESTAMP},
#{record.endDate,jdbcType=TIMESTAMP}
)
</insert>
</mapper>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.connection.ParameterMapper">
<!-- Result mapper for connection parameters -->
<resultMap id="ParameterResultMap" type="org.glyptodon.guacamole.auth.jdbc.connection.ParameterModel">
<result column="connection_id" property="connectionIdentifier" jdbcType="INTEGER"/>
<result column="parameter_name" property="name" jdbcType="VARCHAR"/>
<result column="parameter_value" property="value" jdbcType="VARCHAR"/>
</resultMap>
<!-- Select all parameters of a given connection -->
<select id="select" resultMap="ParameterResultMap">
SELECT
connection_id,
parameter_name,
parameter_value
FROM guacamole_connection_parameter
WHERE
connection_id = #{identifier,jdbcType=INTEGER}::integer
</select>
<!-- Delete all parameters of a given connection -->
<delete id="delete">
DELETE FROM guacamole_connection_parameter
WHERE connection_id = #{identifier,jdbcType=INTEGER}::integer
</delete>
<!-- Insert all given parameters -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.connection.ParameterModel">
INSERT INTO guacamole_connection_parameter (
connection_id,
parameter_name,
parameter_value
)
VALUES
<foreach collection="parameters" item="parameter" separator=",">
(#{parameter.connectionIdentifier,jdbcType=INTEGER}::integer,
#{parameter.name,jdbcType=VARCHAR},
#{parameter.value,jdbcType=VARCHAR})
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.connectiongroup.ConnectionGroupMapper" >
<!-- Result mapper for connection objects -->
<resultMap id="ConnectionGroupResultMap" type="org.glyptodon.guacamole.auth.jdbc.connectiongroup.ConnectionGroupModel" >
<id column="connection_group_id" property="objectID" jdbcType="INTEGER"/>
<result column="connection_group_name" property="name" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="type" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.ConnectionGroup$Type"/>
</resultMap>
<!-- Select all connection group identifiers -->
<select id="selectIdentifiers" resultType="string">
SELECT connection_group_id
FROM guacamole_connection_group
</select>
<!-- Select identifiers of all readable connection groups -->
<select id="selectReadableIdentifiers" resultType="string">
SELECT connection_group_id
FROM guacamole_connection_group_permission
WHERE
user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select all connection identifiers within a particular connection group -->
<select id="selectIdentifiersWithin" resultType="string">
SELECT connection_group_id
FROM guacamole_connection_group
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
</select>
<!-- Select identifiers of all readable connection groups within a particular connection group -->
<select id="selectReadableIdentifiersWithin" resultType="string">
SELECT guacamole_connection_group.connection_group_id
FROM guacamole_connection_group
JOIN guacamole_connection_group_permission ON guacamole_connection_group_permission.connection_group_id = guacamole_connection_group.connection_group_id
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
AND user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select multiple connection groups by identifier -->
<select id="select" resultMap="ConnectionGroupResultMap">
SELECT
connection_group_id,
connection_group_name,
parent_id,
type
FROM guacamole_connection_group
WHERE connection_group_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
</select>
<!-- Select multiple connection groups by identifier only if readable -->
<select id="selectReadable" resultMap="ConnectionGroupResultMap">
SELECT
guacamole_connection_group.connection_group_id,
connection_group_name,
parent_id,
type
FROM guacamole_connection_group
JOIN guacamole_connection_group_permission ON guacamole_connection_group_permission.connection_group_id = guacamole_connection_group.connection_group_id
WHERE guacamole_connection_group.connection_group_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
AND user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select single connection group by name -->
<select id="selectOneByName" resultMap="ConnectionGroupResultMap">
SELECT
connection_group_id,
connection_group_name,
parent_id,
type
FROM guacamole_connection_group
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
<if test="parentIdentifier == null">parent_id IS NULL</if>
AND connection_group_name = #{name,jdbcType=VARCHAR}
</select>
<!-- Delete single connection group by identifier -->
<delete id="delete">
DELETE FROM guacamole_connection_group
WHERE connection_group_id = #{identifier,jdbcType=INTEGER}::integer
</delete>
<!-- Insert single connection -->
<insert id="insert" useGeneratedKeys="true" keyProperty="object.objectID"
parameterType="org.glyptodon.guacamole.auth.jdbc.connectiongroup.ConnectionGroupModel">
INSERT INTO guacamole_connection_group (
connection_group_name,
parent_id,
type
)
VALUES (
#{object.name,jdbcType=VARCHAR},
#{object.parentIdentifier,jdbcType=INTEGER}::integer,
#{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type
)
</insert>
<!-- Update single connection group -->
<update id="update" parameterType="org.glyptodon.guacamole.auth.jdbc.connectiongroup.ConnectionGroupModel">
UPDATE guacamole_connection_group
SET connection_group_name = #{object.name,jdbcType=VARCHAR},
parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer,
type = #{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type
WHERE connection_group_id = #{object.objectID,jdbcType=INTEGER}::integer
</update>
</mapper>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper" >
<!-- Result mapper for connection permissions -->
<resultMap id="ConnectionGroupPermissionResultMap" type="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
<result column="user_id" property="userID" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="permission" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.permission.ObjectPermission$Type"/>
<result column="connection_group_id" property="objectIdentifier" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all permissions for a given user -->
<select id="select" resultMap="ConnectionGroupPermissionResultMap">
SELECT
guacamole_connection_group_permission.user_id,
username,
permission,
connection_group_id
FROM guacamole_connection_group_permission
JOIN guacamole_user ON guacamole_connection_group_permission.user_id = guacamole_user.user_id
WHERE guacamole_connection_group_permission.user_id = #{user.objectID,jdbcType=INTEGER}
</select>
<!-- Select the single permission matching the given criteria -->
<select id="selectOne" resultMap="ConnectionGroupPermissionResultMap">
SELECT
guacamole_connection_group_permission.user_id,
username,
permission,
connection_group_id
FROM guacamole_connection_group_permission
JOIN guacamole_user ON guacamole_connection_group_permission.user_id = guacamole_user.user_id
WHERE
guacamole_connection_group_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = #{type,jdbcType=VARCHAR}::guacamole_object_permission_type
AND connection_group_id = #{identifier,jdbcType=INTEGER}::integer
</select>
<!-- Select identifiers accessible by the given user for the given permissions -->
<select id="selectAccessibleIdentifiers" resultType="string">
SELECT DISTINCT connection_group_id
FROM guacamole_connection_group_permission
WHERE
user_id = #{user.objectID,jdbcType=INTEGER}
AND connection_group_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
AND permission IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
#{permission,jdbcType=VARCHAR}::guacamole_object_permission_type
</foreach>
</select>
<!-- Delete all given permissions -->
<delete id="delete" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
DELETE FROM guacamole_connection_group_permission
WHERE (user_id, permission, connection_group_id) IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type,
#{permission.objectIdentifier,jdbcType=INTEGER}::integer)
</foreach>
</delete>
<!-- Insert all given permissions -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
INSERT INTO guacamole_connection_group_permission (
user_id,
permission,
connection_group_id
)
VALUES
<foreach collection="permissions" item="permission" separator=",">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type,
#{permission.objectIdentifier,jdbcType=INTEGER}::integer)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.permission.ConnectionPermissionMapper" >
<!-- Result mapper for connection permissions -->
<resultMap id="ConnectionPermissionResultMap" type="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
<result column="user_id" property="userID" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="permission" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.permission.ObjectPermission$Type"/>
<result column="connection_id" property="objectIdentifier" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all permissions for a given user -->
<select id="select" resultMap="ConnectionPermissionResultMap">
SELECT
guacamole_connection_permission.user_id,
username,
permission,
connection_id
FROM guacamole_connection_permission
JOIN guacamole_user ON guacamole_connection_permission.user_id = guacamole_user.user_id
WHERE guacamole_connection_permission.user_id = #{user.objectID,jdbcType=INTEGER}
</select>
<!-- Select the single permission matching the given criteria -->
<select id="selectOne" resultMap="ConnectionPermissionResultMap">
SELECT
guacamole_connection_permission.user_id,
username,
permission,
connection_id
FROM guacamole_connection_permission
JOIN guacamole_user ON guacamole_connection_permission.user_id = guacamole_user.user_id
WHERE
guacamole_connection_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = #{type,jdbcType=VARCHAR}::guacamole_object_permission_type
AND connection_id = #{identifier,jdbcType=INTEGER}::integer
</select>
<!-- Select identifiers accessible by the given user for the given permissions -->
<select id="selectAccessibleIdentifiers" resultType="string">
SELECT DISTINCT connection_id
FROM guacamole_connection_permission
WHERE
user_id = #{user.objectID,jdbcType=INTEGER}
AND connection_id IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}::integer
</foreach>
AND permission IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
#{permission,jdbcType=VARCHAR}::guacamole_object_permission_type
</foreach>
</select>
<!-- Delete all given permissions -->
<delete id="delete" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
DELETE FROM guacamole_connection_permission
WHERE (user_id, permission, connection_id) IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type,
#{permission.objectIdentifier,jdbcType=INTEGER}::integer)
</foreach>
</delete>
<!-- Insert all given permissions -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
INSERT INTO guacamole_connection_permission (
user_id,
permission,
connection_id
)
VALUES
<foreach collection="permissions" item="permission" separator=",">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type,
#{permission.objectIdentifier,jdbcType=INTEGER}::integer)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.permission.SystemPermissionMapper" >
<!-- Result mapper for system permissions -->
<resultMap id="SystemPermissionResultMap" type="org.glyptodon.guacamole.auth.jdbc.permission.SystemPermissionModel">
<result column="user_id" property="userID" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="permission" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.permission.SystemPermission$Type"/>
</resultMap>
<!-- Select all permissions for a given user -->
<select id="select" resultMap="SystemPermissionResultMap">
SELECT
guacamole_system_permission.user_id,
username,
permission
FROM guacamole_system_permission
JOIN guacamole_user ON guacamole_system_permission.user_id = guacamole_user.user_id
WHERE guacamole_system_permission.user_id = #{user.objectID,jdbcType=INTEGER}
</select>
<!-- Select the single permission matching the given criteria -->
<select id="selectOne" resultMap="SystemPermissionResultMap">
SELECT
guacamole_system_permission.user_id,
username,
permission
FROM guacamole_system_permission
JOIN guacamole_user ON guacamole_system_permission.user_id = guacamole_user.user_id
WHERE
guacamole_system_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = #{type,jdbcType=VARCHAR}::guacamole_system_permission_type
</select>
<!-- Delete all given permissions -->
<delete id="delete" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.SystemPermissionModel">
DELETE FROM guacamole_system_permission
WHERE (user_id, permission) IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_system_permission_type)
</foreach>
</delete>
<!-- Insert all given permissions -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.SystemPermissionModel">
INSERT INTO guacamole_system_permission (
user_id,
permission
)
VALUES
<foreach collection="permissions" item="permission" separator=",">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_system_permission_type)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.permission.UserPermissionMapper" >
<!-- Result mapper for user permissions -->
<resultMap id="UserPermissionResultMap" type="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
<result column="user_id" property="userID" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="permission" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.permission.ObjectPermission$Type"/>
<result column="affected_username" property="objectIdentifier" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all permissions for a given user -->
<select id="select" resultMap="UserPermissionResultMap">
SELECT
guacamole_user_permission.user_id,
guacamole_user.username,
permission,
affected.username AS affected_username
FROM guacamole_user_permission
JOIN guacamole_user ON guacamole_user_permission.user_id = guacamole_user.user_id
JOIN guacamole_user affected ON guacamole_user_permission.affected_user_id = affected.user_id
WHERE guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
</select>
<!-- Select the single permission matching the given criteria -->
<select id="selectOne" resultMap="UserPermissionResultMap">
SELECT
guacamole_user_permission.user_id,
guacamole_user.username,
permission,
affected.username AS affected_username
FROM guacamole_user_permission
JOIN guacamole_user ON guacamole_user_permission.user_id = guacamole_user.user_id
JOIN guacamole_user affected ON guacamole_user_permission.affected_user_id = affected.user_id
WHERE
guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = #{type,jdbcType=VARCHAR}::guacamole_object_permission_type
AND affected.username = #{identifier,jdbcType=INTEGER}
</select>
<!-- Select identifiers accessible by the given user for the given permissions -->
<select id="selectAccessibleIdentifiers" resultType="string">
SELECT DISTINCT username
FROM guacamole_user_permission
JOIN guacamole_user ON guacamole_user_permission.affected_user_id = guacamole_user.user_id
WHERE
guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND username IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=INTEGER}
</foreach>
AND permission IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
#{permission,jdbcType=VARCHAR}::guacamole_object_permission_type
</foreach>
</select>
<!-- Delete all given permissions -->
<delete id="delete" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
DELETE FROM guacamole_user_permission
USING guacamole_user_permission
JOIN guacamole_user affected ON guacamole_user_permission.affected_user_id = affected.user_id
WHERE
(guacamole_user_permission.user_id, permission, affected.username) IN
<foreach collection="permissions" item="permission"
open="(" separator="," close=")">
(#{permission.userID,jdbcType=INTEGER},
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type,
#{permission.objectIdentifier,jdbcType=INTEGER})
</foreach>
</delete>
<!-- Insert all given permissions -->
<insert id="insert" parameterType="org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel">
INSERT INTO guacamole_user_permission (
user_id,
permission,
affected_user_id
)
SELECT permissions.user_id, permissions.permission, guacamole_user.user_id FROM
<foreach collection="permissions" item="permission"
open="(" separator="UNION ALL" close=")">
SELECT #{permission.userID,jdbcType=INTEGER} AS user_id,
#{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type AS permission,
#{permission.objectIdentifier,jdbcType=INTEGER} AS username
</foreach>
AS permissions
JOIN guacamole_user ON guacamole_user.username = permissions.username;
</insert>
</mapper>

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
Copyright (C) 2015 Glyptodon LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<mapper namespace="org.glyptodon.guacamole.auth.jdbc.user.UserMapper" >
<!-- Result mapper for user objects -->
<resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" >
<id column="user_id" property="objectID" jdbcType="INTEGER"/>
<result column="username" property="identifier" jdbcType="VARCHAR"/>
<result column="password_hash" property="passwordHash" jdbcType="BINARY"/>
<result column="password_salt" property="passwordSalt" jdbcType="BINARY"/>
</resultMap>
<!-- Select all usernames -->
<select id="selectIdentifiers" resultType="string">
SELECT username
FROM guacamole_user
</select>
<!-- Select usernames of all readable users -->
<select id="selectReadableIdentifiers" resultType="string">
SELECT username
FROM guacamole_user
JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id
WHERE
guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select multiple users by username -->
<select id="select" resultMap="UserResultMap">
SELECT
user_id,
username,
password_hash,
password_salt
FROM guacamole_user
WHERE username IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=VARCHAR}
</foreach>
</select>
<!-- Select multiple users by username only if readable -->
<select id="selectReadable" resultMap="UserResultMap">
SELECT
guacamole_user.user_id,
username,
password_hash,
password_salt
FROM guacamole_user
JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id
WHERE username IN
<foreach collection="identifiers" item="identifier"
open="(" separator="," close=")">
#{identifier,jdbcType=VARCHAR}
</foreach>
AND guacamole_user_permission.user_id = #{user.objectID,jdbcType=INTEGER}
AND permission = 'READ'
</select>
<!-- Select single user by username -->
<select id="selectOne" resultMap="UserResultMap">
SELECT
user_id,
username,
password_hash,
password_salt
FROM guacamole_user
WHERE
username = #{username,jdbcType=VARCHAR}
</select>
<!-- Delete single user by username -->
<delete id="delete">
DELETE FROM guacamole_user
WHERE username = #{identifier,jdbcType=VARCHAR}
</delete>
<!-- Insert single user -->
<insert id="insert" useGeneratedKeys="true" keyProperty="object.objectID"
parameterType="org.glyptodon.guacamole.auth.jdbc.user.UserModel">
INSERT INTO guacamole_user (
username,
password_hash,
password_salt
)
VALUES (
#{object.identifier,jdbcType=VARCHAR},
#{object.passwordHash,jdbcType=BINARY},
#{object.passwordSalt,jdbcType=BINARY}
)
</insert>
<!-- Update single user -->
<update id="update" parameterType="org.glyptodon.guacamole.auth.jdbc.user.UserModel">
UPDATE guacamole_user
SET password_hash = #{object.passwordHash,jdbcType=BINARY},
password_salt = #{object.passwordSalt,jdbcType=BINARY}
WHERE user_id = #{object.objectID,jdbcType=VARCHAR}
</update>
</mapper>

View File

@@ -20,8 +20,9 @@
<!-- Base JDBC classes -->
<module>modules/guacamole-auth-jdbc-base</module>
<!-- MySQL authentication -->
<!-- Database-specific implementations -->
<module>modules/guacamole-auth-jdbc-mysql</module>
<module>modules/guacamole-auth-jdbc-postgresql</module>
</modules>

View File

@@ -27,6 +27,19 @@
</includes>
</fileSet>
<!-- PostgreSQL implementation -->
<fileSet>
<outputDirectory>/postgresql/schema</outputDirectory>
<directory>modules/guacamole-auth-jdbc-postgresql/schema</directory>
</fileSet>
<fileSet>
<directory>modules/guacamole-auth-jdbc-postgresql/target/extension</directory>
<outputDirectory>/postgresql</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>