GUAC-919: Copy REST and Guice changes over from GUAC-546 branch.

This commit is contained in:
James Muehlner
2014-10-30 23:54:58 -07:00
committed by Michael Jumper
parent 48382b8285
commit 6bf2ff3e2f
77 changed files with 3930 additions and 3477 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*~ *~
target/ target/
nb-configuration.xml

View File

@@ -185,6 +185,55 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Guice - Dependency Injection -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
<!-- Guice Servlet -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
</dependency>
<!-- Jersey - JAX-RS Implementation -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.17.1</version>
</dependency>
<!-- Jersey - Guice extension -->
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.17.1</version>
</dependency>
<!-- JSR-250 annotations -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<!-- Apache commons codec library -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
<!-- Jackson for JSON support -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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
}
}

View File

@@ -22,6 +22,8 @@
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.net.GuacamoleTunnel;
@@ -35,8 +37,15 @@ import org.slf4j.LoggerFactory;
* *
* @author Michael Jumper * @author Michael Jumper
*/ */
@Singleton
public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet { public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
/** /**
* Logger for this class. * Logger for this class.
*/ */
@@ -46,8 +55,8 @@ public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
// Attempt to create HTTP tunnel // 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 // If successful, warn of lack of WebSocket
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal."); logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -22,33 +22,31 @@
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic;
import java.io.IOException; import com.google.inject.Guice;
import javax.servlet.http.HttpServlet; import com.google.inject.Injector;
import javax.servlet.http.HttpServletRequest; import com.google.inject.servlet.GuiceServletContextListener;
import javax.servlet.http.HttpServletResponse; import org.glyptodon.guacamole.net.basic.log.LogModule;
import javax.servlet.http.HttpSession; 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 * A ServletContextListener to listen for initialization of the servlet context
* redirecting the user to the login page. * in order to set up dependency injection.
* *
* @author Michael Jumper * @author James Muehlner
*/ */
public class BasicLogout extends HttpServlet { public class BasicServletContextListener extends GuiceServletContextListener {
@Override @Override
protected void service(HttpServletRequest request, HttpServletResponse response) protected Injector getInjector() {
throws IOException {
// Invalidate session, if any
HttpSession httpSession = request.getSession(false);
if (httpSession != null)
httpSession.invalidate();
// Redirect to index
response.sendRedirect("index.xhtml");
return Guice.createInjector(
new LogModule(),
new RESTServletModule(),
new RESTModule(),
new TunnelModule()
);
} }
} }

View File

@@ -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<Object> listeners = new ArrayList<Object>();
/**
* 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<Class> 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<Object> getListeners() {
return Collections.unmodifiableCollection(listeners);
}
}

View File

@@ -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
}
}

View File

@@ -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;
}

View File

@@ -22,29 +22,43 @@
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic;
import javax.servlet.http.HttpServletRequest; import com.google.inject.Inject;
import javax.servlet.http.HttpServletResponse; 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.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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Dummy servlet which provides an endpoint for arbitrary requests intended to * REST service which updates the last access time of the Guacamole session,
* simply keep the HTTP session from expiring. * preventing the session from becoming invalid.
* *
* @author Michael Jumper * @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. * Logger for this class.
*/ */
private final Logger logger = LoggerFactory.getLogger(SessionKeepAlive.class); private final Logger logger = LoggerFactory.getLogger(SessionKeepAlive.class);
@Override @GET
protected void restrictedService( @AuthProviderRESTExposure
UserContext context, public void updateSession(@QueryParam("token") String authToken) throws GuacamoleException {
HttpServletRequest request, HttpServletResponse response) {
// Tickle the session
UserContext context = authenticationService.getUserContext(authToken);
// Do nothing // Do nothing
logger.debug("Keep-alive signal received from user \"{}\".", context.self().getUsername()); logger.debug("Keep-alive signal received from user \"{}\".", context.self().getUsername());

View File

@@ -20,36 +20,31 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.glyptodon.guacamole.net.basic.websocket; package org.glyptodon.guacamole.net.basic;
import java.lang.reflect.InvocationTargetException; import com.google.inject.Provider;
import java.lang.reflect.Method; import com.google.inject.servlet.ServletModule;
import javax.servlet.Servlet; import java.util.Arrays;
import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet;
import javax.servlet.ServletContextEvent; import javax.websocket.DeploymentException;
import javax.servlet.ServletContextListener; import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.glyptodon.guacamole.GuacamoleException; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Simple ServletContextListener which loads a WebSocket tunnel implementation * Module which loads tunnel implementations.
* 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.
* *
* @author Michael Jumper * @author Michael Jumper
*/ */
public class WebSocketSupportLoader implements ServletContextListener { public class TunnelModule extends ServletModule {
/** /**
* Logger for this class. * 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. * 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 { try {
// Attempt to find WebSocket servlet // Attempt to find WebSocket servlet
Class<Servlet> servlet = (Class<Servlet>) Class<HttpServlet> servlet = (Class<HttpServlet>)
GuacamoleClassLoader.getInstance().findClass(classname); GuacamoleClassLoader.getInstance().findClass(classname);
// Dynamically add servlet IF SERVLET 3.0 API AVAILABLE! // Add WebSocket servlet
try { serve("/websocket-tunnel").with(servlet);
return true;
// 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);
}
} }
@@ -160,21 +120,50 @@ public class WebSocketSupportLoader implements ServletContextListener {
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { protected void configureServlets() {
}
@Override bind(TunnelRequestService.class);
public void contextInitialized(ServletContextEvent sce) {
// Set up HTTP tunnel
serve("/tunnel").with(BasicGuacamoleTunnelServlet.class);
// Check for JSR 356 support // Check for JSR 356 support
if (implementsJSR_356()) { if (implementsJSR_356()) {
logger.info("JSR-356 WebSocket support present."); 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<TunnelRequestService> 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; return;
} }
// Try to load each WebSocket tunnel in sequence // Try to load each WebSocket tunnel in sequence
for (String classname : WEBSOCKET_CLASSES) { for (String classname : WEBSOCKET_CLASSES) {
if (loadWebSocketTunnel(sce.getServletContext(), classname)) { if (loadWebSocketTunnel(classname)) {
logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname); logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname);
return; return;
} }

View File

@@ -22,6 +22,9 @@
package org.glyptodon.guacamole.net.basic; 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.Collection;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpSession; 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.Directory;
import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.event.SessionListenerCollection; 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.TunnelCloseEvent;
import org.glyptodon.guacamole.net.event.TunnelConnectEvent; import org.glyptodon.guacamole.net.event.TunnelConnectEvent;
import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener; import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener;
@@ -55,12 +59,19 @@ import org.slf4j.LoggerFactory;
* *
* @author Michael Jumper * @author Michael Jumper
*/ */
public class BasicTunnelRequestUtility { @Singleton
public class TunnelRequestService {
/** /**
* Logger for this class. * 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 * 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 * error, the connect is canceled, and no other
* listeners will run. * listeners will run.
*/ */
private static boolean notifyConnect(Collection listeners, UserContext context, private boolean notifyConnect(Collection listeners, UserContext context,
Credentials credentials, GuacamoleTunnel tunnel) Credentials credentials, GuacamoleTunnel tunnel)
throws GuacamoleException { throws GuacamoleException {
@@ -119,7 +130,7 @@ public class BasicTunnelRequestUtility {
* error, the close is canceled, and no other * error, the close is canceled, and no other
* listeners will run. * listeners will run.
*/ */
private static boolean notifyClose(Collection listeners, UserContext context, private boolean notifyClose(Collection listeners, UserContext context,
Credentials credentials, GuacamoleTunnel tunnel) Credentials credentials, GuacamoleTunnel tunnel)
throws GuacamoleException { throws GuacamoleException {
@@ -151,7 +162,7 @@ public class BasicTunnelRequestUtility {
* @return The created tunnel, or null if the tunnel could not be created. * @return The created tunnel, or null if the tunnel could not be created.
* @throws GuacamoleException If an error occurs while creating the tunnel. * @throws GuacamoleException If an error occurs while creating the tunnel.
*/ */
public static GuacamoleTunnel createTunnel(TunnelRequest request) public GuacamoleTunnel createTunnel(TunnelRequest request)
throws GuacamoleException { throws GuacamoleException {
HttpSession httpSession = request.getSession(); HttpSession httpSession = request.getSession();
@@ -169,6 +180,10 @@ public class BasicTunnelRequestUtility {
throw e; throw e;
} }
// Get auth token and session
String authToken = request.getParameter("authToken");
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
// Get ID of connection // Get ID of connection
String id = request.getParameter("id"); String id = request.getParameter("id");
TunnelRequest.IdentifierType id_type = TunnelRequest.IdentifierType.getType(id); TunnelRequest.IdentifierType id_type = TunnelRequest.IdentifierType.getType(id);
@@ -181,17 +196,17 @@ public class BasicTunnelRequestUtility {
id = id.substring(id_type.PREFIX.length()); id = id.substring(id_type.PREFIX.length());
// Get credentials // Get credentials
final Credentials credentials = AuthenticatingFilter.getCredentials(httpSession); final Credentials credentials = session.getCredentials();
// Get context // Get context
final UserContext context = AuthenticatingFilter.getUserContext(httpSession); final UserContext context = session.getUserContext();
// If no context or no credentials, not logged in // If no context or no credentials, not logged in
if (context == null || credentials == null) if (context == null || credentials == null)
throw new GuacamoleSecurityException("Cannot connect - user not logged in."); throw new GuacamoleSecurityException("Cannot connect - user not logged in.");
// Get clipboard // Get clipboard
final ClipboardState clipboard = AuthenticatingFilter.getClipboardState(httpSession); final ClipboardState clipboard = session.getClipboardState();
// Get client information // Get client information
GuacamoleClientInformation info = new GuacamoleClientInformation(); GuacamoleClientInformation info = new GuacamoleClientInformation();
@@ -279,7 +294,7 @@ public class BasicTunnelRequestUtility {
// Monitor instructions which pertain to server-side events, if necessary // Monitor instructions which pertain to server-side events, if necessary
try { try {
if (GuacamoleProperties.getProperty(CaptureClipboard.INTEGRATION_ENABLED, false)) if (GuacamoleProperties.getProperty(ClipboardRESTService.INTEGRATION_ENABLED, false))
return new MonitoringGuacamoleReader(clipboard, super.acquireReader()); return new MonitoringGuacamoleReader(clipboard, super.acquireReader());
} }
catch (GuacamoleException e) { catch (GuacamoleException e) {

View File

@@ -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<String, ConnectionGroup> findConnectionGroupDirectory(
UserContext context, String parentID) throws GuacamoleException {
// Find the correct connection group directory
ConnectionGroup rootGroup = context.getRootConnectionGroup();
Directory<String, ConnectionGroup> directory;
Directory<String, ConnectionGroup> connectionGroupDirectory =
rootGroup.getConnectionGroupDirectory();
ConnectionGroup parentGroup = connectionGroupDirectory.get(parentID);
if(parentGroup == null)
return null;
directory = parentGroup.getConnectionGroupDirectory();
return directory;
}
}

View File

@@ -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<String, ConnectionGroup> 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);
}
}

View File

@@ -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<String, ConnectionGroup> directory =
context.getRootConnectionGroup().getConnectionGroupDirectory();
// Remove connection
directory.remove(identifier);
}
}

View File

@@ -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<String, Connection> getConnectionDirectory() throws GuacamoleException {
throw new UnsupportedOperationException("Connection directory unsupported in DummyConnectionGroup.");
}
@Override
public Directory<String, ConnectionGroup> getConnectionGroupDirectory() throws GuacamoleException {
throw new UnsupportedOperationException("Connection group directory unsuppprted in DummyConnectionGroup.");
}
}

View File

@@ -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<String, ConnectionGroup> directory)
throws GuacamoleException, XMLStreamException {
// If no connections, write nothing
Set<String> 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);
}
}
}

View File

@@ -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<String, ConnectionGroup> newParentDirectory =
ConnectionGroupUtility.findConnectionGroupDirectory(context, parentID);
// Attempt to get root connection group directory
Directory<String, ConnectionGroup> directory =
context.getRootConnectionGroup().getConnectionGroupDirectory();
// Move connection group
directory.move(identifier, newParentDirectory);
}
}

View File

@@ -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<String, ConnectionGroup> 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);
}
}

View File

@@ -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<String, Connection> findConnectionDirectory(
UserContext context, String parentID) throws GuacamoleException {
// Find the correct connection directory
ConnectionGroup rootGroup = context.getRootConnectionGroup();
Directory<String, Connection> directory;
Directory<String, ConnectionGroup> connectionGroupDirectory =
rootGroup.getConnectionGroupDirectory();
ConnectionGroup parentGroup = connectionGroupDirectory.get(parentID);
if(parentGroup == null)
return null;
directory = parentGroup.getConnectionDirectory();
return directory;
}
}

View File

@@ -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<String, Connection> 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<String> 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);
}
}

View File

@@ -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<ConnectionRecord> getHistory() throws GuacamoleException {
throw new UnsupportedOperationException("History unsupported in DummyConnection.");
}
}

View File

@@ -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<String, ConnectionGroup> directory)
throws GuacamoleException, XMLStreamException {
// If no connections, write nothing
Set<String> 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<String, Connection> directory)
throws GuacamoleException, XMLStreamException {
// If no connections, write nothing
Set<String> 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);
}
}
}

View File

@@ -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<String, Connection> newParentDirectory =
ConnectionUtility.findConnectionDirectory(context, parentID);
// Attempt to get root connection directory
Directory<String, Connection> directory =
context.getRootConnectionGroup().getConnectionDirectory();
// Move connection
directory.move(identifier, newParentDirectory);
}
}

View File

@@ -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<String, Connection> directory =
context.getRootConnectionGroup().getConnectionDirectory();
// Create config
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(protocol);
// Load parameters into config
Enumeration<String> 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);
}
}

View File

@@ -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<String, User> 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);
}
}
}

View File

@@ -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<String, User> directory =
context.getUserDirectory();
// Create user skeleton
User user = new DummyUser();
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString());
// Add user
directory.add(user);
}
}

View File

@@ -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<Permission> permissions = new HashSet<Permission>();
@Override
public Set<Permission> 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);
}
}

View File

@@ -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<String, User> directory = context.getUserDirectory();
// Get users
Set<String> 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);
}
}
}

View File

@@ -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<String, User> 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);
}
}

View File

@@ -26,32 +26,26 @@ import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter; import ch.qos.logback.core.util.StatusPrinter;
import com.google.inject.AbstractModule;
import java.io.File; import java.io.File;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.glyptodon.guacamole.properties.GuacamoleHome; import org.glyptodon.guacamole.properties.GuacamoleHome;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Initializes the logback logging subsystem, used behind SLF4J within * Initializes the logging subsystem.
* Guacamole for all logging.
* *
* @author Michael Jumper * @author Michael Jumper
*/ */
public class LogbackInitializer implements ServletContextListener { public class LogModule extends AbstractModule {
/** /**
* Logger for this class. * Logger for this class.
*/ */
private final Logger logger = LoggerFactory.getLogger(LogbackInitializer.class); private final Logger logger = LoggerFactory.getLogger(LogModule.class);
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { protected void configure() {
}
@Override
public void contextInitialized(ServletContextEvent sce) {
// Only load logback configuration if GUACAMOLE_HOME exists // Only load logback configuration if GUACAMOLE_HOME exists
File guacamoleHome = GuacamoleHome.getDirectory(); File guacamoleHome = GuacamoleHome.getDirectory();

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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 <T> The type of object being patched.
*/
public class APIPatch<T> {
/**
* 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;
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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 {}

View File

@@ -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());
}
}

View File

@@ -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 {}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -20,32 +20,50 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic.rest.auth;
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;
/** /**
* Simple dummy AuthenticatingHttpServlet which provides an endpoint for arbitrary * A simple object to represent an auth token/userID pair in the API.
* authentication requests that do not expect a response. *
* * @author James Muehlner
* @author Michael Jumper
*/ */
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); public String getAuthToken() {
return authToken;
@Override }
protected void restrictedService(
UserContext context, /**
HttpServletRequest request, HttpServletResponse response) { * Get the user ID.
logger.debug("Login was successful for user \"{}\".", context.self().getUsername()); * @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;
} }
} }

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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<String, Long> lastAccessTimeMap = new HashMap<String, Long>();
/**
* Keeps track of the authToken to GuacamoleSession mapping.
*/
private final Map<String, GuacamoleSession> sessionMap = new HashMap<String, GuacamoleSession>();
/**
* 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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;

View File

@@ -20,26 +20,36 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic.rest.clipboard;
import java.io.IOException; import com.google.inject.Inject;
import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET;
import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Path;
import javax.servlet.http.HttpSession; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleServerException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException; 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.BooleanGuacamoleProperty;
import org.glyptodon.guacamole.properties.GuacamoleProperties; 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 * @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. * The amount of time to wait for clipboard changes, in milliseconds.
*/ */
@@ -55,30 +65,23 @@ public class CaptureClipboard extends RestrictedHttpServlet {
}; };
@GET
@Override @AuthProviderRESTExposure
protected void restrictedService( public Response getClipboard(@QueryParam("token") String authToken)
UserContext context,
HttpServletRequest request, HttpServletResponse response)
throws GuacamoleException { throws GuacamoleException {
// Only bother if actually enabled // Only bother if actually enabled
if (GuacamoleProperties.getProperty(INTEGRATION_ENABLED, false)) { if (GuacamoleProperties.getProperty(INTEGRATION_ENABLED, false)) {
// Get clipboard // Get clipboard
final HttpSession session = request.getSession(true); GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
final ClipboardState clipboard = AuthenticatingFilter.getClipboardState(session); final ClipboardState clipboard = session.getClipboardState();
// Send clipboard contents // Send clipboard contents
try { synchronized (clipboard) {
synchronized (clipboard) { clipboard.waitForContents(CLIPBOARD_TIMEOUT);
clipboard.waitForContents(CLIPBOARD_TIMEOUT); return Response.ok(clipboard.getContents(),
response.setContentType(clipboard.getMimetype()); clipboard.getMimetype()).build();
response.getOutputStream().write(clipboard.getContents());
}
}
catch (IOException e) {
throw new GuacamoleServerException("Unable to send clipboard contents", e);
} }
} }

View File

@@ -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<? extends ConnectionRecord> history;
/**
* Map of all associated parameter values, indexed by parameter name.
*/
private Map<String, String> parameters = new HashMap<String, String>();
/**
* 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<? extends ConnectionRecord> getHistory() {
return history;
}
/**
* Returns the parameter map for this connection.
* @return The parameter map for this connection.
*/
public Map<String, String> getParameters() {
return parameters;
}
/**
* Sets the parameter map for this connection.
* @param parameters The parameter map for this connection.
*/
public void setParameters(Map<String, String> 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;
}
}

View File

@@ -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<String, String> parameters = apiConnection.getParameters();
for(Map.Entry<String, String> 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<String, String> 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<? extends ConnectionRecord> getHistory() throws GuacamoleException {
return apiConnection.getHistory();
}
}

View File

@@ -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<APIConnection> 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<String, ConnectionGroup> connectionGroupDirectory = rootGroup.getConnectionGroupDirectory();
parentConnectionGroup = connectionGroupDirectory.get(parentID);
}
if (parentConnectionGroup == null)
throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID.");
Directory<String, Connection> 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<String, Connection> 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<String, Connection> 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<String, ConnectionGroup> connectionGroupDirectory = rootGroup.getConnectionGroupDirectory();
parentConnectionGroup = connectionGroupDirectory.get(parentID);
}
if (parentConnectionGroup == null)
throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID.");
Directory<String, Connection> 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<String, Connection> 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<String, Connection> connectionDirectory =
rootGroup.getConnectionDirectory();
// Find the new parent connection group
Directory<String, ConnectionGroup> 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());
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -20,40 +20,40 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.glyptodon.guacamole.net.basic.crud.connections; package org.glyptodon.guacamole.net.basic.rest.connection;
import javax.servlet.http.HttpServletRequest; import java.util.ArrayList;
import javax.servlet.http.HttpServletResponse; import java.util.List;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.Directory; 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. * A service for performing useful manipulations on REST Connections.
* *
* @author Michael Jumper * @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<APIConnection> convertConnectionList(Directory<String, Connection> connectionDirectory)
throws GuacamoleException {
@Override List<APIConnection> restConnections = new ArrayList<APIConnection>();
protected void restrictedService(
UserContext context, for (String connectionID : connectionDirectory.getIdentifiers())
HttpServletRequest request, HttpServletResponse response) restConnections.add(new APIConnection(connectionDirectory.get(connectionID)));
throws GuacamoleException {
return restConnections;
// Get ID
String identifier = request.getParameter("id");
// Attempt to get connection directory
Directory<String, Connection> directory =
context.getRootConnectionGroup().getConnectionDirectory();
// Remove connection
directory.remove(identifier);
} }
} }

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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;

View File

@@ -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;
}
}

View File

@@ -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<String, Connection> getConnectionDirectory() throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
}
@Override
public Directory<String, ConnectionGroup> getConnectionGroupDirectory() throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
}
@Override
public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
}
}

View File

@@ -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<APIConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> connectionGroupDirectory = rootGroup.getConnectionGroupDirectory();
parentConnectionGroup = connectionGroupDirectory.get(parentID);
}
if (parentConnectionGroup == null)
throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID.");
Directory<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> connectionGroupDirectory =
rootGroup.getConnectionGroupDirectory();
// Find the new parent connection group
Directory<String, ConnectionGroup> 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());
}
}

View File

@@ -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<APIConnectionGroup> convertConnectionGroupList(
Directory<String, ConnectionGroup> connectionGroupDirectory) throws GuacamoleException {
List<APIConnectionGroup> restConnectionGroups = new ArrayList<APIConnectionGroup>();
for (String connectionGroupID : connectionGroupDirectory.getIdentifiers())
restConnectionGroups.add(new APIConnectionGroup(connectionGroupDirectory.get(connectionGroupID)));
return restConnectionGroups;
}
}

View File

@@ -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;

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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;

View File

@@ -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;
}
}
}

View File

@@ -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<APIPermission> 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<APIPatch<APIPermission>> patches) throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
// Get the user directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
// All users who have had permissions added or removed
Map<String, User> modifiedUsers = new HashMap<String, User>();
for (APIPatch<APIPermission> 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);
}
}

View File

@@ -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<APIPermission> convertPermissionList(Iterable<? extends Permission> permissions) {
List<APIPermission> restPermissions = new ArrayList<APIPermission>();
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<Permission> convertAPIPermissionList(Iterable<APIPermission> restPermissions) {
Set<Permission> permissions = new HashSet<Permission>();
for(APIPermission restPermission : restPermissions)
permissions.add(restPermission.toPermission());
return permissions;
}
}

View File

@@ -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;

View File

@@ -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<String, ProtocolInfo> getProtocols() throws GuacamoleException {
// Get and return a map of all protocols.
return protocolRetrievalservice.getProtocolMap();
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,7 @@
* THE SOFTWARE. * 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.BufferedInputStream;
import java.io.File; import java.io.File;
@@ -30,18 +30,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.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.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.DocumentHandler;
import org.glyptodon.guacamole.net.basic.xml.protocol.ProtocolTagHandler; import org.glyptodon.guacamole.net.basic.xml.protocol.ProtocolTagHandler;
import org.glyptodon.guacamole.properties.GuacamoleHome; import org.glyptodon.guacamole.properties.GuacamoleHome;
@@ -53,18 +43,18 @@ import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory; import org.xml.sax.helpers.XMLReaderFactory;
/** /**
* Simple HttpServlet which outputs XML containing a list of all visible * A service for retrieving protocol definitions from the
* protocols. * XML files storing the information.
* *
* @author Michael Jumper * @author James Muehlner
*/ */
public class List extends RestrictedHttpServlet { public class ProtocolRetrievalService {
/** /**
* Logger for this class. * 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. * Array of all known protocol names.
*/ */
@@ -108,110 +98,22 @@ public class List extends RestrictedHttpServlet {
} }
catch (IOException e) { catch (IOException e) {
throw new GuacamoleException("Error reading basic user mapping file.", e); throw new GuacamoleException(e);
} }
catch (SAXException 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 * Retrieves a map of protocol name to ProtocolInfo for all protocols defined
* out the corresponding protocol XML describing all available parameters. * in the XML files.
* *
* @param xml The XMLStreamWriter to use to write the XML. * @return A map of all defined protocols.
* @param protocol The ProtocolInfo object to read parameters and protocol * @throws GuacamoleException If an error occurs while retrieving the protocols.
* information from.
* @throws XMLStreamException If an error occurs while writing the XML.
*/ */
private void writeProtocol(XMLStreamWriter xml, ProtocolInfo protocol) public Map<String, ProtocolInfo> getProtocolMap() throws GuacamoleException {
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");
// Map of all available protocols // Map of all available protocols
Map<String, ProtocolInfo> protocols = new HashMap<String, ProtocolInfo>(); Map<String, ProtocolInfo> protocols = new HashMap<String, ProtocolInfo>();
@@ -225,20 +127,16 @@ public class List extends RestrictedHttpServlet {
// Get all XML files // Get all XML files
File[] files = protocol_directory.listFiles( File[] files = protocol_directory.listFiles(
new FilenameFilter() { new FilenameFilter() {
@Override @Override
public boolean accept(File file, String string) { public boolean accept(File file, String string) {
return string.endsWith(".xml"); return string.endsWith(".xml");
} }
} }
); );
// Load each protocol from each file // Load each protocol from each file
for (File file : files) { for (File file : files) {
try { try {
// Parse protocol // Parse protocol
FileInputStream stream = new FileInputStream(file); FileInputStream stream = new FileInputStream(file);
ProtocolInfo protocol = getProtocol(stream); 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.error("Unable to read connection parameter information from \"{}\": {}", file.getAbsolutePath(), e.getMessage());
logger.debug("Error reading protocol XML.", e); logger.debug("Error reading protocol XML.", e);
} }
} }
} }
// If known protocols are not already defined, read from classpath // 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 protocol not defined yet, attempt to load from classpath
if (!protocols.containsKey(protocol)) { if (!protocols.containsKey(protocol)) {
InputStream stream = ProtocolRetrievalService.class.getResourceAsStream(
InputStream stream = List.class.getResourceAsStream(
"/net/sourceforge/guacamole/net/protocols/" "/net/sourceforge/guacamole/net/protocols/"
+ protocol + ".xml"); + protocol + ".xml");
@@ -272,39 +167,9 @@ public class List extends RestrictedHttpServlet {
protocols.put(protocol, getProtocol(stream)); protocols.put(protocol, getProtocol(stream));
} }
} }
// Write actual XML return protocols;
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);
}
} }
} }

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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;

View File

@@ -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;
}
}

View File

@@ -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<Permission> 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<Permission> 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<Permission> 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.");
}
}

View File

@@ -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<APIUser> getUsers(@QueryParam("token") String authToken) throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
// Get the directory
Directory<String, User> 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<String, User> 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<String, User> 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<String, User> 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<String, User> 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);
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -20,39 +20,40 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.glyptodon.guacamole.net.basic.crud.users; package org.glyptodon.guacamole.net.basic.rest.user;
import javax.servlet.http.HttpServletRequest; import java.util.ArrayList;
import javax.servlet.http.HttpServletResponse; import java.util.List;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.User; 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. * A service for performing useful manipulations on REST Users.
* *
* @author Michael Jumper * @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<APIUser> convertUserList(Directory<String, User> userDirectory)
throws GuacamoleException {
@Override List<APIUser> restUsers = new ArrayList<APIUser>();
protected void restrictedService(
UserContext context, for(String username : userDirectory.getIdentifiers())
HttpServletRequest request, HttpServletResponse response) restUsers.add(new APIUser(userDirectory.get(username)));
throws GuacamoleException {
return restUsers;
// Get username
String username = request.getParameter("name");
// Attempt to get user directory
Directory<String, User> directory = context.getUserDirectory();
// Remove user
directory.remove(username);
} }
} }

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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;

View File

@@ -22,25 +22,22 @@
package org.glyptodon.guacamole.net.basic.websocket; package org.glyptodon.guacamole.net.basic.websocket;
import com.google.inject.Provider;
import java.util.Map; import java.util.Map;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
import javax.websocket.HandshakeResponse; import javax.websocket.HandshakeResponse;
import javax.websocket.Session; import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest; import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel; 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; import org.glyptodon.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
/** /**
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than * Tunnel implementation which uses WebSocket as a tunnel backend, rather than
* HTTP, properly parsing connection IDs included in the connection request. * 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 { public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
/** /**
@@ -62,6 +59,25 @@ public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTun
*/ */
public static class Configurator extends ServerEndpointConfig.Configurator { public static class Configurator extends ServerEndpointConfig.Configurator {
/**
* Provider which provides instances of a service for handling
* tunnel requests.
*/
private final Provider<TunnelRequestService> 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<TunnelRequestService> tunnelRequestServiceProvider) {
this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
}
@Override @Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
@@ -72,8 +88,11 @@ public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTun
userProperties.clear(); userProperties.clear();
try { try {
// Get tunnel request service
TunnelRequestService tunnelRequestService = tunnelRequestServiceProvider.get();
// Store new tunnel within user properties // Store new tunnel within user properties
GuacamoleTunnel tunnel = BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(request)); GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new WebSocketTunnelRequest(request));
if (tunnel != null) if (tunnel != null)
userProperties.put(TUNNEL_USER_PROPERTY, tunnel); userProperties.put(TUNNEL_USER_PROPERTY, tunnel);

View File

@@ -22,10 +22,12 @@
package org.glyptodon.guacamole.net.basic.websocket.jetty8; package org.glyptodon.guacamole.net.basic.websocket.jetty8;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel; 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; 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 * rather than HTTP, properly parsing connection IDs included in the connection
* request. * request.
*/ */
@Singleton
public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override @Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException { throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); return tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
} }
} }

View File

@@ -22,10 +22,12 @@
package org.glyptodon.guacamole.net.basic.websocket.jetty9; 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.eclipse.jetty.websocket.api.Session;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel; 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 * WebSocket listener implementation which properly parses connection IDs
@@ -33,11 +35,18 @@ import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility;
* *
* @author Michael Jumper * @author Michael Jumper
*/ */
@Singleton
public class BasicGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener { public class BasicGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override @Override
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException { protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest())); return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
} }
} }

View File

@@ -22,10 +22,12 @@
package org.glyptodon.guacamole.net.basic.websocket.tomcat; package org.glyptodon.guacamole.net.basic.websocket.tomcat;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel; 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; 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 * rather than HTTP, properly parsing connection IDs included in the connection
* request. * request.
*/ */
@Singleton
public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override @Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException { throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); return tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
}; };
} }

View File

@@ -20,286 +20,35 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
--> -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Basic config --> <!-- Basic config -->
<welcome-file-list> <welcome-file-list>
<welcome-file>index.xhtml</welcome-file> <welcome-file>index.xhtml</welcome-file>
</welcome-file-list> </welcome-file-list>
<session-config> <session-config>
<session-timeout> <session-timeout>30</session-timeout>
30
</session-timeout>
</session-config> </session-config>
<!-- Automatically detect and load WebSocket support --> <!-- Guice -->
<listener>
<listener-class>org.glyptodon.guacamole.net.basic.websocket.WebSocketSupportLoader</listener-class>
</listener>
<!-- Init logging on startup -->
<listener>
<listener-class>org.glyptodon.guacamole.net.basic.log.LogbackInitializer</listener-class>
</listener>
<!-- Authenticate against service calls and pages -->
<filter> <filter>
<filter-name>AuthenticatingFilter</filter-name> <filter-name>guiceFilter</filter-name>
<filter-class>org.glyptodon.guacamole.net.basic.AuthenticatingFilter</filter-class> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter> </filter>
<filter-mapping> <filter-mapping>
<filter-name>AuthenticatingFilter</filter-name> <filter-name>guiceFilter</filter-name>
<url-pattern>*.xhtml</url-pattern> <url-pattern>/*</url-pattern>
<url-pattern>/login</url-pattern>
<url-pattern>/clipboard</url-pattern>
<url-pattern>/connections/*</url-pattern>
<url-pattern>/connectiongroups/*</url-pattern>
<url-pattern>/users/*</url-pattern>
<url-pattern>/permissions/*</url-pattern>
<url-pattern>/protocols/*</url-pattern>
</filter-mapping> </filter-mapping>
<!-- Restrict requests to the WebSocket tunnel --> <listener>
<filter> <listener-class>org.glyptodon.guacamole.net.basic.BasicServletContextListener</listener-class>
<filter-name>RestrictedFilter</filter-name> </listener>
<filter-class>org.glyptodon.guacamole.net.basic.RestrictedFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RestrictedFilter</filter-name>
<url-pattern>/websocket-tunnel</url-pattern>
</filter-mapping>
<!-- Basic Login Servlet --> <!-- Audio file mimetype mappings -->
<servlet>
<description>Login servlet.</description>
<servlet-name>Login</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.BasicLogin</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<!-- Basic Logout Servlet -->
<servlet>
<description>Logout servlet.</description>
<servlet-name>Logout</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.BasicLogout</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<!-- Session Keep-Alive Servlet -->
<servlet>
<description>Session keep-alive servlet.</description>
<servlet-name>SessionKeepAlive</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.SessionKeepAlive</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SessionKeepAlive</servlet-name>
<url-pattern>/keep-alive</url-pattern>
</servlet-mapping>
<!-- Clipboard State Servlet -->
<servlet>
<description>Clipboard state servlet.</description>
<servlet-name>Clipboard</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.CaptureClipboard</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Clipboard</servlet-name>
<url-pattern>/clipboard</url-pattern>
</servlet-mapping>
<!-- Connection Creation Servlet -->
<servlet>
<description>Connection creation servlet.</description>
<servlet-name>ConnectionCreate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connections.Create</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionCreate</servlet-name>
<url-pattern>/connections/create</url-pattern>
</servlet-mapping>
<!-- Connection List Servlet -->
<servlet>
<description>Connection list servlet.</description>
<servlet-name>Connections</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connections.List</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Connections</servlet-name>
<url-pattern>/connections</url-pattern>
</servlet-mapping>
<!-- Connection Update Servlet -->
<servlet>
<description>Connection update servlet.</description>
<servlet-name>ConnectionUpdate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connections.Update</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionUpdate</servlet-name>
<url-pattern>/connections/update</url-pattern>
</servlet-mapping>
<!-- Connection Move Servlet -->
<servlet>
<description>Connection move servlet.</description>
<servlet-name>ConnectionMove</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connections.Move</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionMove</servlet-name>
<url-pattern>/connections/move</url-pattern>
</servlet-mapping>
<!-- Connection Deletion Servlet -->
<servlet>
<description>Connection deletion servlet.</description>
<servlet-name>ConnectionDelete</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connections.Delete</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionDelete</servlet-name>
<url-pattern>/connections/delete</url-pattern>
</servlet-mapping>
<!-- Connection Group Creation Servlet -->
<servlet>
<description>ConnectionGroup creation servlet.</description>
<servlet-name>ConnectionGroupCreate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connectiongroups.Create</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionGroupCreate</servlet-name>
<url-pattern>/connectiongroups/create</url-pattern>
</servlet-mapping>
<!-- Connection Group List Servlet -->
<servlet>
<description>ConnectionGroup list servlet.</description>
<servlet-name>ConnectionGroups</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connectiongroups.List</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionGroups</servlet-name>
<url-pattern>/connectiongroups</url-pattern>
</servlet-mapping>
<!-- Connection Group Update Servlet -->
<servlet>
<description>ConnectionGroup update servlet.</description>
<servlet-name>ConnectionGroupUpdate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connectiongroups.Update</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionGroupUpdate</servlet-name>
<url-pattern>/connectiongroups/update</url-pattern>
</servlet-mapping>
<!-- Connection Group Move Servlet -->
<servlet>
<description>ConnectionGroup move servlet.</description>
<servlet-name>ConnectionGroupMove</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connectiongroups.Move</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionGroupMove</servlet-name>
<url-pattern>/connectiongroups/move</url-pattern>
</servlet-mapping>
<!-- Connection Group Deletion Servlet -->
<servlet>
<description>ConnectionGroup deletion servlet.</description>
<servlet-name>ConnectionGroupDelete</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.connectiongroups.Delete</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectionGroupDelete</servlet-name>
<url-pattern>/connectiongroups/delete</url-pattern>
</servlet-mapping>
<!-- User Creation Servlet -->
<servlet>
<description>User creation servlet.</description>
<servlet-name>UserCreate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.users.Create</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserCreate</servlet-name>
<url-pattern>/users/create</url-pattern>
</servlet-mapping>
<!-- User List Servlet -->
<servlet>
<description>User list servlet.</description>
<servlet-name>Users</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.users.List</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Users</servlet-name>
<url-pattern>/users</url-pattern>
</servlet-mapping>
<!-- User Update Servlet -->
<servlet>
<description>User update servlet.</description>
<servlet-name>UserUpdate</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.users.Update</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserUpdate</servlet-name>
<url-pattern>/users/update</url-pattern>
</servlet-mapping>
<!-- User Deletion Servlet -->
<servlet>
<description>User deletion servlet.</description>
<servlet-name>UserDelete</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.users.Delete</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserDelete</servlet-name>
<url-pattern>/users/delete</url-pattern>
</servlet-mapping>
<!-- Permission List Servlet -->
<servlet>
<description>Permission list servlet.</description>
<servlet-name>Permissions</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.permissions.List</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Permissions</servlet-name>
<url-pattern>/permissions</url-pattern>
</servlet-mapping>
<!-- Protocol List Servlet -->
<servlet>
<description>Protocol list servlet.</description>
<servlet-name>Protocols</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.crud.protocols.List</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Protocols</servlet-name>
<url-pattern>/protocols</url-pattern>
</servlet-mapping>
<!-- Guacamole Tunnel Servlet -->
<servlet>
<description>Tunnel servlet.</description>
<servlet-name>Tunnel</servlet-name>
<servlet-class>org.glyptodon.guacamole.net.basic.BasicGuacamoleTunnelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Tunnel</servlet-name>
<url-pattern>/tunnel</url-pattern>
</servlet-mapping>
<mime-mapping> <mime-mapping>
<extension>mp3</extension> <extension>mp3</extension>
<mime-type>audio/mpeg</mime-type> <mime-type>audio/mpeg</mime-type>