diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java index 232cdd169..be313877e 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionDirectory.java @@ -180,6 +180,9 @@ public class ConnectionDirectory implements Directory{ // Create connection MySQLConnection connection = connectionService.createConnection( name, object.getConfiguration().getProtocol(), user_id, parentID); + + // Set the connection ID + object.setIdentifier(connection.getIdentifier()); // Add connection parameters createConfigurationValues(connection.getConnectionID(), diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionGroupDirectory.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionGroupDirectory.java index fdcb862aa..0dd2899c3 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionGroupDirectory.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ConnectionGroupDirectory.java @@ -168,6 +168,9 @@ public class ConnectionGroupDirectory implements Directory { // Get user MySQLUser user = userService.retrieveUser(identifier); + + if(user == null) + return null; // Verify access is granted permissionCheckService.verifyUserAccess(this.user_id, @@ -157,7 +160,7 @@ public class UserDirectory implements Directory { MySQLConstants.USER_READ); // Return user - return userService.retrieveUser(identifier); + return user; } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java index f4954cb32..2e96c54ac 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java @@ -348,7 +348,7 @@ public class ConnectionGroupService { } /** - * Creates a new connection group having the given name and protocol. + * Creates a new connection group having the given name and type. * * @param name The name to assign to the new connection group. * @param userID The ID of the user who created this connection group. diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/properties/LongGuacamoleProperty.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/properties/LongGuacamoleProperty.java new file mode 100644 index 000000000..7f6c86aac --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/properties/LongGuacamoleProperty.java @@ -0,0 +1,67 @@ + +package org.glyptodon.guacamole.properties; + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is guacamole-ext. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; + +/** + * A GuacamoleProperty whose value is an long. + * + * @author James Muehlner + */ +public abstract class LongGuacamoleProperty implements GuacamoleProperty { + + @Override + public Long parseValue(String value) throws GuacamoleException { + + // If no property provided, return null. + if (value == null) + return null; + + try { + Long longValue = new Long(value); + return longValue; + } + catch (NumberFormatException e) { + throw new GuacamoleServerException("Property \"" + getName() + "\" must be an long.", e); + } + + } + +} diff --git a/guacamole/pom.xml b/guacamole/pom.xml index df7115cbd..0de815902 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -110,6 +110,55 @@ 1.6.1 runtime + + + + com.google.inject + guice + 3.0 + + + + + com.google.inject.extensions + guice-servlet + 3.0 + + + + + com.sun.jersey + jersey-server + 1.17.1 + + + + + com.sun.jersey.contribs + jersey-guice + 1.17.1 + + + + + javax.annotation + jsr250-api + 1.0 + + + + + commons-codec + commons-codec + 1.4 + + + + + com.sun.jersey + jersey-json + 1.17.1 + diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java index 91e1d384e..a6054399c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.glyptodon.guacamole.net.basic.properties; */ import org.glyptodon.guacamole.properties.FileGuacamoleProperty; +import org.glyptodon.guacamole.properties.LongGuacamoleProperty; /** * Properties used by the default Guacamole web application. @@ -64,4 +65,14 @@ public class BasicGuacamoleProperties { }; + /** + * The session timeout for the API, in milliseconds. + */ + public static final LongGuacamoleProperty API_SESSION_TIMEOUT = new LongGuacamoleProperty() { + + @Override + public String getName() { return "api-session-timeout"; } + + }; + } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java new file mode 100644 index 000000000..be68456f8 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java @@ -0,0 +1,47 @@ +package org.glyptodon.guacamole.net.basic.rest; + +/* + * 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 simple object to represent an error to be sent from the REST API. + * @author James Muehlner + */ +public class APIError { + + /** + * The error message. + */ + private String message; + + /** + * Get the error message. + * @return The error message. + */ + public String getMessage() { + return message; + } + + /** + * Create a new APIError with the specified error message. + * @param message The error message. + */ + public APIError(String message) { + this.message = message; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java new file mode 100644 index 000000000..34776ec83 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java @@ -0,0 +1,54 @@ +package org.glyptodon.guacamole.net.basic.rest; + +/* + * 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 javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +/** + * An exception that will result in the given HTTP Status and message or entity + * being returned from the API layer. + * + * @author James Muehlner + */ +public class HTTPException extends WebApplicationException { + + /** + * Construct a new HTTPException with the given HTTP status and entity. + * + * @param status The HTTP Status to use for the response. + * @param entity The entity to use as the body of the response. + */ + public HTTPException(Status status, Object entity) { + super(Response.status(status).entity(entity).build()); + } + + /** + * Construct a new HTTPException with the given HTTP status and message. The + * message will be wrapped in an APIError container. + * + * @param status The HTTP Status to use for the response. + * @param entity The entity to wrap in an APIError as the body of the response. + */ + public HTTPException(Status status, String message) { + super(Response.status(status).entity(new APIError(message)).build()); + } + +} 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 new file mode 100644 index 000000000..35d8e9aea --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -0,0 +1,79 @@ +package org.glyptodon.guacamole.net.basic.rest; + +/* + * 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.AbstractModule; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; +import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthTokenGenerator; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.glyptodon.guacamole.net.basic.rest.auth.BasicTokenUserContextMap; +import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator; +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.net.basic.rest.user.UserService; +import org.glyptodon.guacamole.properties.GuacamoleProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Guice Module for setting up dependency injection for the + * Guacamole REST API. + * + * @author James Muehlner + */ +public class RESTModule extends AbstractModule { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(RESTModule.class); + + /** + * The AuthenticationProvider to use to authenticate all requests. + */ + private AuthenticationProvider authProvider; + + @Override + protected void configure() { + + // Get auth provider instance + try { + authProvider = GuacamoleProperties.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER); + } + catch (GuacamoleException e) { + logger.error("Error getting authentication provider from properties.", e); + throw new RuntimeException(e); + } + + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(TokenUserContextMap.class).toInstance(new BasicTokenUserContextMap()); + bind(ConnectionService.class); + bind(ConnectionGroupService.class); + bind(PermissionService.class); + bind(UserService.class); + bind(AuthenticationService.class); + + bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletContextListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletContextListener.java new file mode 100644 index 000000000..50e1783f8 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletContextListener.java @@ -0,0 +1,44 @@ +package org.glyptodon.guacamole.net.basic.rest; + +/* + * 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.Guice; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * A ServletContextListenr to listen for initialization of the servlet context + * in order to set up the REST services. + * + * @author James Muehlner + */ +public class RESTServletContextListener implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + Guice.createInjector( + new RESTServletModule(), + new RESTModule() + ); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) {} + +} 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 new file mode 100644 index 000000000..c12a8b144 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -0,0 +1,54 @@ +package org.glyptodon.guacamole.net.basic.rest; + +/* + * 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.Scopes; +import com.google.inject.servlet.ServletModule; +import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; +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; +import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService; + +/** + * A Guice Module to set up the servlet mappings for the Guacamole REST API. + * + * @author James Muehlner + */ +public class RESTServletModule extends ServletModule { + + @Override + protected void configureServlets() { + + // Set up the API endpoints + bind(ConnectionRESTService.class); + bind(ConnectionGroupRESTService.class); + bind(PermissionRESTService.class); + bind(UserRESTService.class); + bind(LoginRESTService.class); + + // Set up the servlet and JSON mappings + bind(GuiceContainer.class); + bind(JacksonJsonProvider.class).in(Scopes.SINGLETON); + serve("/*").with(GuiceContainer.class); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java new file mode 100644 index 000000000..47bdffcc4 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java @@ -0,0 +1,49 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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 simple object to represent an auth token in the API. + * + * @author James Muehlner + */ +public class APIAuthToken { + + /** + * The auth token. + */ + private String authToken; + + /** + * Get the auth token. + * @return The auth token. + */ + public String getAuthToken() { + return authToken; + } + + /** + * Create a new APIAuthToken Object with the given auth token. + * + * @param authToken The auth token to create the new APIAuthToken with. + */ + public APIAuthToken(String authToken) { + this.authToken = authToken; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthTokenGenerator.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthTokenGenerator.java new file mode 100644 index 000000000..2842fe673 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthTokenGenerator.java @@ -0,0 +1,34 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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 . + */ + +/** + * Generates an auth token for an authenticated user. + * + * @author James Muehlner + */ +public interface AuthTokenGenerator { + + /** + * Get a new auth token. + * + * @return A new auth token. + */ + public String getToken(); +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java new file mode 100644 index 000000000..c4c93cb11 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java @@ -0,0 +1,62 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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 javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response.Status; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.HTTPException; + +/** + * A service for performing authentication checks in REST endpoints. + * + * @author James Muehlner + */ +public class AuthenticationService { + + /** + * The map of auth tokens to users for the REST endpoints. + */ + @Inject + private TokenUserContextMap tokenUserMap; + + /** + * Finds the UserContext for a given auth token, if the auth token represents + * a currently logged in user. Throws an unauthorized error otherwise. + * + * @param authToken The auth token to check against the map of logged in users. + * @return The userContext that corresponds to the provided auth token. + * @throws WebApplicationException If the auth token does not correspond to + * any logged in user. + */ + public UserContext getUserContextFromAuthToken(String authToken) + throws WebApplicationException { + + // Try to get the userContext from the map of logged in users. + UserContext userContext = tokenUserMap.get(authToken); + + // Authentication failed. + if(userContext == null) + throw new HTTPException(Status.UNAUTHORIZED, "Permission Denied."); + + return userContext; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenUserContextMap.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenUserContextMap.java new file mode 100644 index 000000000..5ebb58e46 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenUserContextMap.java @@ -0,0 +1,135 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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 java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; +import org.glyptodon.guacamole.properties.GuacamoleProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A basic, HashMap-based implementation of the TokenUserContextMap with support + * for session timeouts. + * + * @author James Muehlner + */ +public class BasicTokenUserContextMap implements TokenUserContextMap { + + /** + * Logger for this class. + */ + private static Logger logger = LoggerFactory.getLogger(BasicTokenUserContextMap.class); + + /** + * The last time a user with a specific auth token accessed the API. + */ + private Map lastAccessTimeMap = new HashMap(); + + /** + * Keeps track of the authToken to UserContext mapping. + */ + private Map userContextMap = new HashMap(); + + /** + * The session timeout configuration for an API session, in milliseconds. + */ + private final long SESSION_TIMEOUT; + + /** + * Create a new BasicTokenUserContextMap and initialize the session timeout value. + */ + public BasicTokenUserContextMap() { + + // Set up the SESSION_TIMEOUT value, with a one hour default. + long sessionTimeoutValue; + try { + sessionTimeoutValue = GuacamoleProperties.getProperty(BasicGuacamoleProperties.API_SESSION_TIMEOUT, 3600000l); + } + catch (GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while reading API_SESSION_TIMEOUT property. Defaulting to 1 hour.", e); + sessionTimeoutValue = 3600000l; + } + + SESSION_TIMEOUT = sessionTimeoutValue; + + } + + /** + * Evict an authentication token from the map of logged in users and last + * access times. + * + * @param authToken The authentication token to evict. + */ + private void evict(String authToken) { + userContextMap.remove(authToken); + lastAccessTimeMap.remove(authToken); + } + + /** + * Log that the user represented by this auth token has just used the API. + * + * @param authToken The authentication token to record access time for. + */ + private void logAccessTime(String authToken) { + lastAccessTimeMap.put(authToken, new Date().getTime()); + } + + /** + * Check if a session has timed out. + * @param authToken The auth token for the session. + * @return True if the session has timed out, false otherwise. + */ + private boolean sessionHasTimedOut(String authToken) { + if(!lastAccessTimeMap.containsKey(authToken)) + return true; + + long lastAccessTime = lastAccessTimeMap.get(authToken); + long currentTime = new Date().getTime(); + + return currentTime - lastAccessTime > SESSION_TIMEOUT; + } + + @Override + public UserContext get(String authToken) { + + // If the session has timed out, evict the token and force the user to log in again + if(sessionHasTimedOut(authToken)) { + evict(authToken); + return null; + } + + // Update the last access time and return the UserContext + logAccessTime(authToken); + return userContextMap.get(authToken); + } + + @Override + public void put(String authToken, UserContext userContext) { + + // Update the last access time, and create the token/UserContext mapping + logAccessTime(authToken); + userContextMap.put(authToken, userContext); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/LoginRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/LoginRESTService.java new file mode 100644 index 000000000..9971bf359 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/LoginRESTService.java @@ -0,0 +1,109 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +import com.google.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +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.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.net.basic.rest.HTTPException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * 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 authenticating to the Guacamole REST API. Given valid + * credentials, the service will return an auth token. Invalid credentials will + * result in a permission error. + * + * @author James Muehlner + */ + + +@Path("/api/login") +@Produces(MediaType.APPLICATION_JSON) +public class LoginRESTService { + + /** + * The authentication provider used to authenticate this user. + */ + @Inject + private AuthenticationProvider authProvider; + + /** + * The map of auth tokens to users for the REST endpoints. + */ + @Inject + private TokenUserContextMap tokenUserMap; + + /** + * A generator for creating new auth tokens. + */ + @Inject + private AuthTokenGenerator authTokenGenerator; + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(LoginRESTService.class); + + /** + * Authenticates a user, generates an auth token, associates that auth token + * with the user's UserContext for use by further requests. + * + * @param username The username of the user who is to be authenticated. + * @param password The password of the user who is to be authenticated. + * @return The auth token for the newly logged-in user. + */ + @POST + public APIAuthToken login(@QueryParam("username") String username, + @QueryParam("password") String password) { + + Credentials credentials = new Credentials(); + credentials.setUsername(username); + credentials.setPassword(password); + + UserContext userContext; + + try { + userContext = authProvider.getUserContext(credentials); + } catch(GuacamoleException e) { + logger.error("Exception caught while authenticating user.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, + "Unexpected server error."); + } + + // authentication failed. + if(userContext == null) + throw new HTTPException(Status.UNAUTHORIZED, "Permission Denied."); + + String authToken = authTokenGenerator.getToken(); + + tokenUserMap.put(authToken, userContext); + + return new APIAuthToken(authToken); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/SecureRandomAuthTokenGenerator.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/SecureRandomAuthTokenGenerator.java new file mode 100644 index 000000000..54f89197a --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/SecureRandomAuthTokenGenerator.java @@ -0,0 +1,44 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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 java.security.SecureRandom; +import org.apache.commons.codec.binary.Hex; + +/** + * An implementation of the AuthTokenGenerator based around SecureRandom. + * + * @author James Muehlner + */ +public class SecureRandomAuthTokenGenerator implements AuthTokenGenerator { + + /** + * Instance of SecureRandom for generating the auth token. + */ + private SecureRandom secureRandom = new SecureRandom(); + + @Override + public String getToken() { + byte[] bytes = new byte[32]; + secureRandom.nextBytes(bytes); + + return Hex.encodeHexString(bytes); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenUserContextMap.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenUserContextMap.java new file mode 100644 index 000000000..55ad45a8d --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenUserContextMap.java @@ -0,0 +1,49 @@ +package org.glyptodon.guacamole.net.basic.rest.auth; + +/* + * 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.UserContext; + +/** + * Represents a mapping of auth token to user context for the REST + * authentication system. + * + * @author James Muehlner + */ +public interface TokenUserContextMap { + + /** + * Registers that a user has just logged in with the specified authToken and + * UserContext. + * + * @param authToken The authentication token for the logged in user. + * @param userContext The UserContext for the logged in user. + */ + public void put(String authToken, UserContext userContext); + + /** + * Get the UserContext for a logged in user. If the auth token does not + * represent a user who is currently logged in, returns null. + * + * @param authToken The authentication token for the logged in user. + * @return The UserContext for the given auth token, if the auth token + * represents a currently logged in user, null otherwise. + */ + public UserContext get(String authToken); +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java new file mode 100644 index 000000000..cb88a3e0d --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java @@ -0,0 +1,6 @@ + +/** + * Classes related to the authentication aspect of the Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.auth; + diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java new file mode 100644 index 000000000..14a610aeb --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java @@ -0,0 +1,127 @@ +package org.glyptodon.guacamole.net.basic.rest.connection; + +/* + * 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 java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; + +/** + * A simple connection to expose through the REST endpoints. + * + * @author James Muehlner + */ +public class APIConnection { + + /** + * The name of this connection. + */ + private String name; + + /** + * The identifier of this connection. + */ + private String identifier; + + /** + * The history records associated with this connection. + */ + private List history; + + /** + * Map of all associated parameter values, indexed by parameter name. + */ + private Map parameters = new HashMap(); + + /** + * Create an empty APIConnection. + */ + public APIConnection() {} + + /** + * Create an APIConnection from a Connection record. + * @param connection The connection to create this APIConnection from. + * @throws GuacamoleException If a problem is encountered while + * instantiating this new APIConnection. + */ + public APIConnection(Connection connection) + throws GuacamoleException { + this.name = connection.getName(); + this.identifier = connection.getIdentifier(); + this.history = connection.getHistory(); + } + + /** + * Returns the name of this connection. + * @return The name of this connection. + */ + public String getName() { + return name; + } + + /** + * Set the name of this connection. + * @param name The name of this connection. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the unique identifier for this connection. + * @return The unique identifier for this connection. + */ + public String getIdentifier() { + return identifier; + } + /** + * Sets the unique identifier for this connection. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Returns the history records associated with this connection. + * @return The history records associated with this connection. + */ + public List getHistory() { + return history; + } + + /** + * Returns the parameter map for this connection. + * @return The parameter map for this connection. + */ + public Map getParameters() { + return parameters; + } + + /** + * Sets the parameter map for this connection. + * @param parameters The parameter map for this connection. + */ + public void setParameters(Map parameters) { + this.parameters = parameters; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java new file mode 100644 index 000000000..0b9404ac6 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java @@ -0,0 +1,100 @@ +package org.glyptodon.guacamole.net.basic.rest.connection; + +/* + * 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 java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.GuacamoleSocket; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; +import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; +import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; + +/** + * A wrapper to make an APIConnection look like a Connection. Useful where a + * org.glyptodon.guacamole.net.auth.Connection is required. + * + * @author James Muehlner + */ +public class APIConnectionWrapper implements Connection { + + private final APIConnection apiConnection; + + public APIConnectionWrapper(APIConnection apiConnection) { + this.apiConnection = apiConnection; + } + + @Override + public String getName() { + return apiConnection.getName(); + } + + @Override + public void setName(String name) { + apiConnection.setName(name); + } + + @Override + public String getIdentifier() { + return apiConnection.getIdentifier(); + } + + @Override + public void setIdentifier(String identifier) { + apiConnection.setIdentifier(identifier); + } + + @Override + public GuacamoleConfiguration getConfiguration() { + + // Create the GuacamoleConfiguration from the parameter map + GuacamoleConfiguration configuration = new GuacamoleConfiguration(); + + Map parameters = apiConnection.getParameters(); + + for(String key : parameters.keySet()) + configuration.setParameter(key, parameters.get(key)); + + return configuration; + } + + @Override + public void setConfiguration(GuacamoleConfiguration config) { + + // Create a parameter map from the GuacamoleConfiguration + Map newParameters = new HashMap(); + for(String key : config.getParameterNames()) + newParameters.put(key, config.getParameter(key)); + + apiConnection.setParameters(newParameters); + } + + @Override + public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } + + @Override + public List getHistory() throws GuacamoleException { + return apiConnection.getHistory(); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java new file mode 100644 index 000000000..6a0e70d96 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -0,0 +1,320 @@ +package org.glyptodon.guacamole.net.basic.rest.connection; + +/* + * 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.ArrayList; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +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.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.UserContext; +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/connection") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ConnectionRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ConnectionRESTService.class); + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + + /** + * A service for managing the REST endpoint APIConnection objects. + */ + @Inject + private ConnectionService connectionService; + + /** + * Gets a list of connections with the given ConnectionGroup parentID. + * If no parentID is provided, returns the connections from the root group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param parentID The ID of the ConnectionGroup the connections + * belong to. If null, the root connection group will be used. + * @return The connection list. + */ + @GET + public List getConnections(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // If the parent connection group is passed in, try to find it. + ConnectionGroup parentConnectionGroup; + if(parentID == null) + parentConnectionGroup = userContext.getRootConnectionGroup(); + else { + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + parentConnectionGroup = connectionGroupDirectory.get(parentID); + } + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + Directory connectionDirectory = + parentConnectionGroup.getConnectionDirectory(); + + // Return the converted connection directory + return connectionService.convertConnectionList(connectionDirectory); + } 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 connections.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Gets an individual connection. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the Connection.. + * @return The connection. + */ + @GET + @Path("/{connectionID}") + public APIConnection getConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Get the connection + Connection connection = connectionDirectory.get(connectionID); + + if(connection == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + return new APIConnection(connection); + } 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 getting connection.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Deletes an individual connection. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the Connection to delete. + */ + @DELETE + @Path("/{connectionID}") + public void deleteConnection(@QueryParam("token") String authToken, @PathParam("connectionID") String connectionID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Make sure the connection is there before trying to delete + if(connectionDirectory.get(connectionID) == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + // Delete the connection + connectionDirectory.remove(connectionID); + } 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 deleting connection.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Creates a new connection and returns the identifier of the new connection. + * If a parentID is provided, the connection will be created in the + * connection group with the parentID. Otherwise, the root connection group + * will be used. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param parentID The ID of the ConnectionGroup the connections + * belong to. If null, the root connection group will be used. + * @param connection The connection to create. + * @return The identifier of the new connection. + */ + @POST + public String createConnection(@QueryParam("token") String authToken, + @QueryParam("parentID") String parentID, APIConnection connection) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + if(connection == null) + throw new GuacamoleClientException("A connection is required for this request."); + + // If the parent connection group is passed in, try to find it. + ConnectionGroup parentConnectionGroup; + if(parentID == null) + parentConnectionGroup = userContext.getRootConnectionGroup(); + else { + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + parentConnectionGroup = connectionGroupDirectory.get(parentID); + } + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + Directory connectionDirectory = + parentConnectionGroup.getConnectionDirectory(); + + // Create the connection + connectionDirectory.add(new APIConnectionWrapper(connection)); + + // Return the new connection identifier + return connection.getIdentifier(); + } 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 creating connection.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Updates a connection. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the Connection to move. + * @param connection The connection to update. + */ + @POST + @Path("/{connectionID}") + public void updateConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID, APIConnection connection) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + if(connection == null) + throw new GuacamoleClientException("A connection is required for this request."); + + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Make sure the connection is there before trying to update + if(connectionDirectory.get(connectionID) == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + // Update the connection + connectionDirectory.update(new APIConnectionWrapper(connection)); + } 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 updating connection.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Moves an individual connection to a different connection group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the Connection to move. + * @param parentID The ID of the ConnectionGroup the connections + * belong to. If null, the root connection group will be used. + */ + @PUT + @Path("/{connectionID}") + public void moveConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID, @QueryParam("parentID") String parentID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Find the new parent connection group + Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + ConnectionGroup parentConnectionGroup = connectionGroupDirectory.get(parentID); + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + // Move the connection + connectionDirectory.move(connectionID, parentConnectionGroup.getConnectionDirectory()); + } 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 moving connection.", 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/connection/ConnectionService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java new file mode 100644 index 000000000..2a669f5ee --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java @@ -0,0 +1,53 @@ +package org.glyptodon.guacamole.net.basic.rest.connection; + +import java.util.ArrayList; +import java.util.List; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.Directory; + +/* + * 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 Connections. + * + * @author James Muehlner + */ +public class ConnectionService { + + /** + * Converts a Connection Directory to a list of APIConnection objects for + * exposing with the REST endpoints. + * + * @param connectionDirectory The Connection Directory to convert for REST endpoint use. + * @return A List of APIConnection objects for use with the REST endpoint. + * @throws GuacamoleException If an error occurs while converting the + * connection directory. + */ + public List convertConnectionList(Directory connectionDirectory) + throws GuacamoleException { + List restConnections = new ArrayList(); + + for(String connectionID : connectionDirectory.getIdentifiers()) { + restConnections.add(new APIConnection(connectionDirectory.get(connectionID))); + } + + return restConnections; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java new file mode 100644 index 000000000..8e4dcfde6 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java @@ -0,0 +1,6 @@ + +/** + * Classes related to the connection manipulation aspect of the Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.connection; + diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java new file mode 100644 index 000000000..0dfbc177f --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -0,0 +1,110 @@ +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.ConnectionGroup.Type; + +/* + * 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 simple connection group to expose through the REST endpoints. + * + * @author James Muehlner + */ +public class APIConnectionGroup { + + /** + * The name of this connection group. + */ + private String name; + + /** + * The identifier of this connection group. + */ + private String identifier; + + /** + * The type of this connection group. + */ + private Type type; + + /** + * Create an empty APIConnectionGroup. + */ + public APIConnectionGroup() {} + + /** + * Create a new APIConnectionGroup from the given ConnectionGroup record. + * + * @param connectionGroup The ConnectionGroup record to initialize this + * APIConnectionGroup from. + */ + public APIConnectionGroup(ConnectionGroup connectionGroup) { + this.identifier = connectionGroup.getIdentifier(); + this.name = connectionGroup.getName(); + this.type = connectionGroup.getType(); + } + + /** + * Returns the name of this connection group. + * @return The name of this connection group. + */ + public String getName() { + return name; + } + + /** + * Set the name of this connection group. + * @param name The name of this connection group. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the identifier of this connection group. + * @return The identifier of this connection group. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Set the identifier of this connection group. + * @param identifier The identifier of this connection group. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Returns the type of this connection group. + * @return The type of this connection group. + */ + public Type getType() { + return type; + } + + /** + * Set the type of this connection group. + * @param type The Type of this connection group. + */ + public void setType(Type type) { + this.type = type; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroupWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroupWrapper.java new file mode 100644 index 000000000..c61d3812e --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroupWrapper.java @@ -0,0 +1,95 @@ +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +/* + * 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.GuacamoleException; +import org.glyptodon.guacamole.net.GuacamoleSocket; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; + +/** + * A wrapper to make an APIConnection look like a ConnectionGroup. + * Useful where a org.glyptodon.guacamole.net.auth.ConnectionGroup is required. + * + * @author James Muehlner + */ +public class APIConnectionGroupWrapper implements ConnectionGroup { + + /** + * The wrapped APIConnectionGroup. + */ + private APIConnectionGroup apiConnectionGroup; + + /** + * Create a new APIConnectionGroupWrapper to wrap the given + * APIConnectionGroup as a ConnectionGroup. + * @param apiConnectionGroup the APIConnectionGroup to wrap. + */ + public APIConnectionGroupWrapper(APIConnectionGroup apiConnectionGroup) { + this.apiConnectionGroup = apiConnectionGroup; + } + + @Override + public String getName() { + return apiConnectionGroup.getName(); + } + + @Override + public void setName(String name) { + apiConnectionGroup.setName(name); + } + + @Override + public String getIdentifier() { + return apiConnectionGroup.getIdentifier(); + } + + @Override + public void setIdentifier(String identifier) { + apiConnectionGroup.setIdentifier(identifier); + } + + @Override + public void setType(Type type) { + apiConnectionGroup.setType(type); + } + + @Override + public Type getType() { + return apiConnectionGroup.getType(); + } + + @Override + public Directory getConnectionDirectory() throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } + + @Override + public Directory getConnectionGroupDirectory() throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } + + @Override + public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java new file mode 100644 index 000000000..5dede8ac9 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -0,0 +1,320 @@ +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +/* + * 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.ArrayList; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +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.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.UserContext; +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 group CRUD operations. + * + * @author James Muehlner + */ +@Path("/api/connectionGroup") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ConnectionGroupRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ConnectionGroupRESTService.class); + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + + /** + * A service for managing the REST endpoint APIConnection objects. + */ + @Inject + private ConnectionGroupService connectionGroupService; + + /** + * Gets a list of connection groups with the given ConnectionGroup parentID. + * If no parentID is provided, returns the connection groups from the root group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param parentID The ID of the ConnectionGroup the connection groups + * belong to. If null, the root connection group will be used. + * @return The connection list. + */ + @GET + public List getConnectionGroups(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // If the parent connection group is passed in, try to find it. + ConnectionGroup parentConnectionGroup; + if(parentID == null) + parentConnectionGroup = userContext.getRootConnectionGroup(); + else { + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + parentConnectionGroup = connectionGroupDirectory.get(parentID); + } + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + Directory connectionGroupDirectory = + parentConnectionGroup.getConnectionGroupDirectory(); + + // return the converted connection group list + return connectionGroupService.convertConnectionGroupList(connectionGroupDirectory); + } 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 connection groups.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Gets an individual connection group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionGroupID The ID of the ConnectionGroup. + * @return The connection group. + */ + @GET + @Path("/{connectionGroupID}") + public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = + rootGroup.getConnectionGroupDirectory(); + + // Get the connection group + ConnectionGroup connectionGroup = connectionGroupDirectory.get(connectionGroupID); + + if(connectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided ID."); + + // Return the connectiion group + return new APIConnectionGroup(connectionGroup); + } 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 getting connection group.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Deletes an individual connection group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionGroupID The ID of the ConnectionGroup to delete. + */ + @DELETE + @Path("/{connectionGroupID}") + public void deleteConnectionGroup(@QueryParam("token") String authToken, @PathParam("connectionGroupID") String connectionGroupID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = + rootGroup.getConnectionGroupDirectory(); + + // Make sure the connection is there before trying to delete + if(connectionGroupDirectory.get(connectionGroupID) == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided ID."); + + // Delete the connection group + connectionGroupDirectory.remove(connectionGroupID); + } 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 deleting connection group.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Creates a new connection group and returns the identifier of the new connection group. + * If a parentID is provided, the connection group will be created in the + * connection group with the parentID. Otherwise, the root connection group + * will be used. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param parentID The ID of the ConnectionGroup the connection groups + * belong to. If null, the root connection group will be used. + * @param connection The connection group to create. + * @return The identifier of the new connection group. + */ + @POST + public String createConnectionGroup(@QueryParam("token") String authToken, + @QueryParam("parentID") String parentID, APIConnectionGroup connectionGroup) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + if(connectionGroup == null) + throw new GuacamoleClientException("A connection group is required for this request."); + + // If the parent connection group is passed in, try to find it. + ConnectionGroup parentConnectionGroup; + if(parentID == null) + parentConnectionGroup = userContext.getRootConnectionGroup(); + else { + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + parentConnectionGroup = connectionGroupDirectory.get(parentID); + } + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + Directory connectionGroupDirectory = + parentConnectionGroup.getConnectionGroupDirectory(); + + // Create the connection group + connectionGroupDirectory.add(new APIConnectionGroupWrapper(connectionGroup)); + + // Return the new connection group identifier + return connectionGroup.getIdentifier(); + } 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 creating connection group.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Updates a connection group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the ConnectionGroup to update. + * @param connection The connection group to update. + */ + @POST + @Path("/{connectionGroupID}") + public void updateConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID, APIConnectionGroup connectionGroup) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + if(connectionGroup == null) + throw new GuacamoleClientException("A connection is required for this request."); + + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = + rootGroup.getConnectionGroupDirectory(); + + // Make sure the connection group is there before trying to update + if(connectionGroupDirectory.get(connectionGroupID) == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided ID."); + + // Update the connection group + connectionGroupDirectory.update(new APIConnectionGroupWrapper(connectionGroup)); + } 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 updating connection group.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Moves an individual connection group to a different connection group. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param connectionID The ID of the ConnectionGroup to move. + * @param parentID The ID of the ConnectionGroup the connection groups + * belong to. If null, the root connection group will be used. + */ + @PUT + @Path("/{connectionGroupID}") + public void moveConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID, @QueryParam("parentID") String parentID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionGroupDirectory = + rootGroup.getConnectionGroupDirectory(); + + // Find the new parent connection group + Directory newConnectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); + ConnectionGroup parentConnectionGroup = newConnectionGroupDirectory.get(parentID); + + if(parentConnectionGroup == null) + throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); + + // Move the connection group + connectionGroupDirectory.move(connectionGroupID, parentConnectionGroup.getConnectionGroupDirectory()); + } 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 moving connection group.", 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/connectiongroup/ConnectionGroupService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java new file mode 100644 index 000000000..1da2fa507 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java @@ -0,0 +1,53 @@ +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +import java.util.ArrayList; +import java.util.List; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; + +/* + * 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 ConnectionGroups. + * + * @author James Muehlner + */ +public class ConnectionGroupService { + + /** + * Converts a ConnectionGroup directory to a list of APIConnectionGroup + * objects for exposing with the REST endpoints. + * + * @param connectionGroupDirectory The ConnectionGroup Directory to convert for REST endpoint use. + * @return A List of APIConnectionGroup objects for use with the REST endpoint. + * @throws GuacamoleException If an error occurs while converting the + * connection group directory. + */ + public List convertConnectionGroupList( + Directory connectionGroupDirectory) throws GuacamoleException { + List restConnectionGroups = new ArrayList(); + + for(String connectionGroupID : connectionGroupDirectory.getIdentifiers()) { + restConnectionGroups.add(new APIConnectionGroup(connectionGroupDirectory.get(connectionGroupID))); + } + + return restConnectionGroups; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/package-info.java new file mode 100644 index 000000000..9b0a3cf17 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/package-info.java @@ -0,0 +1,7 @@ + +/** + * Classes related to the connection group manipulation aspect + * of the Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java new file mode 100644 index 000000000..3abc5554b --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java @@ -0,0 +1,6 @@ + +/** + * Classes related to the basic Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest; + 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..63e842e41 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java @@ -0,0 +1,200 @@ +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.codehaus.jackson.map.annotate.JsonSerialize; +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 + */ +@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) +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(); + this.objectIdentifier = ((ConnectionPermission) permission).getObjectIdentifier(); + } else if(permission instanceof ConnectionGroupPermission) { + this.objectType = ObjectType.CONNECTION_GROUP; + + this.objectPermissionType = ((ConnectionGroupPermission) permission).getType(); + this.objectIdentifier = ((ConnectionGroupPermission) permission).getObjectIdentifier(); + } else if(permission instanceof UserPermission) { + this.objectType = ObjectType.USER; + + this.objectPermissionType = ((UserPermission) permission).getType(); + this.objectIdentifier = ((UserPermission) permission).getObjectIdentifier(); + } 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 toPermission() { + 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..91d779dc4 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java @@ -0,0 +1,167 @@ +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.List; +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.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."); + } + } + + /** + * Adds a 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 add the permission for. + * @param permission The permission to add for the user with the given userID. + */ + @POST + @Path("/{userID}") + public void addPermission(@QueryParam("token") String authToken, + @PathParam("userID") String userID, APIPermission permission) { + 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."); + + // Add the new permission + user.addPermission(permission.toPermission()); + } 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 adding permission.", e); + throw new HTTPException(Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Removes a 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 remove the permission for. + * @param permission The permission to remove for the user with the given userID. + */ + @POST + @Path("/remove{userID}/") + public void removePermission(@QueryParam("token") String authToken, + @PathParam("userID") String userID, APIPermission permission) { + 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."); + + // Remove the permission + user.removePermission(permission.toPermission()); + } 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 removing permission.", 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..85c265054 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java @@ -0,0 +1,67 @@ +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.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) { + 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.toPermission()); + } + + 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; + diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUser.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUser.java new file mode 100644 index 000000000..66b9ec3d5 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUser.java @@ -0,0 +1,82 @@ +package org.glyptodon.guacamole.net.basic.rest.user; + +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.glyptodon.guacamole.net.auth.User; + +/* + * 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 simple User to expose through the REST endpoints. + * + * @author James Muehlner + */ +@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) +public class APIUser { + + /** + * The username of this user. + */ + private String username; + + /** + * The password of this user. + */ + private String password; + + /** + * Construct a new APIUser from the provided User. + * @param user The User to construct the APIUser from. + */ + public APIUser(User user) { + this.username = user.getUsername(); + this.password = user.getPassword(); + } + + /** + * Returns the username for this user. + * @return The username for this user. + */ + public String getUsername() { + return username; + } + + /** + * Set the username for this user. + * @param username The username for this user. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns the password for this user. + * @return The password for this user. + */ + public String getPassword() { + return password; + } + + /** + * Set the password for this user. + * @param password The password for this user. + */ + public void setPassword(String password) { + this.password = password; + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUserWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUserWrapper.java new file mode 100644 index 000000000..db347457c --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUserWrapper.java @@ -0,0 +1,103 @@ +package org.glyptodon.guacamole.net.basic.rest.user; + +/* + * 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 java.util.Collections; +import java.util.Set; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.permission.Permission; + +/** + * A wrapper to make an APIConnection look like a User. Useful where a + * org.glyptodon.guacamole.net.auth.User is required. + * + * @author James Muehlner + */ +public class APIUserWrapper implements User { + + /** + * The wrapped APIUser. + */ + private APIUser apiUser; + + /** + * The set of permissions for this user. + * NOTE: Not exposed by the REST endpoints. + */ + private Set permissionSet = Collections.EMPTY_SET; + + /** + * Wrap a given APIUser to expose as a User. + * @param apiUser The APIUser to wrap. + */ + public APIUserWrapper(APIUser apiUser) { + this.apiUser = apiUser; + } + + /** + * Wrap a given APIUser to expose as a User, with the given permission set. + * @param apiUser The APIUser to wrap. + * @param permissionSet The set of permissions for the wrapped user. + */ + public APIUserWrapper(APIUser apiUser, Set permissionSet) { + this.apiUser = apiUser; + this.permissionSet = permissionSet; + } + + @Override + public String getUsername() { + return apiUser.getUsername(); + } + + @Override + public void setUsername(String username) { + apiUser.setUsername(username); + } + + @Override + public String getPassword() { + return apiUser.getPassword(); + } + + @Override + public void setPassword(String password) { + apiUser.setPassword(password); + } + + @Override + public Set getPermissions() throws GuacamoleException { + return permissionSet; + } + + @Override + public boolean hasPermission(Permission permission) throws GuacamoleException { + return permissionSet.contains(permission); + } + + @Override + public void addPermission(Permission permission) throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } + + @Override + public void removePermission(Permission permission) throws GuacamoleException { + throw new UnsupportedOperationException("Operation not supported."); + } +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java new file mode 100644 index 000000000..e9fa90b94 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -0,0 +1,231 @@ +package org.glyptodon.guacamole.net.basic.rest.user; + +import com.google.inject.Inject; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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; +import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.UserContext; +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; + +/* + * 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 REST Service for handling user CRUD operations. + * + * @author James Muehlner + */ +@Path("/api/user") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class UserRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(UserRESTService.class); + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + + /** + * A service for managing the REST endpoint APIPermission objects. + */ + @Inject + private UserService userService; + + /** + * Gets a list of users in the system. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @return The user list. + */ + @GET + public List getUsers(@QueryParam("token") String authToken) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Convert and return the user directory listing + return userService.convertUserList(userDirectory); + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while listing users.", e); + throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Gets an individual user. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @return user The user. + */ + @GET + @Path("/{userID}") + public APIUser getUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Get the user + User user = userDirectory.get(userID); + + if(user == null) + throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID."); + + // Return the user + return new APIUser(user); + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while getting user.", e); + throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Creates a new user and returns the username. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param user The new user to create. + */ + @POST + public String createUser(@QueryParam("token") String authToken, APIUser user) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Create the user + userDirectory.add(new APIUserWrapper(user)); + + return user.getUsername(); + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while creating user.", e); + throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Updates an individual existing user. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The unique identifier of the user to update. + * @param user The updated user. + */ + @POST + @Path("/{userID}") + public void updateUser(@QueryParam("token") String authToken, @PathParam("userID") String userID, APIUser user) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + if(!user.getUsername().equals(userID)) + throw new HTTPException(Response.Status.BAD_REQUEST, "Username does not match provided userID."); + + // Get the user + User existingUser = userDirectory.get(userID); + + if(existingUser == null) + throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID."); + + /* + * Update the user with the permission set from the existing user + * since the user REST endpoints do not expose permissions + */ + userDirectory.update(new APIUserWrapper(user, existingUser.getPermissions())); + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while updating user.", e); + throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + } + } + + /** + * Deletes an individual existing user. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The unique identifier of the user to delete. + */ + @DELETE + @Path("/{userID}") + public void deleteUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) { + UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken); + + try { + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Get the user + User existingUser = userDirectory.get(userID); + + if(existingUser == null) + throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID."); + + // Delete the user + userDirectory.remove(userID); + } catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied."); + } catch(GuacamoleClientException e) { + throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + } catch(GuacamoleException e) { + logger.error("Unexpected GuacamoleException caught while deleting user.", e); + throw new HTTPException(Response.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/user/UserService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java new file mode 100644 index 000000000..5c6de105f --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java @@ -0,0 +1,54 @@ +package org.glyptodon.guacamole.net.basic.rest.user; + +import java.util.ArrayList; +import java.util.List; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; + +/* + * 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 Users. + * + * @author James Muehlner + */ +public class UserService { + + /** + * Converts a user directory to a list of APIUser objects for + * exposing with the REST endpoints. + * + * @param userDirectory The user directory to convert for REST endpoint use. + * @return A List of APIUser objects for use with the REST endpoint. + * @throws GuacamoleException If an error occurs while converting the + * user directory. + */ + public List convertUserList(Directory userDirectory) + throws GuacamoleException { + List restUsers = new ArrayList(); + + for(String username : userDirectory.getIdentifiers()) { + restUsers.add(new APIUser(userDirectory.get(username))); + } + + return restUsers; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java new file mode 100644 index 000000000..aa411d037 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java @@ -0,0 +1,6 @@ + +/** + * Classes related to the user manipulation aspect of the Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.user; + diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml index 728d5e699..950848b6f 100644 --- a/guacamole/src/main/webapp/WEB-INF/web.xml +++ b/guacamole/src/main/webapp/WEB-INF/web.xml @@ -241,7 +241,21 @@ Tunnel /tunnel + + + guiceFilter + com.google.inject.servlet.GuiceFilter + + + guiceFilter + /api/* + + + + org.glyptodon.guacamole.net.basic.rest.RESTServletContextListener + + mp3 audio/mpeg