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

View File

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

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

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

View File

@@ -20,36 +20,31 @@
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic.websocket;
package org.glyptodon.guacamole.net.basic;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.google.inject.Provider;
import com.google.inject.servlet.ServletModule;
import java.util.Arrays;
import javax.servlet.http.HttpServlet;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.net.basic.websocket.BasicGuacamoleWebSocketTunnelEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple ServletContextListener which loads a WebSocket tunnel implementation
* if available, using the Servlet 3.0 API to dynamically load and install
* the tunnel servlet.
*
* Note that because Guacamole depends on the Servlet 2.5 API, and 3.0 may
* not be available or needed if WebSocket is not desired, the 3.0 API is
* detected and invoked dynamically via reflection.
* Module which loads tunnel implementations.
*
* @author Michael Jumper
*/
public class WebSocketSupportLoader implements ServletContextListener {
public class TunnelModule extends ServletModule {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(WebSocketSupportLoader.class);
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
/**
* Classnames of all legacy (non-JSR) WebSocket tunnel implementations.
@@ -94,52 +89,17 @@ public class WebSocketSupportLoader implements ServletContextListener {
}
private boolean loadWebSocketTunnel(ServletContext context, String classname) {
private boolean loadWebSocketTunnel(String classname) {
try {
// Attempt to find WebSocket servlet
Class<Servlet> servlet = (Class<Servlet>)
Class<HttpServlet> servlet = (Class<HttpServlet>)
GuacamoleClassLoader.getInstance().findClass(classname);
// Dynamically add servlet IF SERVLET 3.0 API AVAILABLE!
try {
// Get servlet registration class
Class regClass = Class.forName("javax.servlet.ServletRegistration");
// Get and invoke addServlet()
Method addServlet = ServletContext.class.getMethod("addServlet",
String.class, Class.class);
Object reg = addServlet.invoke(context, "WebSocketTunnel", servlet);
// Get and invoke addMapping()
Method addMapping = regClass.getMethod("addMapping", String[].class);
addMapping.invoke(reg, (Object) new String[]{"/websocket-tunnel"});
// If we succesfully load and register the WebSocket tunnel servlet,
// WebSocket is supported.
return true;
}
// Servlet API 3.0 unsupported
catch (ClassNotFoundException e) {
logger.info("Servlet API 3.0 not found.", e);
}
catch (NoSuchMethodException e) {
logger.warn("Servlet API 3.0 found, but incomplete.", e);
}
// Servlet API 3.0 found, but errors during use
catch (IllegalAccessException e) {
logger.error("Unable to load WebSocket tunnel servlet: {}", e.getMessage());
logger.debug("Error loading WebSocket tunnel servlet.", e);
}
catch (InvocationTargetException e) {
logger.error("Unable to load WebSocket tunnel servlet: {}", e.getMessage());
logger.debug("Error loading WebSocket tunnel servlet.", e);
}
// Add WebSocket servlet
serve("/websocket-tunnel").with(servlet);
return true;
}
@@ -160,21 +120,50 @@ public class WebSocketSupportLoader implements ServletContextListener {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
protected void configureServlets() {
@Override
public void contextInitialized(ServletContextEvent sce) {
bind(TunnelRequestService.class);
// Set up HTTP tunnel
serve("/tunnel").with(BasicGuacamoleTunnelServlet.class);
// Check for JSR 356 support
if (implementsJSR_356()) {
logger.info("JSR-356 WebSocket support present.");
// Get container
ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer");
if (container == null) {
logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support.");
return;
}
Provider<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;
}
// Try to load each WebSocket tunnel in sequence
for (String classname : WEBSOCKET_CLASSES) {
if (loadWebSocketTunnel(sce.getServletContext(), classname)) {
if (loadWebSocketTunnel(classname)) {
logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname);
return;
}

View File

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

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

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
* of this software and associated documentation files (the "Software"), to deal
@@ -20,32 +20,50 @@
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
package org.glyptodon.guacamole.net.basic.rest.auth;
/**
* Simple dummy AuthenticatingHttpServlet which provides an endpoint for arbitrary
* authentication requests that do not expect a response.
*
* @author Michael Jumper
* A simple object to represent an auth token/userID pair in the API.
*
* @author James Muehlner
*/
public class BasicLogin extends RestrictedHttpServlet {
public class APIAuthToken {
/**
* The auth token.
*/
private final String authToken;
/**
* The user ID.
*/
private final String userID;
/**
* Logger for this class.
* Get the auth token.
* @return The auth token.
*/
private final Logger logger = LoggerFactory.getLogger(BasicLogin.class);
@Override
protected void restrictedService(
UserContext context,
HttpServletRequest request, HttpServletResponse response) {
logger.debug("Login was successful for user \"{}\".", context.self().getUsername());
public String getAuthToken() {
return authToken;
}
/**
* Get the user ID.
* @return The user ID.
*/
public String getUserID() {
return userID;
}
/**
* Create a new APIAuthToken Object with the given auth token.
*
* @param authToken The auth token to create the new APIAuthToken with.
* @param userID The ID of the user owning the given token.
*/
public APIAuthToken(String authToken, String userID) {
this.authToken = authToken;
this.userID = userID;
}
}

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

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
* of this software and associated documentation files (the "Software"), to deal
@@ -20,40 +20,40 @@
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic.crud.connections;
package org.glyptodon.guacamole.net.basic.rest.connection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet;
/**
* Simple HttpServlet which handles connection deletion.
*
* @author Michael Jumper
* A service for performing useful manipulations on REST Connections.
*
* @author James Muehlner
*/
public class Delete extends RestrictedHttpServlet {
public class ConnectionService {
/**
* Converts a Connection Directory to a list of APIConnection objects for
* exposing with the REST endpoints.
*
* @param connectionDirectory The Connection Directory to convert for REST endpoint use.
* @return A List of APIConnection objects for use with the REST endpoint.
* @throws GuacamoleException If an error occurs while converting the
* connection directory.
*/
public List<APIConnection> convertConnectionList(Directory<String, Connection> connectionDirectory)
throws GuacamoleException {
@Override
protected void restrictedService(
UserContext context,
HttpServletRequest request, HttpServletResponse response)
throws GuacamoleException {
// Get ID
String identifier = request.getParameter("id");
// Attempt to get connection directory
Directory<String, Connection> directory =
context.getRootConnectionGroup().getConnectionDirectory();
// Remove connection
directory.remove(identifier);
List<APIConnection> restConnections = new ArrayList<APIConnection>();
for (String connectionID : connectionDirectory.getIdentifiers())
restConnections.add(new APIConnection(connectionDirectory.get(connectionID)));
return restConnections;
}
}

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
* 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
* 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
* of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,7 @@
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic.crud.protocols;
package org.glyptodon.guacamole.net.basic.rest.protocol;
import java.io.BufferedInputStream;
import java.io.File;
@@ -30,18 +30,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleServerException;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet;
import org.glyptodon.guacamole.net.basic.ProtocolInfo;
import org.glyptodon.guacamole.net.basic.ProtocolParameter;
import org.glyptodon.guacamole.net.basic.ProtocolParameterOption;
import org.glyptodon.guacamole.net.basic.xml.DocumentHandler;
import org.glyptodon.guacamole.net.basic.xml.protocol.ProtocolTagHandler;
import org.glyptodon.guacamole.properties.GuacamoleHome;
@@ -53,18 +43,18 @@ import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* Simple HttpServlet which outputs XML containing a list of all visible
* protocols.
*
* @author Michael Jumper
* A service for retrieving protocol definitions from the
* XML files storing the information.
*
* @author James Muehlner
*/
public class List extends RestrictedHttpServlet {
public class ProtocolRetrievalService {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(List.class);
private static final Logger logger = LoggerFactory.getLogger(ProtocolRetrievalService.class);
/**
* Array of all known protocol names.
*/
@@ -108,110 +98,22 @@ public class List extends RestrictedHttpServlet {
}
catch (IOException e) {
throw new GuacamoleException("Error reading basic user mapping file.", e);
throw new GuacamoleException(e);
}
catch (SAXException e) {
throw new GuacamoleException("Error parsing basic user mapping XML.", e);
throw new GuacamoleException(e);
}
}
/**
* Given an XML stream and a fully-populated ProtocolInfo object, writes
* out the corresponding protocol XML describing all available parameters.
* Retrieves a map of protocol name to ProtocolInfo for all protocols defined
* in the XML files.
*
* @param xml The XMLStreamWriter to use to write the XML.
* @param protocol The ProtocolInfo object to read parameters and protocol
* information from.
* @throws XMLStreamException If an error occurs while writing the XML.
* @return A map of all defined protocols.
* @throws GuacamoleException If an error occurs while retrieving the protocols.
*/
private void writeProtocol(XMLStreamWriter xml, ProtocolInfo protocol)
throws XMLStreamException {
// Write protocol
xml.writeStartElement("protocol");
xml.writeAttribute("name", protocol.getName());
xml.writeAttribute("title", protocol.getTitle());
// Write parameters
for (ProtocolParameter param : protocol.getParameters()) {
// Write param tag
xml.writeStartElement("param");
xml.writeAttribute("name", param.getName());
xml.writeAttribute("title", param.getTitle());
// Write type
switch (param.getType()) {
// Text parameter
case TEXT:
xml.writeAttribute("type", "text");
break;
// Password parameter
case PASSWORD:
xml.writeAttribute("type", "password");
break;
// Numeric parameter
case NUMERIC:
xml.writeAttribute("type", "numeric");
break;
// Boolean parameter
case BOOLEAN:
xml.writeAttribute("type", "boolean");
xml.writeAttribute("value", param.getValue());
break;
// Enumerated parameter
case ENUM:
xml.writeAttribute("type", "enum");
break;
// Multiline parameter
case MULTILINE:
xml.writeAttribute("type", "multiline");
break;
// If unknown, fail explicitly
default:
throw new UnsupportedOperationException(
"Parameter type not supported: " + param.getType());
}
// Write options
for (ProtocolParameterOption option : param.getOptions()) {
xml.writeStartElement("option");
xml.writeAttribute("value", option.getValue());
xml.writeCharacters(option.getTitle());
xml.writeEndElement();
}
// End parameter
xml.writeEndElement();
}
// End protocol
xml.writeEndElement();
}
@Override
protected void restrictedService(
UserContext context,
HttpServletRequest request, HttpServletResponse response)
throws GuacamoleException {
// Do not cache
response.setHeader("Cache-Control", "no-cache");
// Set encoding
response.setCharacterEncoding("UTF-8");
public Map<String, ProtocolInfo> getProtocolMap() throws GuacamoleException {
// Map of all available protocols
Map<String, ProtocolInfo> protocols = new HashMap<String, ProtocolInfo>();
@@ -225,20 +127,16 @@ public class List extends RestrictedHttpServlet {
// Get all XML files
File[] files = protocol_directory.listFiles(
new FilenameFilter() {
@Override
public boolean accept(File file, String string) {
return string.endsWith(".xml");
}
}
);
// Load each protocol from each file
for (File file : files) {
try {
// Parse protocol
FileInputStream stream = new FileInputStream(file);
ProtocolInfo protocol = getProtocol(stream);
@@ -252,9 +150,7 @@ public class List extends RestrictedHttpServlet {
logger.error("Unable to read connection parameter information from \"{}\": {}", file.getAbsolutePath(), e.getMessage());
logger.debug("Error reading protocol XML.", e);
}
}
}
// If known protocols are not already defined, read from classpath
@@ -262,8 +158,7 @@ public class List extends RestrictedHttpServlet {
// If protocol not defined yet, attempt to load from classpath
if (!protocols.containsKey(protocol)) {
InputStream stream = List.class.getResourceAsStream(
InputStream stream = ProtocolRetrievalService.class.getResourceAsStream(
"/net/sourceforge/guacamole/net/protocols/"
+ protocol + ".xml");
@@ -272,39 +167,9 @@ public class List extends RestrictedHttpServlet {
protocols.put(protocol, getProtocol(stream));
}
}
// Write actual XML
try {
// Write XML content type
response.setHeader("Content-Type", "text/xml");
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter());
// Begin document
xml.writeStartDocument();
xml.writeStartElement("protocols");
// Write all protocols
for (ProtocolInfo protocol : protocols.values())
writeProtocol(xml, protocol);
// End document
xml.writeEndElement();
xml.writeEndDocument();
}
catch (XMLStreamException e) {
throw new GuacamoleServerException(
"Unable to write protocol list XML.", e);
}
catch (IOException e) {
throw new GuacamoleServerException(
"I/O error writing protocol list XML.", e);
}
return protocols;
}
}

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
* 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
* of this software and associated documentation files (the "Software"), to deal
@@ -20,39 +20,40 @@
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic.crud.users;
package org.glyptodon.guacamole.net.basic.rest.user;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet;
/**
* Simple HttpServlet which handles user deletion.
*
* @author Michael Jumper
* A service for performing useful manipulations on REST Users.
*
* @author James Muehlner
*/
public class Delete extends RestrictedHttpServlet {
public class UserService {
/**
* Converts a user directory to a list of APIUser objects for
* exposing with the REST endpoints.
*
* @param userDirectory The user directory to convert for REST endpoint use.
* @return A List of APIUser objects for use with the REST endpoint.
* @throws GuacamoleException If an error occurs while converting the
* user directory.
*/
public List<APIUser> convertUserList(Directory<String, User> userDirectory)
throws GuacamoleException {
@Override
protected void restrictedService(
UserContext context,
HttpServletRequest request, HttpServletResponse response)
throws GuacamoleException {
// Get username
String username = request.getParameter("name");
// Attempt to get user directory
Directory<String, User> directory = context.getUserDirectory();
// Remove user
directory.remove(username);
List<APIUser> restUsers = new ArrayList<APIUser>();
for(String username : userDirectory.getIdentifiers())
restUsers.add(new APIUser(userDirectory.get(username)));
return restUsers;
}
}

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

View File

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

View File

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

View File

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

View File

@@ -20,286 +20,35 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
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 -->
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>
30
</session-timeout>
<session-timeout>30</session-timeout>
</session-config>
<!-- Automatically detect and load WebSocket support -->
<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 -->
<!-- Guice -->
<filter>
<filter-name>AuthenticatingFilter</filter-name>
<filter-class>org.glyptodon.guacamole.net.basic.AuthenticatingFilter</filter-class>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticatingFilter</filter-name>
<url-pattern>*.xhtml</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-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Restrict requests to the WebSocket tunnel -->
<filter>
<filter-name>RestrictedFilter</filter-name>
<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>
<listener>
<listener-class>org.glyptodon.guacamole.net.basic.BasicServletContextListener</listener-class>
</listener>
<!-- Basic Login Servlet -->
<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>
<!-- Audio file mimetype mappings -->
<mime-mapping>
<extension>mp3</extension>
<mime-type>audio/mpeg</mime-type>