diff --git a/.gitignore b/.gitignore index bcc52fede..d26be9c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *~ target/ +nb-configuration.xml diff --git a/guacamole/lib/unknown/binary/javax.ws.rs-api-2.0/SNAPSHOT/javax.ws.rs-api-2.0-SNAPSHOT.jar b/guacamole/lib/unknown/binary/javax.ws.rs-api-2.0/SNAPSHOT/javax.ws.rs-api-2.0-SNAPSHOT.jar new file mode 100644 index 000000000..b9de41659 Binary files /dev/null and b/guacamole/lib/unknown/binary/javax.ws.rs-api-2.0/SNAPSHOT/javax.ws.rs-api-2.0-SNAPSHOT.jar differ diff --git a/guacamole/pom.xml b/guacamole/pom.xml index 33fe0fd6b..723f43941 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -185,6 +185,55 @@ provided + + + 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/AuthenticatingFilter.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/AuthenticatingFilter.java deleted file mode 100644 index 600dbfd41..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/AuthenticatingFilter.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Collection; -import java.util.regex.Pattern; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.xml.bind.DatatypeConverter; -import org.glyptodon.guacamole.GuacamoleClientException; -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.event.SessionListenerCollection; -import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; -import org.glyptodon.guacamole.net.event.AuthenticationFailureEvent; -import org.glyptodon.guacamole.net.event.AuthenticationSuccessEvent; -import org.glyptodon.guacamole.net.event.listener.AuthenticationFailureListener; -import org.glyptodon.guacamole.net.event.listener.AuthenticationSuccessListener; -import org.glyptodon.guacamole.properties.GuacamoleProperties; -import org.glyptodon.guacamole.protocol.GuacamoleStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filter which provides watches requests for credentials, authenticating the - * user against the configured AuthenticationProvider if credentials are - * present. Note that if authentication fails, the request is still allowed. To - * restrict access based on the result of authentication, use - * RestrictedHttpServlet or RestrictedFilter. - * - * The user context is retrieved using the authentication provider defined in - * guacamole.properties. The authentication provider has access to the request - * and session, in addition to any submitted username and password, in order - * to authenticate the user. - * - * The user context will be stored in the current HttpSession. - * - * Success and failure are logged. - * - * @author Michael Jumper - */ -public class AuthenticatingFilter implements Filter { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(AuthenticatingFilter.class); - - /** - * The session attribute holding the current UserContext. - */ - public static final String CONTEXT_ATTRIBUTE = "GUAC_CONTEXT"; - - /** - * The session attribute holding the credentials authorizing this session. - */ - public static final String CREDENTIALS_ATTRIBUTE = "GUAC_CREDS"; - - /** - * The session attribute holding the session-scoped clipboard storage. - */ - public static final String CLIPBOARD_ATTRIBUTE = "GUAC_CLIP"; - - /** - * The AuthenticationProvider to use to authenticate all requests. - */ - private AuthenticationProvider authProvider; - - /** - * Whether HTTP authentication should be used (the "Authorization" header). - */ - private boolean useHttpAuthentication; - - @Override - public void init(FilterConfig config) throws ServletException { - - // Parse Guacamole configuration - try { - - // Get auth provider instance - authProvider = GuacamoleProperties.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER); - - // Enable HTTP auth, if requested - useHttpAuthentication = GuacamoleProperties.getProperty(BasicGuacamoleProperties.ENABLE_HTTP_AUTH, false); - - } - catch (GuacamoleException e) { - logger.error("Unable to read guacamole.properties: {}", e.getMessage()); - logger.debug("Error reading guacamole.properties.", e); - throw new ServletException(e); - } - - } - - /** - * Notifies all listeners in the given collection that authentication has - * failed. - * - * @param listeners A collection of all listeners that should be notified. - * @param credentials The credentials associated with the authentication - * request that failed. - */ - private void notifyFailed(Collection listeners, Credentials credentials) { - - // Build event for auth failure - AuthenticationFailureEvent event = new AuthenticationFailureEvent(credentials); - - // Notify all listeners - for (Object listener : listeners) { - try { - if (listener instanceof AuthenticationFailureListener) - ((AuthenticationFailureListener) listener).authenticationFailed(event); - } - catch (GuacamoleException e) { - logger.debug("Error notifying AuthenticationFailureListener: {}", e); - } - } - - } - - /** - * Notifies all listeners in the given collection that authentication was - * successful. - * - * @param listeners A collection of all listeners that should be notified. - * @param context The UserContext created as a result of authentication - * success. - * @param credentials The credentials associated with the authentication - * request that succeeded. - * @return true if all listeners are allowing the authentication success, - * or if there are no listeners, and false if any listener is - * canceling the authentication success. Note that once one - * listener cancels, no other listeners will run. - * @throws GuacamoleException If any listener throws an error while being - * notified. Note that if any listener throws an - * error, the success is canceled, and no other - * listeners will run. - */ - private boolean notifySuccess(Collection listeners, UserContext context, - Credentials credentials) throws GuacamoleException { - - // Build event for auth success - AuthenticationSuccessEvent event = - new AuthenticationSuccessEvent(context, credentials); - - // Notify all listeners - for (Object listener : listeners) { - if (listener instanceof AuthenticationSuccessListener) { - - // Cancel immediately if hook returns false - if (!((AuthenticationSuccessListener) listener).authenticationSucceeded(event)) - return false; - - } - } - - return true; - - } - - /** - * Sends an error on the given HTTP response using the information within - * the given GuacamoleStatus. - * - * @param response The HTTP response to use to send the error. - * @param guac_status The status to send - * @param message A human-readable message that can be presented to the - * user. - * @throws ServletException If an error prevents sending of the error - * code. - */ - public static void sendError(HttpServletResponse response, - GuacamoleStatus guac_status, String message) - throws ServletException { - - try { - - // If response not committed, send error code and message - if (!response.isCommitted()) { - response.addHeader("Guacamole-Status-Code", Integer.toString(guac_status.getGuacamoleStatusCode())); - response.addHeader("Guacamole-Error-Message", message); - response.sendError(guac_status.getHttpStatusCode()); - } - - } - catch (IOException ioe) { - - // If unable to send error at all due to I/O problems, - // rethrow as servlet exception - throw new ServletException(ioe); - - } - - } - - /** - * Returns the credentials associated with the given session. - * - * @param session The session to retrieve credentials from. - * @return The credentials associated with the given session. - */ - public static Credentials getCredentials(HttpSession session) { - return (Credentials) session.getAttribute(CREDENTIALS_ATTRIBUTE); - } - - /** - * Returns the UserContext associated with the given session. - * - * @param session The session to retrieve UserContext from. - * @return The UserContext associated with the given session. - */ - public static UserContext getUserContext(HttpSession session) { - return (UserContext) session.getAttribute(CONTEXT_ATTRIBUTE); - } - - /** - * Returns the ClipboardState associated with the given session. If none - * exists yet, one is created. - * - * @param session The session to retrieve the ClipboardState from. - * @return The ClipboardState associated with the given session. - */ - public static ClipboardState getClipboardState(HttpSession session) { - - ClipboardState clipboard = (ClipboardState) session.getAttribute(CLIPBOARD_ATTRIBUTE); - if (clipboard == null) { - clipboard = new ClipboardState(); - session.setAttribute(CLIPBOARD_ATTRIBUTE, clipboard); - } - - return clipboard; - - } - - /** - * Returns whether the request given has updated credentials. If this - * function returns false, the UserContext will not be updated. - * - * @param request The request to check for credentials. - * @return true if the request contains credentials, false otherwise. - */ - protected boolean hasNewCredentials(HttpServletRequest request) { - return true; - } - - /** - * Regular expression which matches any IPv4 address. - */ - private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; - - /** - * Regular expression which matches any IPv6 address. - */ - private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})"; - - /** - * Regular expression which matches any IP address, regardless of version. - */ - private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")"; - - /** - * Pattern which matches valid values of the de-facto standard - * "X-Forwarded-For" header. - */ - private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$"); - - /** - * Returns a formatted string containing an IP address, or list of IP - * addresses, which represent the HTTP client and any involved proxies. As - * the headers used to determine proxies can easily be forged, this data is - * superficially validated to ensure that it at least looks like a list of - * IPs. - * - * @param request The HTTP request to format. - * @return A formatted string containing one or more IP addresses. - */ - private String getLoggableAddress(HttpServletRequest request) { - - // Log X-Forwarded-For, if present and valid - String header = request.getHeader("X-Forwarded-For"); - if (header != null && X_FORWARDED_FOR.matcher(header).matches()) - return "[" + header + ", " + request.getRemoteAddr() + "]"; - - // If header absent or invalid, just use source IP - return request.getRemoteAddr(); - - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) resp; - - // Set character encoding to UTF-8 if it's not already set - if(request.getCharacterEncoding() == null) { - try { - request.setCharacterEncoding("UTF-8"); - } catch (UnsupportedEncodingException exception) { - throw new ServletException(exception); - } - } - - try { - - // Obtain context from session - HttpSession httpSession = request.getSession(true); - UserContext context = getUserContext(httpSession); - - // If new credentials present, update/create context - if (hasNewCredentials(request)) { - - // Retrieve username and password from parms - String username = request.getParameter("username"); - String password = request.getParameter("password"); - - // If no username/password given, try Authorization header - if (useHttpAuthentication && username == null && password == null) { - - String authorization = request.getHeader("Authorization"); - if (authorization != null && authorization.startsWith("Basic ")) { - - // Decode base64 authorization - String basicBase64 = authorization.substring(6); - String basicCredentials = new String(DatatypeConverter.parseBase64Binary(basicBase64), "UTF-8"); - - // Pull username/password from auth data - int colon = basicCredentials.indexOf(':'); - if (colon != -1) { - username = basicCredentials.substring(0, colon); - password = basicCredentials.substring(colon+1); - } - - else - logger.info("Invalid HTTP Basic \"Authorization\" header received."); - - } - - } // end Authorization header fallback - - // Build credentials object - Credentials credentials = new Credentials(); - credentials.setSession(httpSession); - credentials.setRequest(request); - credentials.setUsername(username); - credentials.setPassword(password); - - SessionListenerCollection listeners = new SessionListenerCollection(httpSession); - - // If no cached context, attempt to get new context - if (context == null) { - - context = authProvider.getUserContext(credentials); - - // Log successful authentication - if (context != null && logger.isInfoEnabled()) - logger.info("User \"{}\" successfully authenticated from {}.", - context.self().getUsername(), getLoggableAddress(request)); - - } - - // Otherwise, update existing context - else - context = authProvider.updateUserContext(context, credentials); - - // If auth failed, notify listeners - if (context == null) { - - if (logger.isWarnEnabled()) { - - // Only bother logging failures involving usernames - if (credentials.getUsername() != null) - logger.info("Authentication attempt from {} for user \"{}\" failed.", - getLoggableAddress(request), credentials.getUsername()); - else - logger.debug("Authentication attempt from {} without username failed.", - getLoggableAddress(request)); - } - - notifyFailed(listeners, credentials); - } - - // If auth succeeded, notify and check with listeners - else if (!notifySuccess(listeners, context, credentials)) - logger.info("Successful authentication canceled by hook."); - - // If auth still OK, associate context with session - else { - httpSession.setAttribute(CONTEXT_ATTRIBUTE, context); - httpSession.setAttribute(CREDENTIALS_ATTRIBUTE, credentials); - } - - } // end if credentials present - - // Allow servlet to run now that authentication has been validated - chain.doFilter(request, response); - - } - - // Catch any thrown guacamole exception and attempt to pass within the - // HTTP response, logging each error appropriately. - catch (GuacamoleClientException e) { - logger.info("HTTP request rejected: {}", e.getMessage()); - logger.debug("HTTP request rejected by AuthenticatingFilter.", e); - sendError(response, e.getStatus(), e.getMessage()); - } - catch (GuacamoleException e) { - logger.error("Authentication failed internally: {}", e.getMessage()); - logger.debug("Internal server error during authentication.", e); - sendError(response, e.getStatus(), "Internal server error."); - } - - } - - @Override - public void destroy() { - // No destruction needed - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicGuacamoleTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicGuacamoleTunnelServlet.java index f6da591d2..be6e328f6 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicGuacamoleTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicGuacamoleTunnelServlet.java @@ -22,6 +22,8 @@ package org.glyptodon.guacamole.net.basic; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; @@ -35,8 +37,15 @@ import org.slf4j.LoggerFactory; * * @author Michael Jumper */ +@Singleton public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet { + /** + * Service for handling tunnel requests. + */ + @Inject + private TunnelRequestService tunnelRequestService; + /** * Logger for this class. */ @@ -46,8 +55,8 @@ public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet { protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { // Attempt to create HTTP tunnel - GuacamoleTunnel tunnel = BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); - + GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request)); + // If successful, warn of lack of WebSocket logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal."); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogout.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java similarity index 59% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogout.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java index eb40f0c84..d7ba6b499 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogout.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,33 +22,31 @@ package org.glyptodon.guacamole.net.basic; -import java.io.IOException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.servlet.GuiceServletContextListener; +import org.glyptodon.guacamole.net.basic.log.LogModule; +import org.glyptodon.guacamole.net.basic.rest.RESTModule; +import org.glyptodon.guacamole.net.basic.rest.RESTServletModule; /** - * Logs out the current user by invalidating the associated HttpSession and - * redirecting the user to the login page. - * - * @author Michael Jumper + * A ServletContextListener to listen for initialization of the servlet context + * in order to set up dependency injection. + * + * @author James Muehlner */ -public class BasicLogout extends HttpServlet { +public class BasicServletContextListener extends GuiceServletContextListener { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws IOException { - - // Invalidate session, if any - HttpSession httpSession = request.getSession(false); - if (httpSession != null) - httpSession.invalidate(); - - // Redirect to index - response.sendRedirect("index.xhtml"); + protected Injector getInjector() { + return Guice.createInjector( + new LogModule(), + new RESTServletModule(), + new RESTModule(), + new TunnelModule() + ); + } - + } - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java new file mode 100644 index 000000000..912ad8146 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Credentials; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; +import org.glyptodon.guacamole.properties.GuacamoleProperties; + +/** + * Contains Guacamole-specific user information which is tied to the current + * session, such as the UserContext and current clipboard state. + * + * @author Michael Jumper + */ +public class GuacamoleSession { + + /** + * The credentials provided when the user logged in. + */ + private final Credentials credentials; + + /** + * The user context associated with this session. + */ + private final UserContext userContext; + + /** + * Collection of all event listeners configured in guacamole.properties. + */ + private final Collection listeners = new ArrayList(); + + /** + * The current clipboard state. + */ + private final ClipboardState clipboardState = new ClipboardState(); + + /** + * Creates a new Guacamole session associated with the given user context. + * + * @param credentials The credentials provided by the user during login. + * @param userContext The user context to associate this session with. + * @throws GuacamoleException If an error prevents the session from being + * created. + */ + public GuacamoleSession(Credentials credentials, UserContext userContext) throws GuacamoleException { + + this.credentials = credentials; + this.userContext = userContext; + + // Load listeners from guacamole.properties + try { + + // Get all listener classes from properties + Collection listenerClasses = + GuacamoleProperties.getProperty(BasicGuacamoleProperties.EVENT_LISTENERS); + + // Add an instance of each class to the list + if (listenerClasses != null) { + for (Class listenerClass : listenerClasses) { + + // Instantiate listener + Object listener = listenerClass.getConstructor().newInstance(); + + // Add listener to collection of listeners + listeners.add(listener); + + } + } + + } + catch (InstantiationException e) { + throw new GuacamoleException("Listener class is abstract.", e); + } + catch (IllegalAccessException e) { + throw new GuacamoleException("No access to listener constructor.", e); + } + catch (IllegalArgumentException e) { + // This should not happen, given there ARE no arguments + throw new GuacamoleException("Illegal arguments to listener constructor.", e); + } + catch (InvocationTargetException e) { + throw new GuacamoleException("Error while instantiating listener.", e); + } + catch (NoSuchMethodException e) { + throw new GuacamoleException("Listener has no default constructor.", e); + } + catch (SecurityException e) { + throw new GuacamoleException("Security restrictions prevent instantiation of listener.", e); + } + + } + + /** + * Returns the credentials used when the user associated with this session + * logged in. + * + * @return The credentials used when the user associated with this session + * logged in. + */ + public Credentials getCredentials() { + return credentials; + } + + /** + * Returns the UserContext associated with this session. + * + * @return The UserContext associated with this session. + */ + public UserContext getUserContext() { + return userContext; + } + + /** + * Returns the ClipboardState associated with this session. + * + * @return The ClipboardState associated with this session. + */ + public ClipboardState getClipboardState() { + return clipboardState; + } + + /** + * Returns a collection which iterates over instances of all listeners + * defined in guacamole.properties. For each listener defined in + * guacamole.properties, a new instance is created and stored in this + * collection. The contents of this collection is stored within the + * HttpSession, and will be reused if available. Each listener is + * instantiated once per session. Listeners are singleton classes within the + * session, but not globally. + * + * @return A collection which iterates over instances of all listeners + * defined in guacamole.properties. + */ + public Collection getListeners() { + return Collections.unmodifiableCollection(listeners); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java deleted file mode 100644 index 7fbb9c9e7..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic; - -import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.protocol.GuacamoleStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filter which enforces authentication. If no user context is associated with - * the current HTTP session, or no HTTP session exists, the request is denied. - * - * @author Michael Jumper - */ -public class RestrictedFilter implements Filter { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(RestrictedFilter.class); - - @Override - public void init(FilterConfig config) throws ServletException { - // No configuration - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) resp; - - // Pull user context from session - UserContext context = null; - HttpSession session = request.getSession(false); - if (session != null) - context = AuthenticatingFilter.getUserContext(session); - - // If authenticated, proceed with rest of chain - if (context != null) - chain.doFilter(req, resp); - - // Otherwise, deny entire request - else { - final GuacamoleStatus status = GuacamoleStatus.CLIENT_UNAUTHORIZED; - final String message = "Not authenticated"; - - logger.info("HTTP request rejected: {}", message); - response.addHeader("Guacamole-Status-Code", Integer.toString(status.getGuacamoleStatusCode())); - response.addHeader("Guacamole-Error-Message", message); - response.sendError(status.getHttpStatusCode()); - } - - } - - @Override - public void destroy() { - // No destruction needed - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedHttpServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedHttpServlet.java deleted file mode 100644 index ea6f1dc32..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedHttpServlet.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.glyptodon.guacamole.GuacamoleClientException; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleUnauthorizedException; -import org.glyptodon.guacamole.GuacamoleUnsupportedException; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.protocol.GuacamoleStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Abstract servlet which provides an restrictedService() function that is only - * called if the current HTTP session has already been authenticated by the - * AuthenticatingFilter. - * - * @author Michael Jumper - */ -public abstract class RestrictedHttpServlet extends HttpServlet { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(RestrictedHttpServlet.class); - - /** - * Sends an error on the given HTTP response using the information within - * the given GuacamoleStatus. - * - * @param response The HTTP response to use to send the error. - * @param guac_status The status to send - * @param message A human-readable message that can be presented to the - * user. - * @throws ServletException If an error prevents sending of the error - * code. - */ - public static void sendError(HttpServletResponse response, - GuacamoleStatus guac_status, String message) - throws ServletException { - - try { - - // If response not committed, send error code and message - if (!response.isCommitted()) { - response.addHeader("Guacamole-Status-Code", Integer.toString(guac_status.getGuacamoleStatusCode())); - response.addHeader("Guacamole-Error-Message", message); - response.sendError(guac_status.getHttpStatusCode()); - } - - } - catch (IOException ioe) { - - // If unable to send error at all due to I/O problems, - // rethrow as servlet exception - throw new ServletException(ioe); - - } - - } - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - - // Set character encoding to UTF-8 if it's not already set - if(request.getCharacterEncoding() == null) { - try { - request.setCharacterEncoding("UTF-8"); - } catch (UnsupportedEncodingException exception) { - throw new ServletException(exception); - } - } - - try { - - // Obtain context from session - HttpSession httpSession = request.getSession(true); - UserContext context = AuthenticatingFilter.getUserContext(httpSession); - - // If no context, no authorizaton present - if (context == null) - throw new GuacamoleUnauthorizedException("Not authenticated"); - - // Allow servlet to run now that authentication has been validated - restrictedService(context, request, response); - - } - - // Catch any thrown guacamole exception and attempt to pass within the - // HTTP response, logging each error appropriately. - catch (GuacamoleClientException e) { - logger.debug("HTTP request rejected by RestrictedHttpServlet.", e); - sendError(response, e.getStatus(), e.getMessage()); - } - catch (GuacamoleUnsupportedException e) { - logger.debug("Unsupported operation.", e); - sendError(response, e.getStatus(), e.getMessage()); - } - catch (GuacamoleException e) { - logger.error("HTTP request failed: {}", e.getMessage()); - logger.debug("Internal server error while handling HTTP request to restricted resource.", e); - sendError(response, e.getStatus(), "Internal server error."); - } - - } - - /** - * Function called after the request and associated session are validated. - * If the current session is not associated with valid credentials, this - * function will not be called. - * - * @param context The current UserContext. - * @param request The HttpServletRequest being serviced. - * @param response An HttpServletResponse which controls the HTTP response - * of this servlet. - * - * @throws GuacamoleException If an error occurs that interferes with the - * normal operation of this servlet. - */ - protected abstract void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException; - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/SessionKeepAlive.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/SessionKeepAlive.java index 94cc94060..b065e4730 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/SessionKeepAlive.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/SessionKeepAlive.java @@ -22,29 +22,43 @@ package org.glyptodon.guacamole.net.basic; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.google.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Dummy servlet which provides an endpoint for arbitrary requests intended to - * simply keep the HTTP session from expiring. + * REST service which updates the last access time of the Guacamole session, + * preventing the session from becoming invalid. * * @author Michael Jumper */ -public class SessionKeepAlive extends RestrictedHttpServlet { +@Path("/keep-alive") +public class SessionKeepAlive { + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; /** * Logger for this class. */ private final Logger logger = LoggerFactory.getLogger(SessionKeepAlive.class); - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) { + @GET + @AuthProviderRESTExposure + public void updateSession(@QueryParam("token") String authToken) throws GuacamoleException { + + // Tickle the session + UserContext context = authenticationService.getUserContext(authToken); // Do nothing logger.debug("Keep-alive signal received from user \"{}\".", context.self().getUsername()); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java similarity index 58% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java index 34ca01f09..4466aecbf 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java @@ -20,36 +20,31 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.websocket; +package org.glyptodon.guacamole.net.basic; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; +import com.google.inject.Provider; +import com.google.inject.servlet.ServletModule; +import java.util.Arrays; +import javax.servlet.http.HttpServlet; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; +import org.glyptodon.guacamole.net.basic.websocket.BasicGuacamoleWebSocketTunnelEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Simple ServletContextListener which loads a WebSocket tunnel implementation - * if available, using the Servlet 3.0 API to dynamically load and install - * the tunnel servlet. - * - * Note that because Guacamole depends on the Servlet 2.5 API, and 3.0 may - * not be available or needed if WebSocket is not desired, the 3.0 API is - * detected and invoked dynamically via reflection. + * Module which loads tunnel implementations. * * @author Michael Jumper */ -public class WebSocketSupportLoader implements ServletContextListener { +public class TunnelModule extends ServletModule { /** * Logger for this class. */ - private final Logger logger = LoggerFactory.getLogger(WebSocketSupportLoader.class); + private final Logger logger = LoggerFactory.getLogger(TunnelModule.class); /** * Classnames of all legacy (non-JSR) WebSocket tunnel implementations. @@ -94,52 +89,17 @@ public class WebSocketSupportLoader implements ServletContextListener { } - private boolean loadWebSocketTunnel(ServletContext context, String classname) { + private boolean loadWebSocketTunnel(String classname) { try { // Attempt to find WebSocket servlet - Class servlet = (Class) + Class servlet = (Class) GuacamoleClassLoader.getInstance().findClass(classname); - // Dynamically add servlet IF SERVLET 3.0 API AVAILABLE! - try { - - // Get servlet registration class - Class regClass = Class.forName("javax.servlet.ServletRegistration"); - - // Get and invoke addServlet() - Method addServlet = ServletContext.class.getMethod("addServlet", - String.class, Class.class); - Object reg = addServlet.invoke(context, "WebSocketTunnel", servlet); - - // Get and invoke addMapping() - Method addMapping = regClass.getMethod("addMapping", String[].class); - addMapping.invoke(reg, (Object) new String[]{"/websocket-tunnel"}); - - // If we succesfully load and register the WebSocket tunnel servlet, - // WebSocket is supported. - return true; - - } - - // Servlet API 3.0 unsupported - catch (ClassNotFoundException e) { - logger.info("Servlet API 3.0 not found.", e); - } - catch (NoSuchMethodException e) { - logger.warn("Servlet API 3.0 found, but incomplete.", e); - } - - // Servlet API 3.0 found, but errors during use - catch (IllegalAccessException e) { - logger.error("Unable to load WebSocket tunnel servlet: {}", e.getMessage()); - logger.debug("Error loading WebSocket tunnel servlet.", e); - } - catch (InvocationTargetException e) { - logger.error("Unable to load WebSocket tunnel servlet: {}", e.getMessage()); - logger.debug("Error loading WebSocket tunnel servlet.", e); - } + // Add WebSocket servlet + serve("/websocket-tunnel").with(servlet); + return true; } @@ -160,21 +120,50 @@ public class WebSocketSupportLoader implements ServletContextListener { } @Override - public void contextDestroyed(ServletContextEvent sce) { - } + protected void configureServlets() { - @Override - public void contextInitialized(ServletContextEvent sce) { + bind(TunnelRequestService.class); + + // Set up HTTP tunnel + serve("/tunnel").with(BasicGuacamoleTunnelServlet.class); // Check for JSR 356 support if (implementsJSR_356()) { + logger.info("JSR-356 WebSocket support present."); + + // Get container + ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer"); + if (container == null) { + logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support."); + return; + } + + Provider tunnelRequestServiceProvider = getProvider(TunnelRequestService.class); + + // Build configuration for WebSocket tunnel + ServerEndpointConfig config = + ServerEndpointConfig.Builder.create(BasicGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel") + .configurator(new BasicGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider)) + .subprotocols(Arrays.asList(new String[]{"guacamole"})) + .build(); + + try { + + // Add configuration to container + container.addEndpoint(config); + + } + catch (DeploymentException e) { + logger.error("Unable to deploy WebSocket tunnel.", e); + } + return; } // Try to load each WebSocket tunnel in sequence for (String classname : WEBSOCKET_CLASSES) { - if (loadWebSocketTunnel(sce.getServletContext(), classname)) { + if (loadWebSocketTunnel(classname)) { logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname); return; } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicTunnelRequestUtility.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java similarity index 90% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicTunnelRequestUtility.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java index 0fb90992b..bea981608 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicTunnelRequestUtility.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java @@ -22,6 +22,9 @@ package org.glyptodon.guacamole.net.basic; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import java.util.Collection; import java.util.List; import javax.servlet.http.HttpSession; @@ -37,6 +40,7 @@ import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.event.SessionListenerCollection; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.event.TunnelCloseEvent; import org.glyptodon.guacamole.net.event.TunnelConnectEvent; import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener; @@ -55,12 +59,19 @@ import org.slf4j.LoggerFactory; * * @author Michael Jumper */ -public class BasicTunnelRequestUtility { +@Singleton +public class TunnelRequestService { /** * Logger for this class. */ - private static final Logger logger = LoggerFactory.getLogger(BasicTunnelRequestUtility.class); + private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class); + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; /** * Notifies all listeners in the given collection that a tunnel has been @@ -79,7 +90,7 @@ public class BasicTunnelRequestUtility { * error, the connect is canceled, and no other * listeners will run. */ - private static boolean notifyConnect(Collection listeners, UserContext context, + private boolean notifyConnect(Collection listeners, UserContext context, Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { @@ -119,7 +130,7 @@ public class BasicTunnelRequestUtility { * error, the close is canceled, and no other * listeners will run. */ - private static boolean notifyClose(Collection listeners, UserContext context, + private boolean notifyClose(Collection listeners, UserContext context, Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { @@ -151,7 +162,7 @@ public class BasicTunnelRequestUtility { * @return The created tunnel, or null if the tunnel could not be created. * @throws GuacamoleException If an error occurs while creating the tunnel. */ - public static GuacamoleTunnel createTunnel(TunnelRequest request) + public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException { HttpSession httpSession = request.getSession(); @@ -169,6 +180,10 @@ public class BasicTunnelRequestUtility { throw e; } + // Get auth token and session + String authToken = request.getParameter("authToken"); + GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); + // Get ID of connection String id = request.getParameter("id"); TunnelRequest.IdentifierType id_type = TunnelRequest.IdentifierType.getType(id); @@ -181,17 +196,17 @@ public class BasicTunnelRequestUtility { id = id.substring(id_type.PREFIX.length()); // Get credentials - final Credentials credentials = AuthenticatingFilter.getCredentials(httpSession); + final Credentials credentials = session.getCredentials(); // Get context - final UserContext context = AuthenticatingFilter.getUserContext(httpSession); - + final UserContext context = session.getUserContext(); + // If no context or no credentials, not logged in if (context == null || credentials == null) throw new GuacamoleSecurityException("Cannot connect - user not logged in."); // Get clipboard - final ClipboardState clipboard = AuthenticatingFilter.getClipboardState(httpSession); + final ClipboardState clipboard = session.getClipboardState(); // Get client information GuacamoleClientInformation info = new GuacamoleClientInformation(); @@ -279,7 +294,7 @@ public class BasicTunnelRequestUtility { // Monitor instructions which pertain to server-side events, if necessary try { - if (GuacamoleProperties.getProperty(CaptureClipboard.INTEGRATION_ENABLED, false)) + if (GuacamoleProperties.getProperty(ClipboardRESTService.INTEGRATION_ENABLED, false)) return new MonitoringGuacamoleReader(clipboard, super.acquireReader()); } catch (GuacamoleException e) { diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/ConnectionGroupUtility.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/ConnectionGroupUtility.java deleted file mode 100644 index 52cc35f91..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/ConnectionGroupUtility.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.ConnectionGroup; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.UserContext; - -/** - * A class that provides helper methods for the ConnectionGroup CRUD servlets. - * - * @author James Muehlner - */ -class ConnectionGroupUtility { - - // This class should not be instantiated - private ConnectionGroupUtility() {} - - /** - * Get the ConnectionGroupDirectory with the parent connection group - * specified by parentID. - * - * @param context The UserContext to search for the connectionGroup directory. - * @param parentID The ID of the parent connection group to search for. - * - * @return The ConnectionGroupDirectory with the parent connection group, - * if found. - * @throws GuacamoleException If an error is encountered while getting the - * connection group directory. - */ - static Directory findConnectionGroupDirectory( - UserContext context, String parentID) throws GuacamoleException { - - // Find the correct connection group directory - ConnectionGroup rootGroup = context.getRootConnectionGroup(); - Directory directory; - - Directory connectionGroupDirectory = - rootGroup.getConnectionGroupDirectory(); - - ConnectionGroup parentGroup = connectionGroupDirectory.get(parentID); - - if(parentGroup == null) - return null; - - directory = parentGroup.getConnectionGroupDirectory(); - - return directory; - } -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Create.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Create.java deleted file mode 100644 index 702cc851f..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Create.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles connection group creation. - * - * @author James Muehlner - */ -public class Create extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get name and type - String name = request.getParameter("name"); - String type = request.getParameter("type"); - - // Get the ID of the parent connection group - String parentID = request.getParameter("parentID"); - - // Find the correct connection group directory - Directory directory = - ConnectionGroupUtility.findConnectionGroupDirectory(context, parentID); - - if(directory == null) - throw new GuacamoleException("Connection group directory not found."); - - // Create connection skeleton - ConnectionGroup connectionGroup = new DummyConnectionGroup(); - connectionGroup.setName(name); - - if("balancing".equals(type)) - connectionGroup.setType(ConnectionGroup.Type.BALANCING); - else if("organizational".equals(type)) - connectionGroup.setType(ConnectionGroup.Type.ORGANIZATIONAL); - - // Add connection - directory.add(connectionGroup); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Delete.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Delete.java deleted file mode 100644 index 64de349d7..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Delete.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles connection group deletion. - * - * @author Michael Jumper - */ -public class Delete extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID - String identifier = request.getParameter("id"); - - // Attempt to get connection group directory - Directory directory = - context.getRootConnectionGroup().getConnectionGroupDirectory(); - - // Remove connection - directory.remove(identifier); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/DummyConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/DummyConnectionGroup.java deleted file mode 100644 index 5bfd8eafd..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/DummyConnectionGroup.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.GuacamoleSocket; -import org.glyptodon.guacamole.net.auth.AbstractConnectionGroup; -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; - -/** - * Basic ConnectionGroup skeleton, providing a means of storing Connection data - * prior to CRUD operations. This ConnectionGroup has no functionality for actually - * performing a connection operation, and does not promote any of the - * semantics that would otherwise be present because of the authentication - * provider. It is up to the authentication provider to create a new - * ConnectionGroup based on the information contained herein. - * - * @author James Muehlner - */ -public class DummyConnectionGroup extends AbstractConnectionGroup { - - @Override - public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { - throw new UnsupportedOperationException("Connection unsupported in DummyConnectionGroup."); - } - - @Override - public Directory getConnectionDirectory() throws GuacamoleException { - throw new UnsupportedOperationException("Connection directory unsupported in DummyConnectionGroup."); - } - - @Override - public Directory getConnectionGroupDirectory() throws GuacamoleException { - throw new UnsupportedOperationException("Connection group directory unsuppprted in DummyConnectionGroup."); - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/List.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/List.java deleted file mode 100644 index c91ba72e7..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/List.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import java.io.IOException; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleSecurityException; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.net.auth.ConnectionGroup; -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.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.basic.RestrictedHttpServlet; - -/** - * Simple HttpServlet which outputs XML containing a list of all authorized - * connection groups for the current user. - * - * @author Michael Jumper - */ -public class List extends RestrictedHttpServlet { - - /** - * System administration permission. - */ - private static final Permission SYSTEM_PERMISSION = - new SystemPermission(SystemPermission.Type.ADMINISTER); - - - /** - * Checks whether the given user has permission to perform the given - * object operation. Security exceptions are handled appropriately - only - * non-security exceptions pass through. - * - * @param user The user whose permissions should be verified. - * @param type The type of operation to check for permission for. - * @param identifier The identifier of the connection the operation - * would be performed upon. - * @return true if permission is granted, false otherwise. - * - * @throws GuacamoleException If an error occurs while checking permissions. - */ - private boolean hasConfigPermission(User user, ObjectPermission.Type type, - String identifier) - throws GuacamoleException { - - // Build permission - Permission permission = new ConnectionPermission( - type, - identifier - ); - - try { - // Return result of permission check, if possible - return user.hasPermission(permission); - } - catch (GuacamoleSecurityException e) { - // If cannot check due to security restrictions, no permission - return false; - } - - } - - /** - * Writes the XML for the given connection group. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param group The connection group whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnectionGroup(User self, XMLStreamWriter xml, - ConnectionGroup group) throws GuacamoleException, XMLStreamException { - - // Write group - xml.writeStartElement("group"); - xml.writeAttribute("id", group.getIdentifier()); - xml.writeAttribute("name", group.getName()); - - // Write group type - switch (group.getType()) { - - case ORGANIZATIONAL: - xml.writeAttribute("type", "organizational"); - break; - - case BALANCING: - xml.writeAttribute("type", "balancing"); - break; - - } - - // Write contained connection groups - writeConnectionGroups(self, xml, group.getConnectionGroupDirectory()); - - // End of group - xml.writeEndElement(); - - } - - /** - * Writes the XML for the given directory of connection groups. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param directory The directory whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnectionGroups(User self, XMLStreamWriter xml, - Directory directory) - throws GuacamoleException, XMLStreamException { - - // If no connections, write nothing - Set identifiers = directory.getIdentifiers(); - if (identifiers.isEmpty()) - return; - - // Begin connections - xml.writeStartElement("groups"); - - // For each entry, write corresponding connection element - for (String identifier : identifiers) { - - // Write each group - ConnectionGroup group = directory.get(identifier); - writeConnectionGroup(self, xml, group); - - } - - // End connections - xml.writeEndElement(); - - } - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Do not cache - response.setHeader("Cache-Control", "no-cache"); - - // Write XML content type - response.setHeader("Content-Type", "text/xml"); - - // Set encoding - response.setCharacterEncoding("UTF-8"); - - // Get root group - ConnectionGroup root = context.getRootConnectionGroup(); - - // Write actual XML - try { - - // Get self - User self = context.self(); - - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter()); - - // Write content of root group - xml.writeStartDocument(); - writeConnectionGroup(self, xml, root); - xml.writeEndDocument(); - - } - catch (XMLStreamException e) { - throw new GuacamoleServerException( - "Unable to write connection group list XML.", e); - } - catch (IOException e) { - throw new GuacamoleServerException( - "I/O error writing connection group list XML.", e); - } - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Move.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Move.java deleted file mode 100644 index 363f10d83..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Move.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles moving connection groups. - * - * @author James Muehlner - */ -public class Move extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID - String identifier = request.getParameter("id"); - - // Get the identifier of the new parent connection group - String parentID = request.getParameter("parentID"); - - // Attempt to get the new parent connection group directory - Directory newParentDirectory = - ConnectionGroupUtility.findConnectionGroupDirectory(context, parentID); - - // Attempt to get root connection group directory - Directory directory = - context.getRootConnectionGroup().getConnectionGroupDirectory(); - - // Move connection group - directory.move(identifier, newParentDirectory); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Update.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Update.java deleted file mode 100644 index 09931ef87..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/Update.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles connection group update. - * - * @author James Muehlner - */ -public class Update extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID, name, and type - String identifier = request.getParameter("id"); - String name = request.getParameter("name"); - String type = request.getParameter("type"); - - // Attempt to get connection group directory - Directory directory = - context.getRootConnectionGroup().getConnectionGroupDirectory(); - - // Create connection group skeleton - ConnectionGroup connectionGroup = directory.get(identifier); - connectionGroup.setName(name); - - if("balancing".equals(type)) - connectionGroup.setType(ConnectionGroup.Type.BALANCING); - else if("organizational".equals(type)) - connectionGroup.setType(ConnectionGroup.Type.ORGANIZATIONAL); - - // Update connection group - directory.update(connectionGroup); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/ConnectionUtility.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/ConnectionUtility.java deleted file mode 100644 index de4e23942..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/ConnectionUtility.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import org.glyptodon.guacamole.GuacamoleException; -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; - -/** - * A class that provides helper methods for the Connection CRUD servlets. - * - * @author James Muehlner - */ -class ConnectionUtility { - - // This class should not be instantiated - private ConnectionUtility() {} - - /** - * Get the ConnectionDirectory with the parent connection group specified by - * parentID. - * - * @param context The UserContext to search for the connection directory. - * @param parentID The ID of the parent connection group to search for. - * - * @return The ConnectionDirectory with the parent connection group, - * if found. - * @throws GuacamoleException If an error is encountered while getting the - * connection directory. - */ - static Directory findConnectionDirectory( - UserContext context, String parentID) throws GuacamoleException { - - // Find the correct connection directory - ConnectionGroup rootGroup = context.getRootConnectionGroup(); - Directory directory; - - Directory connectionGroupDirectory = - rootGroup.getConnectionGroupDirectory(); - - ConnectionGroup parentGroup = connectionGroupDirectory.get(parentID); - - if(parentGroup == null) - return null; - - directory = parentGroup.getConnectionDirectory(); - - return directory; - } -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Create.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Create.java deleted file mode 100644 index 5fa050b36..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Create.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import java.util.Enumeration; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; -import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; - -/** - * Simple HttpServlet which handles connection creation. - * - * @author Michael Jumper - */ -public class Create extends RestrictedHttpServlet { - - /** - * Prefix given to a parameter name when that parameter is a protocol- - * specific parameter meant for the configuration. - */ - public static final String PARAMETER_PREFIX = "_"; - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get name and protocol - String name = request.getParameter("name"); - String protocol = request.getParameter("protocol"); - - // Get the ID of the parent connection group - String parentID = request.getParameter("parentID"); - - // Find the correct connection directory - Directory directory = - ConnectionUtility.findConnectionDirectory(context, parentID); - - if(directory == null) - throw new GuacamoleException("Connection directory not found."); - - // Create config - GuacamoleConfiguration config = new GuacamoleConfiguration(); - config.setProtocol(protocol); - - // Load parameters into config - Enumeration params = request.getParameterNames(); - while (params.hasMoreElements()) { - - // If parameter starts with prefix, load corresponding parameter - // value into config - String param = params.nextElement(); - if (param.startsWith(PARAMETER_PREFIX)) - config.setParameter( - param.substring(PARAMETER_PREFIX.length()), - request.getParameter(param)); - - } - - // Create connection skeleton - Connection connection = new DummyConnection(); - connection.setName(name); - connection.setConfiguration(config); - - // Add connection - directory.add(connection); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/DummyConnection.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/DummyConnection.java deleted file mode 100644 index c1d7c173f..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/DummyConnection.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import java.util.List; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.GuacamoleSocket; -import org.glyptodon.guacamole.net.auth.AbstractConnection; -import org.glyptodon.guacamole.net.auth.ConnectionRecord; -import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; - -/** - * Basic Connection skeleton, providing a means of storing Connection data - * prior to CRUD operations. This Connection has no functionality for actually - * performing a connection operation, and does not promote any of the - * semantics that would otherwise be present because of the authentication - * provider. It is up to the authentication provider to create a new - * Connection based on the information contained herein. - * - * @author Michael Jumper - */ -public class DummyConnection extends AbstractConnection { - - @Override - public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { - throw new UnsupportedOperationException("Connection unsupported in DummyConnection."); - } - - @Override - public List getHistory() throws GuacamoleException { - throw new UnsupportedOperationException("History unsupported in DummyConnection."); - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/List.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/List.java deleted file mode 100644 index 73d353360..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/List.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import java.io.IOException; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleSecurityException; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.ConnectionGroup; -import org.glyptodon.guacamole.net.auth.ConnectionRecord; -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.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.basic.RestrictedHttpServlet; -import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; - -/** - * Simple HttpServlet which outputs XML containing a list of all authorized - * configurations for the current user. - * - * @author Michael Jumper - */ -public class List extends RestrictedHttpServlet { - - /** - * System administration permission. - */ - private static final Permission SYSTEM_PERMISSION = - new SystemPermission(SystemPermission.Type.ADMINISTER); - - - /** - * Checks whether the given user has permission to perform the given - * object operation. Security exceptions are handled appropriately - only - * non-security exceptions pass through. - * - * @param user The user whose permissions should be verified. - * @param type The type of operation to check for permission for. - * @param identifier The identifier of the connection the operation - * would be performed upon. - * @return true if permission is granted, false otherwise. - * - * @throws GuacamoleException If an error occurs while checking permissions. - */ - private boolean hasConfigPermission(User user, ObjectPermission.Type type, - String identifier) - throws GuacamoleException { - - // Build permission - Permission permission = new ConnectionPermission( - type, - identifier - ); - - try { - // Return result of permission check, if possible - return user.hasPermission(permission); - } - catch (GuacamoleSecurityException e) { - // If cannot check due to security restrictions, no permission - return false; - } - - } - - /** - * Writes the XML for the given connection group. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param group The connection group whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnectionGroup(User self, XMLStreamWriter xml, - ConnectionGroup group) throws GuacamoleException, XMLStreamException { - - // Write group - xml.writeStartElement("group"); - xml.writeAttribute("id", group.getIdentifier()); - xml.writeAttribute("name", group.getName()); - - // Write group type - switch (group.getType()) { - - case ORGANIZATIONAL: - xml.writeAttribute("type", "organizational"); - break; - - case BALANCING: - xml.writeAttribute("type", "balancing"); - break; - - } - - Permission group_admin_permission = new ConnectionGroupPermission( - ObjectPermission.Type.ADMINISTER, group.getIdentifier()); - - // Attempt to list contained groups and connections ONLY if the group - // is organizational or we have admin rights to it - if (group.getType() == ConnectionGroup.Type.ORGANIZATIONAL - || self.hasPermission(SYSTEM_PERMISSION) - || self.hasPermission(group_admin_permission)) { - writeConnections(self, xml, group.getConnectionDirectory()); - writeConnectionGroups(self, xml, group.getConnectionGroupDirectory()); - } - - // End of group - xml.writeEndElement(); - - } - - /** - * Writes the XML for the given connection. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param connection The connection whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnection(User self, XMLStreamWriter xml, - Connection connection) throws GuacamoleException, XMLStreamException { - - // Write connection - xml.writeStartElement("connection"); - xml.writeAttribute("id", connection.getIdentifier()); - xml.writeAttribute("name", connection.getName()); - xml.writeAttribute("protocol", - connection.getConfiguration().getProtocol()); - - // If update permission available, include parameters - if (self.hasPermission(SYSTEM_PERMISSION) || - hasConfigPermission(self, ObjectPermission.Type.UPDATE, - connection.getIdentifier())) { - - // As update permission is present, also list parameters - GuacamoleConfiguration config = connection.getConfiguration(); - for (String name : config.getParameterNames()) { - - String value = connection.getConfiguration().getParameter(name); - xml.writeStartElement("param"); - xml.writeAttribute("name", name); - - if (value != null) - xml.writeCharacters(value); - - xml.writeEndElement(); - } - - } - - // Write history - xml.writeStartElement("history"); - for (ConnectionRecord record : connection.getHistory()) { - xml.writeStartElement("record"); - - // Start date - xml.writeAttribute("start", - Long.toString(record.getStartDate().getTime())); - - // End date - if (record.getEndDate() != null) - xml.writeAttribute("end", - Long.toString(record.getEndDate().getTime())); - - // Whether connection currently active - if (record.isActive()) - xml.writeAttribute("active", "yes"); - - // User involved - xml.writeCharacters(record.getUsername()); - - xml.writeEndElement(); - } - xml.writeEndElement(); - - // End connection - xml.writeEndElement(); - - } - - /** - * Writes the XML for the given directory of connection groups. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param directory The directory whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnectionGroups(User self, XMLStreamWriter xml, - Directory directory) - throws GuacamoleException, XMLStreamException { - - // If no connections, write nothing - Set identifiers = directory.getIdentifiers(); - if (identifiers.isEmpty()) - return; - - // Begin connections - xml.writeStartElement("groups"); - - // For each entry, write corresponding connection element - for (String identifier : identifiers) { - - // Write each group - ConnectionGroup group = directory.get(identifier); - writeConnectionGroup(self, xml, group); - - } - - // End connections - xml.writeEndElement(); - - } - - /** - * Writes the XML for the given directory of connections. - * - * @param self The user whose permissions dictate the availability of the - * data written. - * @param xml The XMLStremWriter to use when writing the data. - * @param directory The directory whose XML representation will be - * written. - * @throws GuacamoleException If an error occurs while reading the - * requested data. - * @throws XMLStreamException If an error occurs while writing the XML. - */ - private void writeConnections(User self, XMLStreamWriter xml, - Directory directory) - throws GuacamoleException, XMLStreamException { - - // If no connections, write nothing - Set identifiers = directory.getIdentifiers(); - if (identifiers.isEmpty()) - return; - - // Begin connections - xml.writeStartElement("connections"); - - // For each entry, write corresponding connection element - for (String identifier : identifiers) { - - // Write each connection - Connection connection = directory.get(identifier); - writeConnection(self, xml, connection); - - } - - // End connections - xml.writeEndElement(); - - } - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Do not cache - response.setHeader("Cache-Control", "no-cache"); - - // Write XML content type - response.setHeader("Content-Type", "text/xml"); - - // Set encoding - response.setCharacterEncoding("UTF-8"); - - // Get root group - ConnectionGroup root = context.getRootConnectionGroup(); - - // Write actual XML - try { - - // Get self - User self = context.self(); - - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter()); - - // Write content of root group - xml.writeStartDocument(); - writeConnectionGroup(self, xml, root); - xml.writeEndDocument(); - - } - catch (XMLStreamException e) { - throw new GuacamoleServerException( - "Unable to write configuration list XML.", e); - } - catch (IOException e) { - throw new GuacamoleServerException( - "I/O error writing configuration list XML.", e); - } - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Move.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Move.java deleted file mode 100644 index e2fa7c555..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Move.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles moving connections. - * - * @author Michael Jumper - */ -public class Move extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID - String identifier = request.getParameter("id"); - - // Get the identifier of the new parent connection group - String parentID = request.getParameter("parentID"); - - // Attempt to get the new parent connection directory - Directory newParentDirectory = - ConnectionUtility.findConnectionDirectory(context, parentID); - - // Attempt to get root connection directory - Directory directory = - context.getRootConnectionGroup().getConnectionDirectory(); - - // Move connection - directory.move(identifier, newParentDirectory); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Update.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Update.java deleted file mode 100644 index 6b4c90d99..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Update.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.connections; - -import java.util.Enumeration; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; -import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; - -/** - * Simple HttpServlet which handles connection update. - * - * @author Michael Jumper - */ -public class Update extends RestrictedHttpServlet { - - /** - * Prefix given to a parameter name when that parameter is a protocol- - * specific parameter meant for the configuration. - */ - public static final String PARAMETER_PREFIX = "_"; - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID, name, and protocol - String identifier = request.getParameter("id"); - String name = request.getParameter("name"); - String protocol = request.getParameter("protocol"); - - // Attempt to get connection directory - Directory directory = - context.getRootConnectionGroup().getConnectionDirectory(); - - // Create config - GuacamoleConfiguration config = new GuacamoleConfiguration(); - config.setProtocol(protocol); - - // Load parameters into config - Enumeration params = request.getParameterNames(); - while (params.hasMoreElements()) { - - // If parameter starts with prefix, load corresponding parameter - // value into config - String param = params.nextElement(); - if (param.startsWith(PARAMETER_PREFIX)) - config.setParameter( - param.substring(PARAMETER_PREFIX.length()), - request.getParameter(param)); - - } - - // Create connection skeleton - Connection connection = directory.get(identifier); - connection.setName(name); - connection.setConfiguration(config); - - // Update connection - directory.update(connection); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/permissions/List.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/permissions/List.java deleted file mode 100644 index df0c197fb..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/permissions/List.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.permissions; - -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.glyptodon.guacamole.GuacamoleClientException; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleSecurityException; -import org.glyptodon.guacamole.GuacamoleServerException; -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.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; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; - -/** - * Simple HttpServlet which outputs XML containing a list of all visible - * permissions of a given user. - * - * @author Michael Jumper - */ -public class List extends RestrictedHttpServlet { - - /** - * Returns the XML attribute value representation of the given - * SystemPermission.Type. - * - * @param type The SystemPermission.Type to translate into a String. - * @return The XML attribute value representation of the given - * SystemPermission.Type. - * - * @throws GuacamoleException If the type given is not implemented. - */ - private String toString(SystemPermission.Type type) - throws GuacamoleException { - - switch (type) { - case CREATE_USER: return "create-user"; - case CREATE_CONNECTION: return "create-connection"; - case CREATE_CONNECTION_GROUP: return "create-connection-group"; - case ADMINISTER: return "admin"; - } - - throw new GuacamoleException("Unknown permission type: " + type); - - } - - /** - * Returns the XML attribute value representation of the given - * ObjectPermission.Type. - * - * @param type The ObjectPermission.Type to translate into a String. - * @return The XML attribute value representation of the given - * ObjectPermission.Type. - * - * @throws GuacamoleException If the type given is not implemented. - */ - private String toString(ObjectPermission.Type type) - throws GuacamoleException { - - switch (type) { - case READ: return "read"; - case UPDATE: return "update"; - case DELETE: return "delete"; - case ADMINISTER: return "admin"; - } - - throw new GuacamoleException("Unknown permission type: " + type); - - } - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Do not cache - response.setHeader("Cache-Control", "no-cache"); - - // Set encoding - response.setCharacterEncoding("UTF-8"); - - // Write actual XML - try { - - User user; - - // Get username - String username = request.getParameter("user"); - if (username != null) { - - // Get user directory - Directory users = context.getUserDirectory(); - - // Get specific user - user = users.get(username); - } - else - user = context.self(); - - if (user == null) - throw new GuacamoleSecurityException("No such user."); - - // Write XML content type - response.setHeader("Content-Type", "text/xml"); - - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter()); - - // Begin document - xml.writeStartDocument(); - xml.writeStartElement("permissions"); - xml.writeAttribute("user", user.getUsername()); - - // For each entry, write corresponding user element - for (Permission permission : user.getPermissions()) { - - // System permission - if (permission instanceof SystemPermission) { - - // Get permission - SystemPermission sp = (SystemPermission) permission; - - // Write permission - xml.writeEmptyElement("system"); - xml.writeAttribute("type", toString(sp.getType())); - - } - - // Config permission - else if (permission instanceof ConnectionPermission) { - - // Get permission - ConnectionPermission cp = - (ConnectionPermission) permission; - - // Write permission - xml.writeEmptyElement("connection"); - xml.writeAttribute("type", toString(cp.getType())); - xml.writeAttribute("name", cp.getObjectIdentifier()); - - } - - // Connection group permission - else if (permission instanceof ConnectionGroupPermission) { - - // Get permission - ConnectionGroupPermission cgp = - (ConnectionGroupPermission) permission; - - // Write permission - xml.writeEmptyElement("connection-group"); - xml.writeAttribute("type", toString(cgp.getType())); - xml.writeAttribute("name", cgp.getObjectIdentifier()); - - } - - // User permission - else if (permission instanceof UserPermission) { - - // Get permission - UserPermission up = (UserPermission) permission; - - // Write permission - xml.writeEmptyElement("user"); - xml.writeAttribute("type", toString(up.getType())); - xml.writeAttribute("name", up.getObjectIdentifier()); - - } - - else - throw new GuacamoleClientException( - "Unsupported permission type."); - - } - - // End document - xml.writeEndElement(); - xml.writeEndDocument(); - - } - catch (XMLStreamException e) { - throw new GuacamoleServerException( - "Unable to write permission list XML.", e); - } - catch (IOException e) { - throw new GuacamoleServerException( - "I/O error writing permission list XML.", e); - } - - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Create.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Create.java deleted file mode 100644 index 06bebc693..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Create.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.users; - -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles user creation. - * - * @author Michael Jumper - */ -public class Create extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Create user as specified - String username = request.getParameter("name"); - - // Attempt to get user directory - Directory directory = - context.getUserDirectory(); - - // Create user skeleton - User user = new DummyUser(); - user.setUsername(username); - user.setPassword(UUID.randomUUID().toString()); - - // Add user - directory.add(user); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/DummyUser.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/DummyUser.java deleted file mode 100644 index 15cd9c356..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/DummyUser.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.users; - -import java.util.HashSet; -import java.util.Set; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.AbstractUser; -import org.glyptodon.guacamole.net.auth.permission.Permission; - -/** - * Basic User skeleton, providing a means of storing User data prior to CRUD - * operations. This User does not promote any of the semantics that would - * otherwise be present because of the authentication provider. It is up to the - * authentication provider to create a new User based on the information - * contained herein. - * - * @author Michael Jumper - */ -public class DummyUser extends AbstractUser { - - /** - * Set of all available permissions. - */ - private Set permissions = new HashSet(); - - @Override - public Set getPermissions() throws GuacamoleException { - return permissions; - } - - @Override - public boolean hasPermission(Permission permission) throws GuacamoleException { - return permissions.contains(permission); - } - - @Override - public void addPermission(Permission permission) throws GuacamoleException { - permissions.add(permission); - } - - @Override - public void removePermission(Permission permission) throws GuacamoleException { - permissions.remove(permission); - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/List.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/List.java deleted file mode 100644 index a62f8b6b1..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/List.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.users; - -import java.io.IOException; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleServerException; -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.RestrictedHttpServlet; - -/** - * Simple HttpServlet which outputs XML containing a list of all visible users. - * - * @author Michael Jumper - */ -public class List extends RestrictedHttpServlet { - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Do not cache - response.setHeader("Cache-Control", "no-cache"); - - // Write XML content type - response.setHeader("Content-Type", "text/xml"); - - // Set encoding - response.setCharacterEncoding("UTF-8"); - - // Write actual XML - try { - - // Get user directory - Directory directory = context.getUserDirectory(); - - // Get users - Set users = directory.getIdentifiers(); - - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter()); - - // Begin document - xml.writeStartDocument(); - xml.writeStartElement("users"); - - // For each entry, write corresponding user element - for (String username : users) { - - // Get user - User user = directory.get(username); - - // Write user - xml.writeEmptyElement("user"); - xml.writeAttribute("name", user.getUsername()); - - } - - // End document - xml.writeEndElement(); - xml.writeEndDocument(); - - } - catch (XMLStreamException e) { - throw new GuacamoleServerException( - "Unable to write configuration list XML.", e); - } - catch (IOException e) { - throw new GuacamoleServerException( - "I/O error writing configuration list XML.", e); - } - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Update.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Update.java deleted file mode 100644 index 7487357ec..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Update.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.crud.users; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.GuacamoleClientException; -import org.glyptodon.guacamole.GuacamoleException; -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.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; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; - -/** - * Simple HttpServlet which handles user update. - * - * @author Michael Jumper - */ -public class Update extends RestrictedHttpServlet { - - /** - * String given for user creation permission. - */ - private static final String CREATE_USER_PERMISSION = "create-user"; - - /** - * String given for connection creation permission. - */ - private static final String CREATE_CONNECTION_PERMISSION = "create-connection"; - - /** - * String given for connection group creation permission. - */ - private static final String CREATE_CONNECTION_GROUP_PERMISSION = "create-connection-group"; - - /** - * String given for system administration permission. - */ - private static final String ADMIN_PERMISSION = "admin"; - - /** - * Prefix given before an object identifier for read permission. - */ - private static final String READ_PREFIX = "read:"; - - /** - * Prefix given before an object identifier for delete permission. - */ - private static final String DELETE_PREFIX = "delete:"; - - /** - * Prefix given before an object identifier for update permission. - */ - private static final String UPDATE_PREFIX = "update:"; - - /** - * Prefix given before an object identifier for administration permission. - */ - private static final String ADMIN_PREFIX = "admin:"; - - /** - * Given a permission string, returns the corresponding system permission. - * - * @param str The permission string to parse. - * @return The parsed system permission. - * @throws GuacamoleException If the given string could not be parsed. - */ - private Permission parseSystemPermission(String str) - throws GuacamoleException { - - // Create user - if (str.equals(CREATE_USER_PERMISSION)) - return new SystemPermission(SystemPermission.Type.CREATE_USER); - - // Create connection - if (str.equals(CREATE_CONNECTION_PERMISSION)) - return new SystemPermission(SystemPermission.Type.CREATE_CONNECTION); - - // Create connection group - if (str.equals(CREATE_CONNECTION_GROUP_PERMISSION)) - return new SystemPermission(SystemPermission.Type.CREATE_CONNECTION_GROUP); - - // Administration - if (str.equals(ADMIN_PERMISSION)) - return new SystemPermission(SystemPermission.Type.ADMINISTER); - - throw new GuacamoleException("Invalid permission string."); - - } - - /** - * Given a permission string, returns the corresponding user permission. - * - * @param str The permission string to parse. - * @return The parsed user permission. - * @throws GuacamoleException If the given string could not be parsed. - */ - private Permission parseUserPermission(String str) - throws GuacamoleException { - - // Read - if (str.startsWith(READ_PREFIX)) - return new UserPermission(ObjectPermission.Type.READ, - str.substring(READ_PREFIX.length())); - - // Update - if (str.startsWith(UPDATE_PREFIX)) - return new UserPermission(ObjectPermission.Type.UPDATE, - str.substring(UPDATE_PREFIX.length())); - - // Delete - if (str.startsWith(DELETE_PREFIX)) - return new UserPermission(ObjectPermission.Type.DELETE, - str.substring(DELETE_PREFIX.length())); - - // Administration - if (str.startsWith(ADMIN_PREFIX)) - return new UserPermission(ObjectPermission.Type.ADMINISTER, - str.substring(ADMIN_PREFIX.length())); - - throw new GuacamoleException("Invalid permission string."); - - } - - /** - * Given a permission string, returns the corresponding connection - * permission. - * - * @param str The permission string to parse. - * @return The parsed connection permission. - * @throws GuacamoleException If the given string could not be parsed. - */ - private Permission parseConnectionPermission(String str) - throws GuacamoleException { - - // Read - if (str.startsWith(READ_PREFIX)) - return new ConnectionPermission(ObjectPermission.Type.READ, - str.substring(READ_PREFIX.length())); - - // Update - if (str.startsWith(UPDATE_PREFIX)) - return new ConnectionPermission(ObjectPermission.Type.UPDATE, - str.substring(UPDATE_PREFIX.length())); - - // Delete - if (str.startsWith(DELETE_PREFIX)) - return new ConnectionPermission(ObjectPermission.Type.DELETE, - str.substring(DELETE_PREFIX.length())); - - // Administration - if (str.startsWith(ADMIN_PREFIX)) - return new ConnectionPermission(ObjectPermission.Type.ADMINISTER, - str.substring(ADMIN_PREFIX.length())); - - throw new GuacamoleClientException("Invalid permission string."); - - } - - /** - * Given a permission string, returns the corresponding connection group - * permission. - * - * @param str The permission string to parse. - * @return The parsed connection group permission. - * @throws GuacamoleException If the given string could not be parsed. - */ - private Permission parseConnectionGroupPermission(String str) - throws GuacamoleException { - - // Read - if (str.startsWith(READ_PREFIX)) - return new ConnectionGroupPermission(ObjectPermission.Type.READ, - str.substring(READ_PREFIX.length())); - - // Update - if (str.startsWith(UPDATE_PREFIX)) - return new ConnectionGroupPermission(ObjectPermission.Type.UPDATE, - str.substring(UPDATE_PREFIX.length())); - - // Delete - if (str.startsWith(DELETE_PREFIX)) - return new ConnectionGroupPermission(ObjectPermission.Type.DELETE, - str.substring(DELETE_PREFIX.length())); - - // Administration - if (str.startsWith(ADMIN_PREFIX)) - return new ConnectionGroupPermission(ObjectPermission.Type.ADMINISTER, - str.substring(ADMIN_PREFIX.length())); - - throw new GuacamoleClientException("Invalid permission string."); - - } - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Create user as specified - String username = request.getParameter("name"); - String password = request.getParameter("password"); - - // Attempt to get user directory - Directory directory = - context.getUserDirectory(); - - // Get user data, setting password if given - User user = directory.get(username); - user.setUsername(username); - if (password != null) - user.setPassword(password); - - /* - * NEW PERMISSIONS - */ - - // Set added system permissions - String[] add_sys_permission = request.getParameterValues("+sys"); - if (add_sys_permission != null) { - for (String str : add_sys_permission) - user.addPermission(parseSystemPermission(str)); - } - - // Set added user permissions - String[] add_user_permission = request.getParameterValues("+user"); - if (add_user_permission != null) { - for (String str : add_user_permission) - user.addPermission(parseUserPermission(str)); - } - - // Set added connection permissions - String[] add_connection_permission = request.getParameterValues("+connection"); - if (add_connection_permission != null) { - for (String str : add_connection_permission) - user.addPermission(parseConnectionPermission(str)); - } - - // Set added connection group permissions - String[] add_connection_group_permission = request.getParameterValues("+connection-group"); - if (add_connection_group_permission != null) { - for (String str : add_connection_group_permission) - user.addPermission(parseConnectionGroupPermission(str)); - } - - /* - * REMOVED PERMISSIONS - */ - - // Unset removed system permissions - String[] remove_sys_permission = request.getParameterValues("-sys"); - if (remove_sys_permission != null) { - for (String str : remove_sys_permission) - user.removePermission(parseSystemPermission(str)); - } - - // Unset removed user permissions - String[] remove_user_permission = request.getParameterValues("-user"); - if (remove_user_permission != null) { - for (String str : remove_user_permission) - user.removePermission(parseUserPermission(str)); - } - - // Unset removed connection permissions - String[] remove_connection_permission = request.getParameterValues("-connection"); - if (remove_connection_permission != null) { - for (String str : remove_connection_permission) - user.removePermission(parseConnectionPermission(str)); - } - - // Unset removed connection group permissions - String[] remove_connection_group_permission = request.getParameterValues("-connection-group"); - if (remove_connection_group_permission != null) { - for (String str : remove_connection_group_permission) - user.removePermission(parseConnectionGroupPermission(str)); - } - - // Update user - directory.update(user); - - } - -} - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogbackInitializer.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogModule.java similarity index 85% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogbackInitializer.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogModule.java index 3ea4ed6d8..a5ba1a039 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogbackInitializer.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/log/LogModule.java @@ -26,32 +26,26 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; +import com.google.inject.AbstractModule; import java.io.File; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; import org.glyptodon.guacamole.properties.GuacamoleHome; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Initializes the logback logging subsystem, used behind SLF4J within - * Guacamole for all logging. + * Initializes the logging subsystem. * * @author Michael Jumper */ -public class LogbackInitializer implements ServletContextListener { +public class LogModule extends AbstractModule { /** * Logger for this class. */ - private final Logger logger = LoggerFactory.getLogger(LogbackInitializer.class); + private final Logger logger = LoggerFactory.getLogger(LogModule.class); @Override - public void contextDestroyed(ServletContextEvent sce) { - } - - @Override - public void contextInitialized(ServletContextEvent sce) { + protected void configure() { // Only load logback configuration if GUACAMOLE_HOME exists File guacamoleHome = GuacamoleHome.getDirectory(); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java new file mode 100644 index 000000000..f6277eeaa --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +/** + * Useful constants for the REST API. + * + * @author James Muehlner + */ +public class APIConstants { + + /** + * The identifier of the ROOT connection group. + */ + public static final String ROOT_CONNECTION_GROUP_IDENTIFIER = "ROOT"; + +} 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..f08c2e402 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +/** + * A simple object to represent an error to be sent from the REST API. + * @author James Muehlner + */ +public class APIError { + + /** + * The error message. + */ + private final 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/APIPatch.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIPatch.java new file mode 100644 index 000000000..c5c2dec8e --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIPatch.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +/** + * An object for representing the body of a HTTP PATCH method. + * See https://tools.ietf.org/html/rfc6902 + * + * @author James Muehlner + * @param The type of object being patched. + */ +public class APIPatch { + + /** + * The possible operations for a PATCH request. + */ + public enum Operation { + add, remove, test, copy, replace, move + } + + /** + * The operation to perform for this patch. + */ + private Operation op; + + /** + * The value for this patch. + */ + private T value; + + /** + * The path for this patch. + */ + private String path; + + /** + * Returns the operation for this patch. + * @return the operation for this patch. + */ + public Operation getOp() { + return op; + } + + /** + * Set the operation for this patch. + * @param op The operation for this patch. + */ + public void setOp(Operation op) { + this.op = op; + } + + /** + * Returns the value of this patch. + * @return The value of this patch. + */ + public T getValue() { + return value; + } + + /** + * Sets the value of this patch. + * @param value The value of this patch. + */ + public void setValue(T value) { + this.value = value; + } + + /** + * Returns the path for this patch. + * @return The path for this patch. + */ + public String getPath() { + return path; + } + + /** + * Set the path for this patch. + * @param path The path for this patch. + */ + public void setPath(String path) { + this.path = path; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java new file mode 100644 index 000000000..38ec68b5d --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +import javax.ws.rs.core.Response; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A method interceptor to wrap some custom exception handling around methods + * that expose AuthenticationProvider functionality through the REST interface. + * Translates various types of GuacamoleExceptions into appropriate HTTP responses. + * + * @author James Muehlner + */ +public class AuthProviderRESTExceptionWrapper implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + // Get the logger for the intercepted class + Logger logger = LoggerFactory.getLogger(invocation.getMethod().getDeclaringClass()); + + try { + return invocation.proceed(); + } + catch(GuacamoleSecurityException e) { + throw new HTTPException(Response.Status.FORBIDDEN, 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 executing " + invocation.getMethod().getName() + ".", 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/AuthProviderRESTExposure.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExposure.java new file mode 100644 index 000000000..2a450c2c3 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExposure.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks that a method exposes functionality from the Guacamole AuthenticationProvider + * using a REST interface. + * + * @author James Muehlner + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AuthProviderRESTExposure {} 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..b4d00d540 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +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 message The message to build the response entity with. + */ + 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/PATCH.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/PATCH.java new file mode 100644 index 000000000..bca1b3389 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/PATCH.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +package org.glyptodon.guacamole.net.basic.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.ws.rs.HttpMethod; + +/** + * An annotation for using the HTTP PATCH method in the REST endpoints. + * + * @author James Muehlner + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PATCH") +public @interface PATCH {} \ No newline at end of file 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..049f37a7e --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +import com.google.inject.AbstractModule; +import com.google.inject.matcher.Matchers; +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.BasicTokenSessionMap; +import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator; +import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap; +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.protocol.ProtocolRetrievalService; +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("Unable to read authentication provider from guacamole.properties: {}", e.getMessage()); + logger.debug("Error reading authentication provider from guacamole.properties.", e); + throw new RuntimeException(e); + } + + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(TokenSessionMap.class).toInstance(new BasicTokenSessionMap()); + bind(ConnectionService.class); + bind(ConnectionGroupService.class); + bind(PermissionService.class); + bind(UserService.class); + bind(AuthenticationService.class); + bind(ProtocolRetrievalService.class); + + bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); + + bindInterceptor(Matchers.any(), Matchers.annotatedWith(AuthProviderRESTExposure.class), new AuthProviderRESTExceptionWrapper()); + } + +} 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..399136fae --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +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.protocol.ProtocolRESTService; +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(ProtocolRESTService.class); + bind(UserRESTService.class); + bind(LoginRESTService.class); + + // Set up the servlet and JSON mappings + bind(GuiceContainer.class); + bind(JacksonJsonProvider.class).in(Scopes.SINGLETON); + serve("/api/*").with(GuiceContainer.class); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogin.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java similarity index 52% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogin.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java index a462d5e4a..5d71c9edb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicLogin.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,32 +20,50 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package org.glyptodon.guacamole.net.basic.rest.auth; /** - * Simple dummy AuthenticatingHttpServlet which provides an endpoint for arbitrary - * authentication requests that do not expect a response. - * - * @author Michael Jumper + * A simple object to represent an auth token/userID pair in the API. + * + * @author James Muehlner */ -public class BasicLogin extends RestrictedHttpServlet { +public class APIAuthToken { + + /** + * The auth token. + */ + private final String authToken; + + + /** + * The user ID. + */ + private final String userID; /** - * Logger for this class. + * Get the auth token. + * @return The auth token. */ - private final Logger logger = LoggerFactory.getLogger(BasicLogin.class); - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) { - logger.debug("Login was successful for user \"{}\".", context.self().getUsername()); + public String getAuthToken() { + return authToken; + } + + /** + * Get the user ID. + * @return The user ID. + */ + public String getUserID() { + return userID; + } + + /** + * Create a new APIAuthToken Object with the given auth token. + * + * @param authToken The auth token to create the new APIAuthToken with. + * @param userID The ID of the user owning the given token. + */ + public APIAuthToken(String authToken, String userID) { + this.authToken = authToken; + this.userID = userID; } - } 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..1c35d580e --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthTokenGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +/** + * 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..46eb082c4 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +import com.google.inject.Inject; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleUnauthorizedException; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.GuacamoleSession; + +/** + * A service for performing authentication checks in REST endpoints. + * + * @author James Muehlner + */ +public class AuthenticationService { + + /** + * The map of auth tokens to sessions for the REST endpoints. + */ + @Inject + private TokenSessionMap tokenSessionMap; + + /** + * Finds the Guacamole session 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 session that corresponds to the provided auth token. + * @throws GuacamoleException If the auth token does not correspond to any + * logged in user. + */ + public GuacamoleSession getGuacamoleSession(String authToken) + throws GuacamoleException { + + // Try to get the session from the map of logged in users. + GuacamoleSession session = tokenSessionMap.get(authToken); + + // Authentication failed. + if (session == null) + throw new GuacamoleUnauthorizedException("Permission Denied."); + + return session; + + } + + /** + * 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 user context that corresponds to the provided auth token. + * @throws GuacamoleException If the auth token does not correspond to any + * logged in user. + */ + public UserContext getUserContext(String authToken) throws GuacamoleException { + return getGuacamoleSession(authToken).getUserContext(); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenSessionMap.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenSessionMap.java new file mode 100644 index 000000000..7b5a3e608 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/BasicTokenSessionMap.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.GuacamoleSession; +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 TokenSessionMap with support + * for session timeouts. + * + * @author James Muehlner + */ +public class BasicTokenSessionMap implements TokenSessionMap { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(BasicTokenSessionMap.class); + + /** + * The last time a user with a specific auth token accessed the API. + */ + private final Map lastAccessTimeMap = new HashMap(); + + /** + * Keeps track of the authToken to GuacamoleSession mapping. + */ + private final Map sessionMap = new HashMap(); + + /** + * The session timeout configuration for an API session, in milliseconds. + */ + private final long SESSION_TIMEOUT; + + /** + * Create a new BasicTokenGuacamoleSessionMap and initialize the session timeout value. + */ + public BasicTokenSessionMap() { + + // 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) { + sessionMap.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 GuacamoleSession 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 GuacamoleSession + logAccessTime(authToken); + return sessionMap.get(authToken); + + } + + @Override + public void put(String authToken, GuacamoleSession session) { + + // Update the last access time, and create the token/GuacamoleSession mapping + logAccessTime(authToken); + sessionMap.put(authToken, session); + + } + +} 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..b4a85adf8 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/LoginRESTService.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +import com.google.inject.Inject; +import javax.servlet.http.HttpServletRequest; +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.Context; +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.GuacamoleSession; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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("/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 sessions for the REST endpoints. + */ + @Inject + private TokenSessionMap tokenSessionMap; + + /** + * 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. + * @param request The HttpServletRequest associated with the login attempt. + * @return The auth token for the newly logged-in user. + * @throws GuacamoleException If an error prevents successful login. + */ + @POST + @AuthProviderRESTExposure + public APIAuthToken login(@QueryParam("username") String username, + @QueryParam("password") String password, + @Context HttpServletRequest request) throws GuacamoleException { + + Credentials credentials = new Credentials(); + credentials.setUsername(username); + credentials.setPassword(password); + credentials.setRequest(request); + credentials.setSession(request.getSession(true)); + + 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(); + + tokenSessionMap.put(authToken, new GuacamoleSession(credentials, userContext)); + + logger.debug("Login was successful for user \"{}\".", userContext.self().getUsername()); + return new APIAuthToken(authToken, username); + + } + +} 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..7fadd1014 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/SecureRandomAuthTokenGenerator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +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 final 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/TokenSessionMap.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenSessionMap.java new file mode 100644 index 000000000..9991aab52 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenSessionMap.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +import org.glyptodon.guacamole.net.basic.GuacamoleSession; + +/** + * Represents a mapping of auth token to Guacamole session for the REST + * authentication system. + * + * @author James Muehlner + */ +public interface TokenSessionMap { + + /** + * Registers that a user has just logged in with the specified authToken and + * GuacamoleSession. + * + * @param authToken The authentication token for the logged in user. + * @param session The GuacamoleSession for the logged in user. + */ + public void put(String authToken, GuacamoleSession session); + + /** + * Get the GuacamoleSession 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 GuacamoleSession for the given auth token, if the auth token + * represents a currently logged in user, null otherwise. + */ + public GuacamoleSession get(String authToken); + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java similarity index 86% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java index 73acc5540..ece55a39c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ */ /** - * Servlets dedicated to CRUD operations related to Connections. + * Classes related to the authentication aspect of the Guacamole REST API. */ -package org.glyptodon.guacamole.net.basic.crud.connections; +package org.glyptodon.guacamole.net.basic.rest.auth; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/CaptureClipboard.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java similarity index 64% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/CaptureClipboard.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java index 4b1fc1cd4..972895cc4 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/CaptureClipboard.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java @@ -20,26 +20,36 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic; +package org.glyptodon.guacamole.net.basic.rest.clipboard; -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import com.google.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.GuacamoleUnsupportedException; -import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.ClipboardState; +import org.glyptodon.guacamole.net.basic.GuacamoleSession; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty; import org.glyptodon.guacamole.properties.GuacamoleProperties; /** - * Servlet which dumps the current contents of the clipboard. + * A REST service for reading the current contents of the clipboard. * * @author Michael Jumper */ -public class CaptureClipboard extends RestrictedHttpServlet { +@Path("/clipboard") +public class ClipboardRESTService { + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + /** * The amount of time to wait for clipboard changes, in milliseconds. */ @@ -55,30 +65,23 @@ public class CaptureClipboard extends RestrictedHttpServlet { }; - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) + @GET + @AuthProviderRESTExposure + public Response getClipboard(@QueryParam("token") String authToken) throws GuacamoleException { // Only bother if actually enabled if (GuacamoleProperties.getProperty(INTEGRATION_ENABLED, false)) { // Get clipboard - final HttpSession session = request.getSession(true); - final ClipboardState clipboard = AuthenticatingFilter.getClipboardState(session); + GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); + final ClipboardState clipboard = session.getClipboardState(); // Send clipboard contents - try { - synchronized (clipboard) { - clipboard.waitForContents(CLIPBOARD_TIMEOUT); - response.setContentType(clipboard.getMimetype()); - response.getOutputStream().write(clipboard.getContents()); - } - } - catch (IOException e) { - throw new GuacamoleServerException("Unable to send clipboard contents", e); + synchronized (clipboard) { + clipboard.waitForContents(CLIPBOARD_TIMEOUT); + return Response.ok(clipboard.getContents(), + clipboard.getMimetype()).build(); } } 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..dab0c3884 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connection; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; +import org.glyptodon.guacamole.net.basic.rest.APIConstants; +import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; + +/** + * A simple connection to expose through the REST endpoints. + * + * @author James Muehlner + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class APIConnection { + + /** + * The name of this connection. + */ + private String name; + + /** + * The identifier of this connection. + */ + private String identifier; + + /** + * The identifier of the parent connection group for this connection. + */ + private String parentIdentifier; + + /** + * The protocol of this connection. + */ + private String protocol; + + /** + * 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.parentIdentifier = connection.getParentIdentifier(); + this.history = connection.getHistory(); + + // Use the explicit ROOT group ID + if (this.parentIdentifier == null) + this.parentIdentifier = APIConstants.ROOT_CONNECTION_GROUP_IDENTIFIER; + + GuacamoleConfiguration configuration = connection.getConfiguration(); + + this.protocol = configuration.getProtocol(); + + for (String key: configuration.getParameterNames()) + this.parameters.put(key, configuration.getParameter(key)); + + } + + /** + * 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. + * @param identifier The unique identifier for this connection. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Returns the unique identifier for this connection. + * @return The unique identifier for this connection. + */ + public String getParentIdentifier() { + return parentIdentifier; + } + + /** + * Sets the parent connection group identifier for this connection. + * @param parentIdentifier The parent connection group identifier + * for this connection. + */ + public void setParentIdentifier(String parentIdentifier) { + this.parentIdentifier = parentIdentifier; + } + + /** + * 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; + } + + /** + * Returns the protocol for this connection. + * @return The protocol for this connection. + */ + public String getProtocol() { + return protocol; + } + + /** + * Sets the protocol for this connection. + * @param protocol protocol for this connection. + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } + +} 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..aca765c40 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connection; + +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 String getParentIdentifier() { + return apiConnection.getParentIdentifier(); + } + + @Override + public void setParentIdentifier(String parentIdentifier) { + apiConnection.setParentIdentifier(parentIdentifier); + } + + @Override + public GuacamoleConfiguration getConfiguration() { + + // Create the GuacamoleConfiguration from the parameter map + GuacamoleConfiguration configuration = new GuacamoleConfiguration(); + + Map parameters = apiConnection.getParameters(); + + for(Map.Entry entry : parameters.entrySet()) + configuration.setParameter(entry.getKey(), entry.getValue()); + + configuration.setProtocol(apiConnection.getProtocol()); + + return configuration; + } + + @Override + public void setConfiguration(GuacamoleConfiguration config) { + + // Create a parameter map from the GuacamoleConfiguration + Map parameters = apiConnection.getParameters(); + for(String key : config.getParameterNames()) + parameters.put(key, config.getParameter(key)); + + // Set the protocol + apiConnection.setProtocol(config.getProtocol()); + } + + @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..103b56643 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connection; + +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.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.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.AuthProviderRESTExposure; +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("/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. + * @throws GuacamoleException If a problem is encountered while listing connections. + */ + @GET + @AuthProviderRESTExposure + public List getConnections(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while retrieving the connection. + */ + @GET + @Path("/{connectionID}") + @AuthProviderRESTExposure + public APIConnection getConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while deleting the connection. + */ + @DELETE + @Path("/{connectionID}") + @AuthProviderRESTExposure + public void deleteConnection(@QueryParam("token") String authToken, @PathParam("connectionID") String connectionID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while creating the connection. + */ + @POST + @AuthProviderRESTExposure + public String createConnection(@QueryParam("token") String authToken, + @QueryParam("parentID") String parentID, APIConnection connection) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + 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(); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while updating the connection. + */ + @POST + @Path("/{connectionID}") + @AuthProviderRESTExposure + public void updateConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID, APIConnection connection) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + 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(); + + Connection connectionFromAuthProvider = connectionDirectory.get(connectionID); + + // Make sure the connection is there before trying to update + if (connectionFromAuthProvider == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + // Copy the information from this connection over to an object from the Auth Provider + APIConnectionWrapper wrappedConnection = new APIConnectionWrapper(connection); + connectionFromAuthProvider.setConfiguration(wrappedConnection.getConfiguration()); + connectionFromAuthProvider.setName(wrappedConnection.getName()); + + // Update the connection + connectionDirectory.update(connectionFromAuthProvider); + + } + + /** + * 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 connection is to be moved to. + * @throws GuacamoleException If a problem is encountered while moving the connection. + */ + @PUT + @Path("/{connectionID}") + @AuthProviderRESTExposure + public void moveConnection(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID, @QueryParam("parentID") String parentID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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()); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Delete.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java similarity index 51% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Delete.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java index 3c2957ce3..44449818d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connections/Delete.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,40 +20,40 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.crud.connections; +package org.glyptodon.guacamole.net.basic.rest.connection; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +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; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; /** - * Simple HttpServlet which handles connection deletion. - * - * @author Michael Jumper + * A service for performing useful manipulations on REST Connections. + * + * @author James Muehlner */ -public class Delete extends RestrictedHttpServlet { +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 { - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get ID - String identifier = request.getParameter("id"); - - // Attempt to get connection directory - Directory directory = - context.getRootConnectionGroup().getConnectionDirectory(); - - // Remove connection - directory.remove(identifier); + 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/crud/connectiongroups/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java similarity index 85% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java index af137a42b..3d55216fd 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/connectiongroups/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ */ /** - * Servlets dedicated to CRUD operations related to ConnectionGroups. + * Classes related to the connection manipulation aspect of the Guacamole REST API. */ -package org.glyptodon.guacamole.net.basic.crud.connectiongroups; +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..4ac155d39 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.ConnectionGroup.Type; +import org.glyptodon.guacamole.net.basic.rest.APIConstants; + +/** + * A simple connection group to expose through the REST endpoints. + * + * @author James Muehlner + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class APIConnectionGroup { + + /** + * The name of this connection group. + */ + private String name; + + /** + * The identifier of this connection group. + */ + private String identifier; + + /** + * The identifier of the parent connection group for this connection group. + */ + private String parentIdentifier; + + /** + * 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.parentIdentifier = connectionGroup.getParentIdentifier(); + + // Use the explicit ROOT group ID + if (this.parentIdentifier == null) + this.parentIdentifier = APIConstants.ROOT_CONNECTION_GROUP_IDENTIFIER; + + 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 unique identifier for this connection group. + * @return The unique identifier for this connection group. + */ + public String getParentIdentifier() { + return parentIdentifier; + } + /** + * Sets the parent connection group identifier for this connection group. + * @param parentIdentifier The parent connection group identifier + * for this connection group. + */ + public void setParentIdentifier(String parentIdentifier) { + this.parentIdentifier = parentIdentifier; + } + + /** + * 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..c38b22977 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroupWrapper.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +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 final 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 String getParentIdentifier() { + return apiConnectionGroup.getParentIdentifier(); + } + + @Override + public void setParentIdentifier(String parentIdentifier) { + apiConnectionGroup.setParentIdentifier(parentIdentifier); + } + + @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..aa04a5f60 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.connectiongroup; + +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.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.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +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("/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; + + /** + * The ID that will be guaranteed to refer to the root connection group. + */ + private static final String ROOT_CONNECTION_GROUP_ID = "ROOT"; + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while listing connection groups. + */ + @GET + @AuthProviderRESTExposure + public List getConnectionGroups(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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 connection group found with the provided parentID."); + + Directory connectionGroupDirectory = + parentConnectionGroup.getConnectionGroupDirectory(); + + // Return the converted connection group list + return connectionGroupService.convertConnectionGroupList(connectionGroupDirectory); + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while retrieving the connection group. + */ + @GET + @Path("/{connectionGroupID}") + @AuthProviderRESTExposure + public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Return the root group if it was asked for + if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + return new APIConnectionGroup(rootGroup); + + 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); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while deleting the connection group. + */ + @DELETE + @Path("/{connectionGroupID}") + @AuthProviderRESTExposure + public void deleteConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Use the root group if it was asked for + if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + connectionGroupID = rootGroup.getIdentifier(); + + 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); + + } + + /** + * 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 connectionGroup The connection group to create. + * @return The identifier of the new connection group. + * @throws GuacamoleException If a problem is encountered while creating the connection group. + */ + @POST + @AuthProviderRESTExposure + public String createConnectionGroup(@QueryParam("token") String authToken, + @QueryParam("parentID") String parentID, APIConnectionGroup connectionGroup) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + 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(); + + } + + /** + * Updates a 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 update. + * @param connectionGroup The connection group to update. + * @throws GuacamoleException If a problem is encountered while updating the connection group. + */ + @POST + @Path("/{connectionGroupID}") + @AuthProviderRESTExposure + public void updateConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID, APIConnectionGroup connectionGroup) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + if (connectionGroup == null) + throw new GuacamoleClientException("A connection group is required for this request."); + + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Use the root group if it was asked for + if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + connectionGroupID = rootGroup.getIdentifier(); + + 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)); + + } + + /** + * 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 connectionGroupID The ID of the ConnectionGroup to move. + * @param parentID The ID of the ConnectionGroup the connection group is to be moved to. + * @throws GuacamoleException If a problem is encountered while moving the connection group. + */ + @PUT + @Path("/{connectionGroupID}") + @AuthProviderRESTExposure + public void moveConnectionGroup(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID, + @QueryParam("parentID") String parentID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the connection group directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Use the root group if it was asked for + if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + connectionGroupID = rootGroup.getIdentifier(); + + 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()); + + } + +} 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..ecbe5fffd --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.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; + +/** + * 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..dc404cbc1 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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/crud/users/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java similarity index 87% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java index bad264863..31a00e57c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ */ /** - * Servlets dedicated to CRUD operations related to Users. + * Classes related to the basic Guacamole REST API. */ -package org.glyptodon.guacamole.net.basic.crud.users; +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..ee97de0fd --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.permission; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +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 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@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 { + + /** + * A normal connection. + */ + CONNECTION, + + /** + * A connection group. + */ + CONNECTION_GROUP, + + /** + * A Guacamole user. + */ + USER, + + /** + * The Guacamole system itself. + */ + 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) { + + // Connection permission + if (permission instanceof ConnectionPermission) { + this.objectType = ObjectType.CONNECTION; + this.objectPermissionType = ((ConnectionPermission) permission).getType(); + this.objectIdentifier = ((ConnectionPermission) permission).getObjectIdentifier(); + } + + // Connection group permission + else if (permission instanceof ConnectionGroupPermission) { + this.objectType = ObjectType.CONNECTION_GROUP; + this.objectPermissionType = ((ConnectionGroupPermission) permission).getType(); + this.objectIdentifier = ((ConnectionGroupPermission) permission).getObjectIdentifier(); + } + + // User permission + else if (permission instanceof UserPermission) { + this.objectType = ObjectType.USER; + this.objectPermissionType = ((UserPermission) permission).getType(); + this.objectIdentifier = ((UserPermission) permission).getObjectIdentifier(); + } + + // System permission + 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. + * + * @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..08e49812b --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.permission; + +import com.google.inject.Inject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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.GuacamoleException; +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.auth.permission.Permission; +import org.glyptodon.guacamole.net.basic.rest.APIPatch; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.PATCH; +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("/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. + * @throws GuacamoleException If a problem is encountered while listing permissions. + */ + @GET + @Path("/{userID}") + @AuthProviderRESTExposure + public List getPermissions(@QueryParam("token") String authToken, @PathParam("userID") String userID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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()); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while adding the permission. + */ + @POST + @Path("/{userID}") + @AuthProviderRESTExposure + public void addPermission(@QueryParam("token") String authToken, + @PathParam("userID") String userID, APIPermission permission) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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()); + userContext.getUserDirectory().update(user); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while removing the permission. + */ + @POST + @Path("/remove/{userID}/") + @AuthProviderRESTExposure + public void removePermission(@QueryParam("token") String authToken, + @PathParam("userID") String userID, APIPermission permission) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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()); + userContext.getUserDirectory().update(user); + + } + + + /** + * Applies a given list of permission patches. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param patches The permission patches to apply for this request. + * @throws GuacamoleException If a problem is encountered while removing the permission. + */ + @PATCH + @AuthProviderRESTExposure + public void patchPermissions(@QueryParam("token") String authToken, + List> patches) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the user directory + Directory userDirectory = userContext.getUserDirectory(); + + // All users who have had permissions added or removed + Map modifiedUsers = new HashMap(); + + for (APIPatch patch : patches) { + + String userID = patch.getPath(); + Permission permission = patch.getValue().toPermission(); + + // See if we've already modified this user in this request + User user = modifiedUsers.get(userID); + if (user == null) + user = userDirectory.get(userID); + + if (user == null) + throw new HTTPException(Status.NOT_FOUND, "User not found with userID " + userID + "."); + + // Only the add and remove operations are supported for permissions + switch(patch.getOp()) { + case add: + user.addPermission(permission); + modifiedUsers.put(userID, user); + break; + case remove: + user.removePermission(permission); + modifiedUsers.put(userID, user); + break; + } + + } + + // Save the permission changes for all modified users + for (User user : modifiedUsers.values()) + userDirectory.update(user); + + } + +} 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..bdff3d84b --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.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; + +/** + * 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..274d74b0b --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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/protocol/ProtocolRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/ProtocolRESTService.java new file mode 100644 index 000000000..dacdf8f56 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/ProtocolRESTService.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.protocol; + +import com.google.inject.Inject; +import java.util.Map; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.ProtocolInfo; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A REST Service for handling the listing of protocols. + * + * @author James Muehlner + */ +@Path("/protocol") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ProtocolRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ProtocolRESTService.class); + + /** + * Service for retrieving protocol definitions. + */ + @Inject + private ProtocolRetrievalService protocolRetrievalservice; + + /** + * Gets a map of protocols defined in the system - protocol name to protocol. + * + * @return The protocol map. + * @throws GuacamoleException If a problem is encountered while listing protocols. + */ + @GET + @AuthProviderRESTExposure + public Map getProtocols() throws GuacamoleException { + + // Get and return a map of all protocols. + return protocolRetrievalservice.getProtocolMap(); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/List.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/ProtocolRetrievalService.java similarity index 51% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/List.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/ProtocolRetrievalService.java index f8eda88e9..9f8958f8d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/List.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/ProtocolRetrievalService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.crud.protocols; +package org.glyptodon.guacamole.net.basic.rest.protocol; import java.io.BufferedInputStream; import java.io.File; @@ -30,18 +30,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; import org.glyptodon.guacamole.net.basic.ProtocolInfo; -import org.glyptodon.guacamole.net.basic.ProtocolParameter; -import org.glyptodon.guacamole.net.basic.ProtocolParameterOption; import org.glyptodon.guacamole.net.basic.xml.DocumentHandler; import org.glyptodon.guacamole.net.basic.xml.protocol.ProtocolTagHandler; import org.glyptodon.guacamole.properties.GuacamoleHome; @@ -53,18 +43,18 @@ import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; /** - * Simple HttpServlet which outputs XML containing a list of all visible - * protocols. - * - * @author Michael Jumper + * A service for retrieving protocol definitions from the + * XML files storing the information. + * + * @author James Muehlner */ -public class List extends RestrictedHttpServlet { +public class ProtocolRetrievalService { /** * Logger for this class. */ - private Logger logger = LoggerFactory.getLogger(List.class); - + private static final Logger logger = LoggerFactory.getLogger(ProtocolRetrievalService.class); + /** * Array of all known protocol names. */ @@ -108,110 +98,22 @@ public class List extends RestrictedHttpServlet { } catch (IOException e) { - throw new GuacamoleException("Error reading basic user mapping file.", e); + throw new GuacamoleException(e); } catch (SAXException e) { - throw new GuacamoleException("Error parsing basic user mapping XML.", e); + throw new GuacamoleException(e); } } /** - * Given an XML stream and a fully-populated ProtocolInfo object, writes - * out the corresponding protocol XML describing all available parameters. + * Retrieves a map of protocol name to ProtocolInfo for all protocols defined + * in the XML files. * - * @param xml The XMLStreamWriter to use to write the XML. - * @param protocol The ProtocolInfo object to read parameters and protocol - * information from. - * @throws XMLStreamException If an error occurs while writing the XML. + * @return A map of all defined protocols. + * @throws GuacamoleException If an error occurs while retrieving the protocols. */ - private void writeProtocol(XMLStreamWriter xml, ProtocolInfo protocol) - throws XMLStreamException { - - // Write protocol - xml.writeStartElement("protocol"); - xml.writeAttribute("name", protocol.getName()); - xml.writeAttribute("title", protocol.getTitle()); - - // Write parameters - for (ProtocolParameter param : protocol.getParameters()) { - - // Write param tag - xml.writeStartElement("param"); - xml.writeAttribute("name", param.getName()); - xml.writeAttribute("title", param.getTitle()); - - // Write type - switch (param.getType()) { - - // Text parameter - case TEXT: - xml.writeAttribute("type", "text"); - break; - - // Password parameter - case PASSWORD: - xml.writeAttribute("type", "password"); - break; - - // Numeric parameter - case NUMERIC: - xml.writeAttribute("type", "numeric"); - break; - - // Boolean parameter - case BOOLEAN: - xml.writeAttribute("type", "boolean"); - xml.writeAttribute("value", param.getValue()); - break; - - // Enumerated parameter - case ENUM: - xml.writeAttribute("type", "enum"); - break; - - // Multiline parameter - case MULTILINE: - xml.writeAttribute("type", "multiline"); - break; - - // If unknown, fail explicitly - default: - throw new UnsupportedOperationException( - "Parameter type not supported: " + param.getType()); - - } - - // Write options - for (ProtocolParameterOption option : param.getOptions()) { - xml.writeStartElement("option"); - xml.writeAttribute("value", option.getValue()); - xml.writeCharacters(option.getTitle()); - xml.writeEndElement(); - } - - // End parameter - xml.writeEndElement(); - - } - - // End protocol - xml.writeEndElement(); - - } - - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Do not cache - response.setHeader("Cache-Control", "no-cache"); - - // Set encoding - response.setCharacterEncoding("UTF-8"); - + public Map getProtocolMap() throws GuacamoleException { // Map of all available protocols Map protocols = new HashMap(); @@ -225,20 +127,16 @@ public class List extends RestrictedHttpServlet { // Get all XML files File[] files = protocol_directory.listFiles( new FilenameFilter() { - @Override public boolean accept(File file, String string) { return string.endsWith(".xml"); } - } ); // Load each protocol from each file for (File file : files) { - try { - // Parse protocol FileInputStream stream = new FileInputStream(file); ProtocolInfo protocol = getProtocol(stream); @@ -252,9 +150,7 @@ public class List extends RestrictedHttpServlet { logger.error("Unable to read connection parameter information from \"{}\": {}", file.getAbsolutePath(), e.getMessage()); logger.debug("Error reading protocol XML.", e); } - } - } // If known protocols are not already defined, read from classpath @@ -262,8 +158,7 @@ public class List extends RestrictedHttpServlet { // If protocol not defined yet, attempt to load from classpath if (!protocols.containsKey(protocol)) { - - InputStream stream = List.class.getResourceAsStream( + InputStream stream = ProtocolRetrievalService.class.getResourceAsStream( "/net/sourceforge/guacamole/net/protocols/" + protocol + ".xml"); @@ -272,39 +167,9 @@ public class List extends RestrictedHttpServlet { protocols.put(protocol, getProtocol(stream)); } - } - - // Write actual XML - try { - // Write XML content type - response.setHeader("Content-Type", "text/xml"); - - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter()); - - // Begin document - xml.writeStartDocument(); - xml.writeStartElement("protocols"); - - // Write all protocols - for (ProtocolInfo protocol : protocols.values()) - writeProtocol(xml, protocol); - - // End document - xml.writeEndElement(); - xml.writeEndDocument(); - - } - catch (XMLStreamException e) { - throw new GuacamoleServerException( - "Unable to write protocol list XML.", e); - } - catch (IOException e) { - throw new GuacamoleServerException( - "I/O error writing protocol list XML.", e); - } - + + return protocols; } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/package-info.java similarity index 86% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/package-info.java index ada38e392..445dc5cef 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/protocols/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/protocol/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ */ /** - * Servlets dedicated to CRUD operations related to protocols. + * Classes related to the protocol retrieval aspect of the Guacamole REST API. */ -package org.glyptodon.guacamole.net.basic.crud.protocols; +package org.glyptodon.guacamole.net.basic.rest.protocol; 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..2efcb6992 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUser.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.user; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.glyptodon.guacamole.net.auth.User; + +/** + * A simple User to expose through the REST endpoints. + * + * @author James Muehlner + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@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 empty APIUser. + */ + public APIUser() {} + + /** + * 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..dec5a9a65 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/APIUserWrapper.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.user; + +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 final 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..176615b1d --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.user; + +import com.google.inject.Inject; +import java.util.List; +import java.util.UUID; +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.GuacamoleException; +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.AuthProviderRESTExposure; +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 user CRUD operations. + * + * @author James Muehlner + */ +@Path("/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. + * @throws GuacamoleException If a problem is encountered while listing users. + */ + @GET + @AuthProviderRESTExposure + public List getUsers(@QueryParam("token") String authToken) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Convert and return the user directory listing + return userService.convertUserList(userDirectory); + + } + + /** + * Gets an individual user. + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The ID of the user to retrieve. + * @return user The user. + * @throws GuacamoleException If a problem is encountered while retrieving the user. + */ + @GET + @Path("/{userID}") + @AuthProviderRESTExposure + public APIUser getUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while creating the user. + * + * @return The username of the newly created user. + */ + @POST + @AuthProviderRESTExposure + public String createUser(@QueryParam("token") String authToken, APIUser user) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the directory + Directory userDirectory = userContext.getUserDirectory(); + + // Randomly set the password if it wasn't provided + if (user.getPassword() == null) + user.setPassword(UUID.randomUUID().toString()); + + // Create the user + userDirectory.add(new APIUserWrapper(user)); + + return user.getUsername(); + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while updating the user. + */ + @POST + @Path("/{userID}") + @AuthProviderRESTExposure + public void updateUser(@QueryParam("token") String authToken, @PathParam("userID") String userID, APIUser user) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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."); + + // Do not update the user password if no password was provided + if (user.getPassword() != null) { + /* + * Update the user with the permission set from the existing user + * since the user REST endpoints do not expose permissions. + */ + existingUser.setPassword(user.getPassword()); + userDirectory.update(existingUser); + } + + } + + /** + * 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. + * @throws GuacamoleException If a problem is encountered while deleting the user. + */ + @DELETE + @Path("/{userID}") + @AuthProviderRESTExposure + public void deleteUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // 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); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Delete.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java similarity index 54% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Delete.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java index 8eb5916d3..c671b5246 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/users/Delete.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,39 +20,40 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.crud.users; +package org.glyptodon.guacamole.net.basic.rest.user; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +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; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; /** - * Simple HttpServlet which handles user deletion. - * - * @author Michael Jumper + * A service for performing useful manipulations on REST Users. + * + * @author James Muehlner */ -public class Delete extends RestrictedHttpServlet { +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 { - @Override - protected void restrictedService( - UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - // Get username - String username = request.getParameter("name"); - - // Attempt to get user directory - Directory directory = context.getUserDirectory(); - - // Remove user - directory.remove(username); + 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/crud/permissions/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java similarity index 86% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/permissions/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java index 9617b8020..d45b5f4f8 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/crud/permissions/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ */ /** - * Servlets dedicated to CRUD operations related to Permissions. + * Classes related to the user manipulation aspect of the Guacamole REST API. */ -package org.glyptodon.guacamole.net.basic.crud.permissions; +package org.glyptodon.guacamole.net.basic.rest.user; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/BasicGuacamoleWebSocketTunnelEndpoint.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/BasicGuacamoleWebSocketTunnelEndpoint.java index 77db63f68..0dbc11efc 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/BasicGuacamoleWebSocketTunnelEndpoint.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/BasicGuacamoleWebSocketTunnelEndpoint.java @@ -22,25 +22,22 @@ package org.glyptodon.guacamole.net.basic.websocket; +import com.google.inject.Provider; import java.util.Map; import javax.websocket.EndpointConfig; import javax.websocket.HandshakeResponse; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; -import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; +import org.glyptodon.guacamole.net.basic.TunnelRequestService; import org.glyptodon.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint; /** * Tunnel implementation which uses WebSocket as a tunnel backend, rather than * HTTP, properly parsing connection IDs included in the connection request. */ -@ServerEndpoint(value = "/websocket-tunnel", - subprotocols = {"guacamole"}, - configurator = BasicGuacamoleWebSocketTunnelEndpoint.Configurator.class) public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint { /** @@ -62,6 +59,25 @@ public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTun */ public static class Configurator extends ServerEndpointConfig.Configurator { + /** + * Provider which provides instances of a service for handling + * tunnel requests. + */ + private final Provider tunnelRequestServiceProvider; + + /** + * Creates a new Configurator which uses the given tunnel request + * service provider to retrieve the necessary service to handle new + * connections requests. + * + * @param tunnelRequestServiceProvider The tunnel request service + * provider to use for all new + * connections. + */ + public Configurator(Provider tunnelRequestServiceProvider) { + this.tunnelRequestServiceProvider = tunnelRequestServiceProvider; + } + @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { @@ -72,8 +88,11 @@ public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTun userProperties.clear(); try { + // Get tunnel request service + TunnelRequestService tunnelRequestService = tunnelRequestServiceProvider.get(); + // Store new tunnel within user properties - GuacamoleTunnel tunnel = BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(request)); + GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new WebSocketTunnelRequest(request)); if (tunnel != null) userProperties.put(TUNNEL_USER_PROPERTY, tunnel); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/BasicGuacamoleWebSocketTunnelServlet.java index f9e6106a3..035e8d3c1 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/BasicGuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/BasicGuacamoleWebSocketTunnelServlet.java @@ -22,10 +22,12 @@ package org.glyptodon.guacamole.net.basic.websocket.jetty8; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; +import org.glyptodon.guacamole.net.basic.TunnelRequestService; import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest; /** @@ -33,12 +35,19 @@ import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest; * rather than HTTP, properly parsing connection IDs included in the connection * request. */ +@Singleton public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { + /** + * Service for handling tunnel requests. + */ + @Inject + private TunnelRequestService tunnelRequestService; + @Override protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { - return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); + return tunnelRequestService.createTunnel(new HTTPTunnelRequest(request)); } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java index 093bcbfaa..12c4660f2 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java @@ -22,10 +22,12 @@ package org.glyptodon.guacamole.net.basic.websocket.jetty9; +import com.google.inject.Inject; +import com.google.inject.Singleton; import org.eclipse.jetty.websocket.api.Session; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; +import org.glyptodon.guacamole.net.basic.TunnelRequestService; /** * WebSocket listener implementation which properly parses connection IDs @@ -33,11 +35,18 @@ import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; * * @author Michael Jumper */ +@Singleton public class BasicGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener { + /** + * Service for handling tunnel requests. + */ + @Inject + private TunnelRequestService tunnelRequestService; + @Override protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException { - return BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest())); + return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest())); } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java index e54cf8bdb..d0de4cbf3 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java @@ -22,10 +22,12 @@ package org.glyptodon.guacamole.net.basic.websocket.tomcat; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; +import org.glyptodon.guacamole.net.basic.TunnelRequestService; import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest; /** @@ -33,12 +35,19 @@ import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest; * rather than HTTP, properly parsing connection IDs included in the connection * request. */ +@Singleton public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { + /** + * Service for handling tunnel requests. + */ + @Inject + private TunnelRequestService tunnelRequestService; + @Override protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { - return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); + return tunnelRequestService.createTunnel(new HTTPTunnelRequest(request)); }; } diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml index 7c9985f62..a59ddbe24 100644 --- a/guacamole/src/main/webapp/WEB-INF/web.xml +++ b/guacamole/src/main/webapp/WEB-INF/web.xml @@ -20,286 +20,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + index.xhtml - - 30 - + 30 - - - org.glyptodon.guacamole.net.basic.websocket.WebSocketSupportLoader - - - - - org.glyptodon.guacamole.net.basic.log.LogbackInitializer - - - + - AuthenticatingFilter - org.glyptodon.guacamole.net.basic.AuthenticatingFilter + guiceFilter + com.google.inject.servlet.GuiceFilter - AuthenticatingFilter - *.xhtml - /login - /clipboard - /connections/* - /connectiongroups/* - /users/* - /permissions/* - /protocols/* + guiceFilter + /* - - - RestrictedFilter - org.glyptodon.guacamole.net.basic.RestrictedFilter - - - RestrictedFilter - /websocket-tunnel - + + org.glyptodon.guacamole.net.basic.BasicServletContextListener + - - - Login servlet. - Login - org.glyptodon.guacamole.net.basic.BasicLogin - - - Login - /login - - - - - Logout servlet. - Logout - org.glyptodon.guacamole.net.basic.BasicLogout - - - Logout - /logout - - - - - Session keep-alive servlet. - SessionKeepAlive - org.glyptodon.guacamole.net.basic.SessionKeepAlive - - - SessionKeepAlive - /keep-alive - - - - - Clipboard state servlet. - Clipboard - org.glyptodon.guacamole.net.basic.CaptureClipboard - - - Clipboard - /clipboard - - - - - Connection creation servlet. - ConnectionCreate - org.glyptodon.guacamole.net.basic.crud.connections.Create - - - ConnectionCreate - /connections/create - - - - - Connection list servlet. - Connections - org.glyptodon.guacamole.net.basic.crud.connections.List - - - Connections - /connections - - - - - Connection update servlet. - ConnectionUpdate - org.glyptodon.guacamole.net.basic.crud.connections.Update - - - ConnectionUpdate - /connections/update - - - - - Connection move servlet. - ConnectionMove - org.glyptodon.guacamole.net.basic.crud.connections.Move - - - ConnectionMove - /connections/move - - - - - Connection deletion servlet. - ConnectionDelete - org.glyptodon.guacamole.net.basic.crud.connections.Delete - - - ConnectionDelete - /connections/delete - - - - - ConnectionGroup creation servlet. - ConnectionGroupCreate - org.glyptodon.guacamole.net.basic.crud.connectiongroups.Create - - - ConnectionGroupCreate - /connectiongroups/create - - - - - ConnectionGroup list servlet. - ConnectionGroups - org.glyptodon.guacamole.net.basic.crud.connectiongroups.List - - - ConnectionGroups - /connectiongroups - - - - - ConnectionGroup update servlet. - ConnectionGroupUpdate - org.glyptodon.guacamole.net.basic.crud.connectiongroups.Update - - - ConnectionGroupUpdate - /connectiongroups/update - - - - - ConnectionGroup move servlet. - ConnectionGroupMove - org.glyptodon.guacamole.net.basic.crud.connectiongroups.Move - - - ConnectionGroupMove - /connectiongroups/move - - - - - ConnectionGroup deletion servlet. - ConnectionGroupDelete - org.glyptodon.guacamole.net.basic.crud.connectiongroups.Delete - - - ConnectionGroupDelete - /connectiongroups/delete - - - - - User creation servlet. - UserCreate - org.glyptodon.guacamole.net.basic.crud.users.Create - - - UserCreate - /users/create - - - - - User list servlet. - Users - org.glyptodon.guacamole.net.basic.crud.users.List - - - Users - /users - - - - - User update servlet. - UserUpdate - org.glyptodon.guacamole.net.basic.crud.users.Update - - - UserUpdate - /users/update - - - - - User deletion servlet. - UserDelete - org.glyptodon.guacamole.net.basic.crud.users.Delete - - - UserDelete - /users/delete - - - - - Permission list servlet. - Permissions - org.glyptodon.guacamole.net.basic.crud.permissions.List - - - Permissions - /permissions - - - - - Protocol list servlet. - Protocols - org.glyptodon.guacamole.net.basic.crud.protocols.List - - - Protocols - /protocols - - - - - Tunnel servlet. - Tunnel - org.glyptodon.guacamole.net.basic.BasicGuacamoleTunnelServlet - - - Tunnel - /tunnel - - + mp3 audio/mpeg