From 3d33b5fc6e782eccdba40abe95d2a89d2e021efd Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Wed, 4 Dec 2013 21:28:19 -0800 Subject: [PATCH] Ticket #362: Added initial permission REST services. --- .../guacamole/net/basic/rest/RESTModule.java | 2 + .../net/basic/rest/RESTServletModule.java | 2 + .../rest/connection/ConnectionService.java | 2 +- .../ConnectionGroupService.java | 3 +- .../basic/rest/permission/APIPermission.java | 195 ++++++++++++++++++ .../permission/PermissionRESTService.java | 158 ++++++++++++++ .../rest/permission/PermissionService.java | 69 +++++++ .../basic/rest/permission/package-info.java | 6 + 8 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/package-info.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index d7f31b8d7..d7ea84bbb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -29,6 +29,7 @@ import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerato import org.glyptodon.guacamole.net.basic.rest.auth.TokenUserContextMap; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService; +import org.glyptodon.guacamole.net.basic.rest.permission.PermissionService; import org.glyptodon.guacamole.properties.GuacamoleProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,6 +68,7 @@ public class RESTModule extends AbstractModule { bind(TokenUserContextMap.class).toInstance(new BasicTokenUserContextMap()); bind(ConnectionService.class); bind(ConnectionGroupService.class); + bind(PermissionService.class); bind(AuthenticationService.class); bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index ccb85adbf..a2f960dfc 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -25,6 +25,7 @@ import org.codehaus.jackson.jaxrs.JacksonJsonProvider; import org.glyptodon.guacamole.net.basic.rest.auth.LoginRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService; +import org.glyptodon.guacamole.net.basic.rest.permission.PermissionRESTService; /** * A Guice Module to set up the servlet mappings for the Guacamole REST API. @@ -39,6 +40,7 @@ public class RESTServletModule extends ServletModule { // Set up the API endpoints bind(ConnectionRESTService.class); bind(ConnectionGroupRESTService.class); + bind(PermissionRESTService.class); bind(LoginRESTService.class); // Set up the servlet and JSON mappings diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java index 710d23d8f..7e26414f1 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java @@ -39,7 +39,7 @@ public class ConnectionService { * @throws GuacamoleException If an error occurs while converting the * connections. */ - public List convertConnectionList(List connections) + public List convertConnectionList(Iterable connections) throws GuacamoleException { List restConnections = new ArrayList(); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java index f16241f6c..402647a84 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java @@ -2,7 +2,6 @@ package org.glyptodon.guacamole.net.basic.rest.connectiongroup; import java.util.ArrayList; import java.util.List; -import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; /* @@ -38,7 +37,7 @@ public class ConnectionGroupService { * @return A List of APIConnectionGroup objects for use with the REST endpoint. */ public List convertConnectionGroupList( - List connectionGroups) { + Iterable connectionGroups) { List restConnectionGroups = new ArrayList(); for(ConnectionGroup connectionGroup : connectionGroups) { diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java new file mode 100644 index 000000000..d7fb5eb1f --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java @@ -0,0 +1,195 @@ +package org.glyptodon.guacamole.net.basic.rest.permission; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.glyptodon.guacamole.net.auth.permission.ConnectionGroupPermission; +import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; +import org.glyptodon.guacamole.net.auth.permission.Permission; +import org.glyptodon.guacamole.net.auth.permission.SystemPermission; +import org.glyptodon.guacamole.net.auth.permission.UserPermission; + +/** + * A simple user permission to expose through the REST endpoints. + * + * @author James Muehlner + */ +public class APIPermission { + + /** + * Create an empty APIPermission. + */ + public APIPermission() {} + + /** + * The type of object that this permission refers to. + */ + private ObjectType objectType; + + /** + * The type of object that a permission can refer to. + */ + public enum ObjectType { + CONNECTION, + CONNECTION_GROUP, + USER, + SYSTEM + } + + /** + * The identifier of the object that this permission refers to. + */ + private String objectIdentifier; + + /** + * The object permission type for this APIPermission, if relevant. This is + * only used if this.objectType is CONNECTION, CONNECTION_GROUP, or USER. + */ + private ObjectPermission.Type objectPermissionType; + + /** + * The system permission type for this APIPermission, if relevant. This is + * only used if this.objectType is SYSTEM. + */ + private SystemPermission.Type systemPermissionType; + + /** + * Create an APIConnection from a Connection record. + * + * @param permission The permission to create this APIPermission from. + */ + public APIPermission(Permission permission) { + if(permission instanceof ConnectionPermission) { + this.objectType = ObjectType.CONNECTION; + + this.objectPermissionType = ((ConnectionPermission) permission).getType(); + } else if(permission instanceof ConnectionGroupPermission) { + this.objectType = ObjectType.CONNECTION_GROUP; + + this.objectPermissionType = ((ConnectionGroupPermission) permission).getType(); + } else if(permission instanceof UserPermission) { + this.objectType = ObjectType.USER; + + this.objectPermissionType = ((UserPermission) permission).getType(); + } else if(permission instanceof SystemPermission) { + this.objectType = ObjectType.SYSTEM; + + this.systemPermissionType = ((SystemPermission) permission).getType(); + } + } + + /** + * Returns the type of object that this permission refers to. + * + * @return The type of object that this permission refers to. + */ + public ObjectType getObjectType() { + return objectType; + } + + /** + * Set the type of object that this permission refers to. + * @param objectType The type of object that this permission refers to. + */ + public void setObjectType(ObjectType objectType) { + this.objectType = objectType; + } + + /** + * Returns a string representation of the permission type. + * If this.objectType is CONNECTION, CONNECTION_GROUP, or USER, this will be + * the string representation of the objectPermissionType. + * If this.objectType is SYSTEM, this will be the string representation of + * the systemPermissionType. + * + * @return A string representation of the permission type. + */ + public String getPermissionType() { + switch(this.objectType) { + case CONNECTION: + case CONNECTION_GROUP: + case USER: + return this.objectPermissionType.toString(); + case SYSTEM: + return this.systemPermissionType.toString(); + default: + return null; + } + } + + /** + * Set the permission type from a string representation of that type. + * Since it's not clear at this point whether this is an object permission or + * system permission, try to set both of them. + * + * @param permissionType The string representation of the permission type. + */ + public void setPermissionType(String permissionType) { + try { + this.objectPermissionType = ObjectPermission.Type.valueOf(permissionType); + } catch(IllegalArgumentException e) {} + + try { + this.systemPermissionType = SystemPermission.Type.valueOf(permissionType); + } catch(IllegalArgumentException e) {} + } + + /** + * Returns the identifier of the object that this permission refers to. + * + * @return The identifier of the object that this permission refers to. + */ + public String getObjectIdentifier() { + return objectIdentifier; + } + + /** + * Set the identifier of the object that this permission refers to. + * + * @param objectIdentifier The identifier of the object that this permission refers to. + */ + public void setObjectIdentifier(String objectIdentifier) { + this.objectIdentifier = objectIdentifier; + } + + /** + * Returns an org.glyptodon.guacamole.net.auth.permission.Permission + * representation of this APIPermission. + * + * @return An org.glyptodon.guacamole.net.auth.permission.Permission + * representation of this APIPermission. + */ + public Permission getPermission() { + switch(this.objectType) { + case CONNECTION: + return new ConnectionPermission + (this.objectPermissionType, this.objectIdentifier); + case CONNECTION_GROUP: + return new ConnectionGroupPermission + (this.objectPermissionType, this.objectIdentifier); + case USER: + return new UserPermission + (this.objectPermissionType, this.objectIdentifier); + case SYSTEM: + return new SystemPermission(this.systemPermissionType); + default: + return null; + } + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java new file mode 100644 index 000000000..7815dee8c --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java @@ -0,0 +1,158 @@ +package org.glyptodon.guacamole.net.basic.rest.permission; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import com.google.inject.Inject; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; +import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.permission.Permission; +import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A REST Service for handling connection CRUD operations. + * + * @author James Muehlner + */ +@Path("/api/permission") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class PermissionRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(PermissionRESTService.class); + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + + /** + * A service for managing the REST endpoint APIPermission objects. + */ + @Inject + private PermissionService permissionService; + + /** + * Gets a list of permissions for the user with the given userID. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The ID of the user to retrieve permissions for. + * @return The permission list. + */ + @GET + @Path("/{userID}") + public List getPermissions(@QueryParam("token") String authToken, @PathParam("userID") String userID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the user + User user = userContext.getUserDirectory().get(userID); + + if(user == null) + throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); + + return permissionService.convertPermissionList(user.getPermissions()); + + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while listing permissions.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Sets the permissions for a user with the given userID. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The user ID to retrieve permissions for. + * @param permissions The permissions to set for the user with the given userID. + */ + @POST + @Path("/{userID}") + public void setPermissions(@QueryParam("token") String authToken, + @PathParam("userID") String userID, List permissions) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the user + User user = userContext.getUserDirectory().get(userID); + + if(user == null) + throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); + + // All the permissions the user should have after this operation + Set newPermissions = permissionService.convertAPIPermissionList(permissions); + + // Get the original permissions the user had + Set originalPermissions = user.getPermissions(); + + Set permissionsToRemove = new HashSet(originalPermissions); + + // Find all permissions in the original set, but not the new one + permissionsToRemove.removeAll(newPermissions); + + // Remove all permissions that are no longer wanted + for(Permission permissionToRemove : permissionsToRemove) { + user.removePermission(permissionToRemove); + } + + // Get only those permissions that need to be added + newPermissions.removeAll(originalPermissions); + + // Add all new permissions + for(Permission newPermission : newPermissions) { + user.addPermission(newPermission); + } + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught setting permissions.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java new file mode 100644 index 000000000..2478f3e03 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java @@ -0,0 +1,69 @@ +package org.glyptodon.guacamole.net.basic.rest.permission; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.permission.Permission; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * A service for performing useful manipulations on REST Permissions. + * + * @author James Muehlner + */ +public class PermissionService { + + /** + * Converts a list of Permission to a list of APIPermission objects for + * exposing with the REST endpoints. + * + * @param permissions The Connections to convert for REST endpoint use. + * @return A List of APIPermission objects for use with the REST endpoint. + */ + public List convertPermissionList(Iterable permissions) + throws GuacamoleException { + List restPermissions = new ArrayList(); + + for(Permission permission : permissions) { + restPermissions.add(new APIPermission(permission)); + } + + return restPermissions; + } + + /** + * Converts a list of APIPermission to a set of Permission objects for internal + * Guacamole use. + * + * @param restPermissions The APIPermission objects from the REST endpoints. + * @return a List of Permission objects for internal Guacamole use. + */ + public Set convertAPIPermissionList(Iterable restPermissions) { + Set permissions = new HashSet(); + + for(APIPermission restPermission : restPermissions) { + permissions.add(restPermission.getPermission()); + } + + return permissions; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/package-info.java new file mode 100644 index 000000000..df354267c --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/package-info.java @@ -0,0 +1,6 @@ + +/** + * Classes related to the permission manipulation aspect of the Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.permission; +