GUACAMOLE-1: Refactor org.glyptodon package/groupId to org.apache.
This commit is contained in:
1 parent
2358d88683
commit
6990344697
402 files changed
+1536
-1536
No files matched your search
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Connects users to a tunnel associated with the authorized connection
|
||||
* having the given ID.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Singleton
|
||||
public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
|
||||
|
||||
// Attempt to create HTTP tunnel
|
||||
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.");
|
||||
|
||||
return tunnel;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Stage;
|
||||
import com.google.inject.servlet.GuiceServletContextListener;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.net.basic.extension.ExtensionModule;
|
||||
import org.apache.guacamole.net.basic.log.LogModule;
|
||||
import org.apache.guacamole.net.basic.rest.RESTServiceModule;
|
||||
import org.apache.guacamole.net.basic.rest.auth.BasicTokenSessionMap;
|
||||
import org.apache.guacamole.net.basic.rest.auth.TokenSessionMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A ServletContextListener to listen for initialization of the servlet context
|
||||
* in order to set up dependency injection.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class BasicServletContextListener extends GuiceServletContextListener {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(BasicServletContextListener.class);
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Singleton instance of a TokenSessionMap.
|
||||
*/
|
||||
private TokenSessionMap sessionMap;
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent servletContextEvent) {
|
||||
|
||||
try {
|
||||
environment = new LocalEnvironment();
|
||||
sessionMap = new BasicTokenSessionMap(environment);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
|
||||
logger.debug("Error reading guacamole.properties.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
super.contextInitialized(servletContextEvent);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Injector getInjector() {
|
||||
return Guice.createInjector(Stage.PRODUCTION,
|
||||
new EnvironmentModule(environment),
|
||||
new LogModule(environment),
|
||||
new ExtensionModule(environment),
|
||||
new RESTServiceModule(sessionMap),
|
||||
new TunnelModule()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent servletContextEvent) {
|
||||
|
||||
super.contextDestroyed(servletContextEvent);
|
||||
|
||||
// Shutdown TokenSessionMap
|
||||
if (sessionMap != null)
|
||||
sessionMap.shutdown();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
/**
|
||||
* Provides central storage for a cross-connection clipboard state. This
|
||||
* clipboard state is shared only for a single HTTP session. Multiple HTTP
|
||||
* sessions will all have their own state.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ClipboardState {
|
||||
|
||||
/**
|
||||
* The maximum number of bytes to track.
|
||||
*/
|
||||
private static final int MAXIMUM_LENGTH = 262144;
|
||||
|
||||
/**
|
||||
* The mimetype of the current contents.
|
||||
*/
|
||||
private String mimetype = "text/plain";
|
||||
|
||||
/**
|
||||
* The mimetype of the pending contents.
|
||||
*/
|
||||
private String pending_mimetype = "text/plain";
|
||||
|
||||
/**
|
||||
* The current contents.
|
||||
*/
|
||||
private byte[] contents = new byte[0];
|
||||
|
||||
/**
|
||||
* The pending clipboard contents.
|
||||
*/
|
||||
private final byte[] pending = new byte[MAXIMUM_LENGTH];
|
||||
|
||||
/**
|
||||
* The length of the pending data, in bytes.
|
||||
*/
|
||||
private int pending_length = 0;
|
||||
|
||||
/**
|
||||
* The timestamp of the last contents update.
|
||||
*/
|
||||
private long last_update = 0;
|
||||
|
||||
/**
|
||||
* Returns the current clipboard contents.
|
||||
* @return The current clipboard contents
|
||||
*/
|
||||
public synchronized byte[] getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mimetype of the current clipboard contents.
|
||||
* @return The mimetype of the current clipboard contents.
|
||||
*/
|
||||
public synchronized String getMimetype() {
|
||||
return mimetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins a new update of the clipboard contents. The actual contents will
|
||||
* not be saved until commit() is called.
|
||||
*
|
||||
* @param mimetype The mimetype of the contents being added.
|
||||
*/
|
||||
public synchronized void begin(String mimetype) {
|
||||
pending_length = 0;
|
||||
this.pending_mimetype = mimetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given data to the clipboard contents.
|
||||
*
|
||||
* @param data The raw data to append.
|
||||
*/
|
||||
public synchronized void append(byte[] data) {
|
||||
|
||||
// Calculate size of copy
|
||||
int length = data.length;
|
||||
int remaining = pending.length - pending_length;
|
||||
if (remaining < length)
|
||||
length = remaining;
|
||||
|
||||
// Append data
|
||||
System.arraycopy(data, 0, pending, pending_length, length);
|
||||
pending_length += length;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the pending contents to the clipboard, notifying any threads
|
||||
* waiting for clipboard updates.
|
||||
*/
|
||||
public synchronized void commit() {
|
||||
|
||||
// Commit contents
|
||||
mimetype = pending_mimetype;
|
||||
contents = new byte[pending_length];
|
||||
System.arraycopy(pending, 0, contents, 0, pending_length);
|
||||
|
||||
// Notify of update
|
||||
last_update = System.currentTimeMillis();
|
||||
this.notifyAll();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait up to the given timeout for new clipboard data.
|
||||
*
|
||||
* @param timeout The amount of time to wait, in milliseconds.
|
||||
* @return true if the contents were updated within the timeframe given,
|
||||
* false otherwise.
|
||||
*/
|
||||
public synchronized boolean waitForContents(int timeout) {
|
||||
|
||||
// Wait for new contents if it's been a while
|
||||
if (System.currentTimeMillis() - last_update > timeout) {
|
||||
try {
|
||||
this.wait(timeout);
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException e) { /* ignore */ }
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
|
||||
/**
|
||||
* Guice module which binds the base Guacamole server environment.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class EnvironmentModule extends AbstractModule {
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* Creates a new EnvironmentModule which will bind the given environment
|
||||
* for future injection.
|
||||
*
|
||||
* @param environment
|
||||
* The environment to bind.
|
||||
*/
|
||||
public EnvironmentModule(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
// Bind environment
|
||||
bind(Environment.class).toInstance(environment);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
||||
|
||||
/**
|
||||
* A ClassLoader implementation which finds classes within a configurable
|
||||
* directory. This directory is set within guacamole.properties. This class
|
||||
* is deprecated in favor of DirectoryClassLoader, which is automatically
|
||||
* configured based on the presence/absence of GUACAMOLE_HOME/lib.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Deprecated
|
||||
public class GuacamoleClassLoader extends ClassLoader {
|
||||
|
||||
/**
|
||||
* Class loader which will load classes from the classpath specified
|
||||
* in guacamole.properties.
|
||||
*/
|
||||
private URLClassLoader classLoader = null;
|
||||
|
||||
/**
|
||||
* Any exception that occurs while the class loader is being instantiated.
|
||||
*/
|
||||
private static GuacamoleException exception = null;
|
||||
|
||||
/**
|
||||
* Singleton instance of the GuacamoleClassLoader.
|
||||
*/
|
||||
private static GuacamoleClassLoader instance = null;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
// Attempt to create singleton classloader which loads classes from
|
||||
// all .jar's in the lib directory defined in guacamole.properties
|
||||
instance = AccessController.doPrivileged(new PrivilegedExceptionAction<GuacamoleClassLoader>() {
|
||||
|
||||
@Override
|
||||
public GuacamoleClassLoader run() throws GuacamoleException {
|
||||
|
||||
// TODONT: This should be injected, but GuacamoleClassLoader will be removed soon.
|
||||
Environment environment = new LocalEnvironment();
|
||||
|
||||
return new GuacamoleClassLoader(
|
||||
environment.getProperty(BasicGuacamoleProperties.LIB_DIRECTORY)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
catch (PrivilegedActionException e) {
|
||||
// On error, record exception
|
||||
exception = (GuacamoleException) e.getException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GuacamoleClassLoader which reads classes from the given
|
||||
* directory.
|
||||
*
|
||||
* @param libDirectory The directory to load classes from.
|
||||
* @throws GuacamoleException If the file given is not a director, or if
|
||||
* an error occurs while constructing the URL
|
||||
* for the backing classloader.
|
||||
*/
|
||||
private GuacamoleClassLoader(File libDirectory) throws GuacamoleException {
|
||||
|
||||
// If no directory provided, just direct requests to parent classloader
|
||||
if (libDirectory == null)
|
||||
return;
|
||||
|
||||
// Validate directory is indeed a directory
|
||||
if (!libDirectory.isDirectory())
|
||||
throw new GuacamoleException(libDirectory + " is not a directory.");
|
||||
|
||||
// Get list of URLs for all .jar's in the lib directory
|
||||
Collection<URL> jarURLs = new ArrayList<URL>();
|
||||
File[] files = libDirectory.listFiles(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
|
||||
// If it ends with .jar, accept the file
|
||||
return name.endsWith(".jar");
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Verify directory was successfully read
|
||||
if (files == null)
|
||||
throw new GuacamoleException("Unable to read contents of directory " + libDirectory);
|
||||
|
||||
// Add the URL for each .jar to the jar URL list
|
||||
for (File file : files) {
|
||||
|
||||
try {
|
||||
jarURLs.add(file.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new GuacamoleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set delegate classloader to new URLClassLoader which loads from the
|
||||
// .jars found above.
|
||||
|
||||
URL[] urls = new URL[jarURLs.size()];
|
||||
classLoader = new URLClassLoader(
|
||||
jarURLs.toArray(urls),
|
||||
getClass().getClassLoader()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of a GuacamoleClassLoader which finds classes
|
||||
* within the directory configured in guacamole.properties.
|
||||
*
|
||||
* @return An instance of a GuacamoleClassLoader.
|
||||
* @throws GuacamoleException If no instance could be returned due to an
|
||||
* error.
|
||||
*/
|
||||
public static GuacamoleClassLoader getInstance() throws GuacamoleException {
|
||||
|
||||
// If instance could not be created, rethrow original exception
|
||||
if (exception != null) throw exception;
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
|
||||
// If no classloader, use default loader
|
||||
if (classLoader == null)
|
||||
return Class.forName(name);
|
||||
|
||||
// Otherwise, delegate
|
||||
return classLoader.loadClass(name);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(GuacamoleSession.class);
|
||||
|
||||
/**
|
||||
* The user associated with this session.
|
||||
*/
|
||||
private AuthenticatedUser authenticatedUser;
|
||||
|
||||
/**
|
||||
* All UserContexts associated with this session. Each
|
||||
* AuthenticationProvider may provide its own UserContext.
|
||||
*/
|
||||
private List<UserContext> userContexts;
|
||||
|
||||
/**
|
||||
* All currently-active tunnels, indexed by tunnel UUID.
|
||||
*/
|
||||
private final Map<String, GuacamoleTunnel> tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
|
||||
|
||||
/**
|
||||
* The last time this session was accessed.
|
||||
*/
|
||||
private long lastAccessedTime;
|
||||
|
||||
/**
|
||||
* Creates a new Guacamole session associated with the given
|
||||
* AuthenticatedUser and UserContexts.
|
||||
*
|
||||
* @param environment
|
||||
* The environment of the Guacamole server associated with this new
|
||||
* session.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The authenticated user to associate this session with.
|
||||
*
|
||||
* @param userContexts
|
||||
* The List of UserContexts to associate with this session.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents the session from being created.
|
||||
*/
|
||||
public GuacamoleSession(Environment environment,
|
||||
AuthenticatedUser authenticatedUser,
|
||||
List<UserContext> userContexts)
|
||||
throws GuacamoleException {
|
||||
this.lastAccessedTime = System.currentTimeMillis();
|
||||
this.authenticatedUser = authenticatedUser;
|
||||
this.userContexts = userContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authenticated user associated with this session.
|
||||
*
|
||||
* @return
|
||||
* The authenticated user associated with this session.
|
||||
*/
|
||||
public AuthenticatedUser getAuthenticatedUser() {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the authenticated user associated with this session with the
|
||||
* given authenticated user.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The authenticated user to associated with this session.
|
||||
*/
|
||||
public void setAuthenticatedUser(AuthenticatedUser authenticatedUser) {
|
||||
this.authenticatedUser = authenticatedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all UserContexts associated with this session. Each
|
||||
* AuthenticationProvider currently loaded by Guacamole may provide its own
|
||||
* UserContext for any successfully-authenticated user.
|
||||
*
|
||||
* @return
|
||||
* An unmodifiable list of all UserContexts associated with this
|
||||
* session.
|
||||
*/
|
||||
public List<UserContext> getUserContexts() {
|
||||
return Collections.unmodifiableList(userContexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all UserContexts associated with this session with the given
|
||||
* List of UserContexts.
|
||||
*
|
||||
* @param userContexts
|
||||
* The List of UserContexts to associate with this session.
|
||||
*/
|
||||
public void setUserContexts(List<UserContext> userContexts) {
|
||||
this.userContexts = userContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this session has any associated active tunnels.
|
||||
*
|
||||
* @return true if this session has any associated active tunnels,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean hasTunnels() {
|
||||
return !tunnels.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all active tunnels associated with this session, where
|
||||
* each key is the String representation of the tunnel's UUID. Changes to
|
||||
* this map immediately affect the set of tunnels associated with this
|
||||
* session. A tunnel need not be present here to be used by the user
|
||||
* associated with this session, but tunnels not in this set will not
|
||||
* be taken into account when determining whether a session is in use.
|
||||
*
|
||||
* @return A map of all active tunnels associated with this session.
|
||||
*/
|
||||
public Map<String, GuacamoleTunnel> getTunnels() {
|
||||
return tunnels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the given tunnel with this session, such that it is taken
|
||||
* into account when determining session activity.
|
||||
*
|
||||
* @param tunnel The tunnel to associate with this session.
|
||||
*/
|
||||
public void addTunnel(GuacamoleTunnel tunnel) {
|
||||
tunnels.put(tunnel.getUUID().toString(), tunnel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociates the tunnel having the given UUID from this session.
|
||||
*
|
||||
* @param uuid The UUID of the tunnel to disassociate from this session.
|
||||
* @return true if the tunnel existed and was removed, false otherwise.
|
||||
*/
|
||||
public boolean removeTunnel(String uuid) {
|
||||
return tunnels.remove(uuid) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this session, marking it as accessed.
|
||||
*/
|
||||
public void access() {
|
||||
lastAccessedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last accessed, as the number of
|
||||
* milliseconds since midnight January 1, 1970 GMT. Session access must
|
||||
* be explicitly marked through calls to the access() function.
|
||||
*
|
||||
* @return The time this session was last accessed.
|
||||
*/
|
||||
public long getLastAccessedTime() {
|
||||
return lastAccessedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all associated tunnels and prevents any further use of this
|
||||
* session.
|
||||
*/
|
||||
public void invalidate() {
|
||||
|
||||
// Close all associated tunnels, if possible
|
||||
for (GuacamoleTunnel tunnel : tunnels.values()) {
|
||||
try {
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close tunnel.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* HTTP-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class HTTPTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* A copy of the parameters obtained from the HttpServletRequest used to
|
||||
* construct the HTTPTunnelRequest.
|
||||
*/
|
||||
private final Map<String, List<String>> parameterMap =
|
||||
new HashMap<String, List<String>>();
|
||||
|
||||
/**
|
||||
* Creates a HTTPTunnelRequest which copies and exposes the parameters
|
||||
* from the given HttpServletRequest.
|
||||
*
|
||||
* @param request
|
||||
* The HttpServletRequest to copy parameter values from.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
|
||||
public HTTPTunnelRequest(HttpServletRequest request) {
|
||||
|
||||
// For each parameter
|
||||
for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
|
||||
request.getParameterMap()).entrySet()) {
|
||||
|
||||
// Get parameter name and corresponding values
|
||||
String parameterName = mapEntry.getKey();
|
||||
List<String> parameterValues = Arrays.asList(mapEntry.getValue());
|
||||
|
||||
// Store copy of all values in our own map
|
||||
parameterMap.put(
|
||||
parameterName,
|
||||
new ArrayList<String>(parameterValues)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
List<String> values = getParameterValues(name);
|
||||
|
||||
// Return the first value from the list if available
|
||||
if (values != null && !values.isEmpty())
|
||||
return values.get(0);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
return parameterMap.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.Module;
|
||||
|
||||
/**
|
||||
* Generic means of loading a tunnel without adding explicit dependencies within
|
||||
* the main ServletModule, as not all servlet containers may have the classes
|
||||
* required by all tunnel implementations.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public interface TunnelLoader extends Module {
|
||||
|
||||
/**
|
||||
* Checks whether this type of tunnel is supported by the servlet container.
|
||||
*
|
||||
* @return true if this type of tunnel is supported and can be loaded
|
||||
* without errors, false otherwise.
|
||||
*/
|
||||
public boolean isSupported();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Module which loads tunnel implementations.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class TunnelModule extends ServletModule {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
|
||||
|
||||
/**
|
||||
* Classnames of all implementation-specific WebSocket tunnel modules.
|
||||
*/
|
||||
private static final String[] WEBSOCKET_MODULES = {
|
||||
"org.apache.guacamole.net.basic.websocket.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.net.basic.websocket.jetty8.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.net.basic.websocket.jetty9.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.net.basic.websocket.tomcat.WebSocketTunnelModule"
|
||||
};
|
||||
|
||||
private boolean loadWebSocketModule(String classname) {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket module
|
||||
Class<?> module = Class.forName(classname);
|
||||
|
||||
// Create loader
|
||||
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
|
||||
|
||||
// Install module, if supported
|
||||
if (loader.isSupported()) {
|
||||
install(loader);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If no such class or constructor, etc., then this particular
|
||||
// WebSocket support is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
catch (NoSuchMethodException e) {}
|
||||
|
||||
// Log errors which indicate bugs
|
||||
catch (InstantiationException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
|
||||
// Load attempt failed
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
bind(TunnelRequestService.class);
|
||||
|
||||
// Set up HTTP tunnel
|
||||
serve("/tunnel").with(BasicGuacamoleTunnelServlet.class);
|
||||
|
||||
// Try to load each WebSocket tunnel in sequence
|
||||
for (String classname : WEBSOCKET_MODULES) {
|
||||
if (loadWebSocketModule(classname)) {
|
||||
logger.debug("WebSocket module loaded: {}", classname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Warn of lack of WebSocket
|
||||
logger.info("WebSocket support NOT present. Only HTTP will be used.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* A request object which provides only the functions absolutely required to
|
||||
* retrieve and connect to a tunnel.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class TunnelRequest {
|
||||
|
||||
/**
|
||||
* The name of the request parameter containing the user's authentication
|
||||
* token.
|
||||
*/
|
||||
public static final String AUTH_TOKEN_PARAMETER = "token";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the identifier of the
|
||||
* AuthenticationProvider associated with the UserContext containing the
|
||||
* object to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying the type of object to which a
|
||||
* tunnel is being requested. Currently, this may be "c" for a Guacamole
|
||||
* connection, or "g" for a Guacamole connection group.
|
||||
*/
|
||||
public static final String TYPE_PARAMETER = "GUAC_TYPE";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the unique identifier of the object
|
||||
* to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display width, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display height, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display resolution, in
|
||||
* DPI.
|
||||
*/
|
||||
public static final String DPI_PARAMETER = "GUAC_DPI";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported audio mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported video mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported image mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
|
||||
|
||||
/**
|
||||
* All supported object types that can be used as the destination of a
|
||||
* tunnel.
|
||||
*/
|
||||
public static enum Type {
|
||||
|
||||
/**
|
||||
* A Guacamole connection.
|
||||
*/
|
||||
CONNECTION("c"),
|
||||
|
||||
/**
|
||||
* A Guacamole connection group.
|
||||
*/
|
||||
CONNECTION_GROUP("g");
|
||||
|
||||
/**
|
||||
* The parameter value which denotes a destination object of this type.
|
||||
*/
|
||||
final String PARAMETER_VALUE;
|
||||
|
||||
/**
|
||||
* Defines a Type having the given corresponding parameter value.
|
||||
*
|
||||
* @param value
|
||||
* The parameter value which denotes a destination object of this
|
||||
* type.
|
||||
*/
|
||||
Type(String value) {
|
||||
PARAMETER_VALUE = value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value of the parameter having the given name.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name, or null if no such
|
||||
* parameter was specified.
|
||||
*/
|
||||
public abstract String getParameter(String name);
|
||||
|
||||
/**
|
||||
* Returns a list of all values specified for the given parameter.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* All values of the parameter having the given name , or null if no
|
||||
* such parameter was specified.
|
||||
*/
|
||||
public abstract List<String> getParameterValues(String name);
|
||||
|
||||
/**
|
||||
* Returns the value of the parameter having the given name, throwing an
|
||||
* exception if the parameter is missing.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not present in the request.
|
||||
*/
|
||||
public String getRequiredParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter, aborting if absent
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value of the parameter having the given name,
|
||||
* throwing an exception if the parameter cannot be parsed.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The integer value of the parameter having the given name, or null if
|
||||
* the parameter is missing.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not a valid integer.
|
||||
*/
|
||||
public Integer getIntegerParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Attempt to parse as an integer
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
// Rethrow any parsing error as a GuacamoleClientException
|
||||
catch (NumberFormatException e) {
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication token associated with this tunnel request.
|
||||
*
|
||||
* @return
|
||||
* The authentication token associated with this tunnel request, or
|
||||
* null if no authentication token is present.
|
||||
*/
|
||||
public String getAuthenticationToken() {
|
||||
return getParameter(AUTH_TOKEN_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created. In the context of the REST API and
|
||||
* the JavaScript side of the web application, this is referred to as the
|
||||
* data source identifier.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getAuthenticationProviderIdentifier()
|
||||
throws GuacamoleException {
|
||||
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @return
|
||||
* The type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the type was not present in the request, or if the type requested
|
||||
* is in the wrong format.
|
||||
*/
|
||||
public Type getType() throws GuacamoleException {
|
||||
|
||||
String type = getRequiredParameter(TYPE_PARAMETER);
|
||||
|
||||
// For each possible object type
|
||||
for (Type possibleType : Type.values()) {
|
||||
|
||||
// Match against defined parameter value
|
||||
if (type.equals(possibleType.PARAMETER_VALUE))
|
||||
return possibleType;
|
||||
|
||||
}
|
||||
|
||||
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the destination of the tunnel being requested.
|
||||
* As there are multiple types of destination objects available, and within
|
||||
* multiple data sources, the associated object type and data source are
|
||||
* also necessary to determine what this identifier refers to.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the destination of the tunnel being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getIdentifier() throws GuacamoleException {
|
||||
return getRequiredParameter(IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display width desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display width desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the width specified was not a valid integer.
|
||||
*/
|
||||
public Integer getWidth() throws GuacamoleException {
|
||||
return getIntegerParameter(WIDTH_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display height desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display height desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the height specified was not a valid integer.
|
||||
*/
|
||||
public Integer getHeight() throws GuacamoleException {
|
||||
return getIntegerParameter(HEIGHT_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display resolution desired for the Guacamole session over
|
||||
* the tunnel being requested, in DPI.
|
||||
*
|
||||
* @return
|
||||
* The display resolution desired for the Guacamole session over the
|
||||
* tunnel being requested, or null if no resolution was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the resolution specified was not a valid integer.
|
||||
*/
|
||||
public Integer getDPI() throws GuacamoleException {
|
||||
return getIntegerParameter(DPI_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all audio mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all audio mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getAudioMimetypes() {
|
||||
return getParameterValues(AUDIO_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all video mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all video mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getVideoMimetypes() {
|
||||
return getParameterValues(VIDEO_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all image mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all image mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getImageMimetypes() {
|
||||
return getParameterValues(IMAGE_PARAMETER);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleSecurityException;
|
||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class that takes a standard request from the Guacamole JavaScript
|
||||
* client and produces the corresponding GuacamoleTunnel. The implementation
|
||||
* of this utility is specific to the form of request used by the upstream
|
||||
* Guacamole web application, and is not necessarily useful to applications
|
||||
* that use purely the Guacamole API.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
* @author Vasily Loginov
|
||||
*/
|
||||
@Singleton
|
||||
public class TunnelRequestService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
|
||||
|
||||
/**
|
||||
* A service for authenticating users from auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Reads and returns the client information provided within the given
|
||||
* request.
|
||||
*
|
||||
* @param request
|
||||
* The request describing tunnel to create.
|
||||
*
|
||||
* @return GuacamoleClientInformation
|
||||
* An object containing information about the client sending the tunnel
|
||||
* request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameters of the tunnel request are invalid.
|
||||
*/
|
||||
protected GuacamoleClientInformation getClientInformation(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get client information
|
||||
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
||||
|
||||
// Set width if provided
|
||||
Integer width = request.getWidth();
|
||||
if (width != null)
|
||||
info.setOptimalScreenWidth(width);
|
||||
|
||||
// Set height if provided
|
||||
Integer height = request.getHeight();
|
||||
if (height != null)
|
||||
info.setOptimalScreenHeight(height);
|
||||
|
||||
// Set resolution if provided
|
||||
Integer dpi = request.getDPI();
|
||||
if (dpi != null)
|
||||
info.setOptimalResolution(dpi);
|
||||
|
||||
// Add audio mimetypes
|
||||
List<String> audioMimetypes = request.getAudioMimetypes();
|
||||
if (audioMimetypes != null)
|
||||
info.getAudioMimetypes().addAll(audioMimetypes);
|
||||
|
||||
// Add video mimetypes
|
||||
List<String> videoMimetypes = request.getVideoMimetypes();
|
||||
if (videoMimetypes != null)
|
||||
info.getVideoMimetypes().addAll(videoMimetypes);
|
||||
|
||||
// Add image mimetypes
|
||||
List<String> imageMimetypes = request.getImageMimetypes();
|
||||
if (imageMimetypes != null)
|
||||
info.getImageMimetypes().addAll(imageMimetypes);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tunnel using which is connected to the connection or
|
||||
* connection group identifier by the given ID. Client information
|
||||
* is specified in the {@code info} parameter.
|
||||
*
|
||||
* @param context
|
||||
* The UserContext associated with the user for whom the tunnel is
|
||||
* being created.
|
||||
*
|
||||
* @param type
|
||||
* The type of object being connected to (connection or group).
|
||||
*
|
||||
* @param id
|
||||
* The id of the connection or group being connected to.
|
||||
*
|
||||
* @param info
|
||||
* Information describing the connected Guacamole client.
|
||||
*
|
||||
* @return
|
||||
* A new tunnel, connected as required by the request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the tunnel.
|
||||
*/
|
||||
protected GuacamoleTunnel createConnectedTunnel(UserContext context,
|
||||
final TunnelRequest.Type type, String id,
|
||||
GuacamoleClientInformation info)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Create connected tunnel from identifier
|
||||
GuacamoleTunnel tunnel = null;
|
||||
switch (type) {
|
||||
|
||||
// Connection identifiers
|
||||
case CONNECTION: {
|
||||
|
||||
// Get connection directory
|
||||
Directory<Connection> directory = context.getConnectionDirectory();
|
||||
|
||||
// Get authorized connection
|
||||
Connection connection = directory.get(id);
|
||||
if (connection == null) {
|
||||
logger.info("Connection \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
|
||||
throw new GuacamoleSecurityException("Requested connection is not authorized.");
|
||||
}
|
||||
|
||||
// Connect tunnel
|
||||
tunnel = connection.connect(info);
|
||||
logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id);
|
||||
break;
|
||||
}
|
||||
|
||||
// Connection group identifiers
|
||||
case CONNECTION_GROUP: {
|
||||
|
||||
// Get connection group directory
|
||||
Directory<ConnectionGroup> directory = context.getConnectionGroupDirectory();
|
||||
|
||||
// Get authorized connection group
|
||||
ConnectionGroup group = directory.get(id);
|
||||
if (group == null) {
|
||||
logger.info("Connection group \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
|
||||
throw new GuacamoleSecurityException("Requested connection group is not authorized.");
|
||||
}
|
||||
|
||||
// Connect tunnel
|
||||
tunnel = group.connect(info);
|
||||
logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id);
|
||||
break;
|
||||
}
|
||||
|
||||
// Type is guaranteed to be one of the above
|
||||
default:
|
||||
assert(false);
|
||||
|
||||
}
|
||||
|
||||
return tunnel;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the given tunnel with the given session, returning a wrapped
|
||||
* version of the same tunnel which automatically handles closure and
|
||||
* removal from the session.
|
||||
*
|
||||
* @param tunnel
|
||||
* The connected tunnel to wrap and monitor.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token associated with the given session. If
|
||||
* provided, this token will be automatically invalidated (and the
|
||||
* corresponding session destroyed) if tunnel errors imply that the
|
||||
* user is no longer authorized.
|
||||
*
|
||||
* @param session
|
||||
* The Guacamole session to associate the tunnel with.
|
||||
*
|
||||
* @param type
|
||||
* The type of object being connected to (connection or group).
|
||||
*
|
||||
* @param id
|
||||
* The id of the connection or group being connected to.
|
||||
*
|
||||
* @return
|
||||
* A new tunnel, associated with the given session, which delegates all
|
||||
* functionality to the given tunnel while monitoring and automatically
|
||||
* handling closure.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while obtaining the tunnel.
|
||||
*/
|
||||
protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel,
|
||||
final String authToken, final GuacamoleSession session,
|
||||
final TunnelRequest.Type type, final String id)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Monitor tunnel closure and data
|
||||
GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
|
||||
|
||||
/**
|
||||
* The time the connection began, measured in milliseconds since
|
||||
* midnight, January 1, 1970 UTC.
|
||||
*/
|
||||
private final long connectionStartTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void close() throws GuacamoleException {
|
||||
|
||||
long connectionEndTime = System.currentTimeMillis();
|
||||
long duration = connectionEndTime - connectionStartTime;
|
||||
|
||||
// Log closure
|
||||
switch (type) {
|
||||
|
||||
// Connection identifiers
|
||||
case CONNECTION:
|
||||
logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds",
|
||||
session.getAuthenticatedUser().getIdentifier(), id, duration);
|
||||
break;
|
||||
|
||||
// Connection group identifiers
|
||||
case CONNECTION_GROUP:
|
||||
logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds",
|
||||
session.getAuthenticatedUser().getIdentifier(), id, duration);
|
||||
break;
|
||||
|
||||
// Type is guaranteed to be one of the above
|
||||
default:
|
||||
assert(false);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Close and clean up tunnel
|
||||
session.removeTunnel(getUUID().toString());
|
||||
super.close();
|
||||
|
||||
}
|
||||
|
||||
// Ensure any associated session is invalidated if unauthorized
|
||||
catch (GuacamoleUnauthorizedException e) {
|
||||
|
||||
// If there is an associated auth token, invalidate it
|
||||
if (authenticationService.destroyGuacamoleSession(authToken))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
|
||||
|
||||
// Continue with exception processing
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Associate tunnel with session
|
||||
session.addTunnel(monitoredTunnel);
|
||||
return monitoredTunnel;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tunnel using the parameters and credentials present in
|
||||
* the given request.
|
||||
*
|
||||
* @param request
|
||||
* The request describing the tunnel to create.
|
||||
*
|
||||
* @return
|
||||
* The created tunnel, or null if the tunnel could not be created.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the tunnel.
|
||||
*/
|
||||
public GuacamoleTunnel createTunnel(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Parse request parameters
|
||||
String authToken = request.getAuthenticationToken();
|
||||
String id = request.getIdentifier();
|
||||
TunnelRequest.Type type = request.getType();
|
||||
String authProviderIdentifier = request.getAuthenticationProviderIdentifier();
|
||||
GuacamoleClientInformation info = getClientInformation(request);
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
try {
|
||||
|
||||
// Create connected tunnel using provided connection ID and client information
|
||||
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
|
||||
|
||||
// Associate tunnel with session
|
||||
return createAssociatedTunnel(tunnel, authToken, session, type, id);
|
||||
|
||||
}
|
||||
|
||||
// Ensure any associated session is invalidated if unauthorized
|
||||
catch (GuacamoleUnauthorizedException e) {
|
||||
|
||||
// If there is an associated auth token, invalidate it
|
||||
if (authenticationService.destroyGuacamoleSession(authToken))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
|
||||
|
||||
// Continue with exception processing
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.auth;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
|
||||
/**
|
||||
* Mapping of username/password pair to configuration set. In addition to basic
|
||||
* storage of the username, password, and configurations, this class also
|
||||
* provides password validation functions.
|
||||
*
|
||||
* @author Mike Jumper
|
||||
*/
|
||||
public class Authorization {
|
||||
|
||||
/**
|
||||
* All supported password encodings.
|
||||
*/
|
||||
public static enum Encoding {
|
||||
|
||||
/**
|
||||
* Plain-text password (not hashed at all).
|
||||
*/
|
||||
PLAIN_TEXT,
|
||||
|
||||
/**
|
||||
* Password hashed with MD5.
|
||||
*/
|
||||
MD5
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The username being authorized.
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* The password corresponding to the username being authorized, which may
|
||||
* be hashed.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* The encoding used when the password was hashed.
|
||||
*/
|
||||
private Encoding encoding = Encoding.PLAIN_TEXT;
|
||||
|
||||
/**
|
||||
* Map of all authorized configurations, indexed by configuration name.
|
||||
*/
|
||||
private Map<String, GuacamoleConfiguration> configs = new
|
||||
TreeMap<String, GuacamoleConfiguration>();
|
||||
|
||||
/**
|
||||
* Lookup table of hex bytes characters by value.
|
||||
*/
|
||||
private static final char HEX_CHARS[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces a String containing the bytes provided in hexadecimal notation.
|
||||
*
|
||||
* @param bytes The bytes to convert into hex.
|
||||
* @return A String containing the hex representation of the given bytes.
|
||||
*/
|
||||
private static String getHexString(byte[] bytes) {
|
||||
|
||||
// If null byte array given, return null
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
// Create string builder for holding the hex representation,
|
||||
// pre-calculating the exact length
|
||||
StringBuilder hex = new StringBuilder(2 * bytes.length);
|
||||
|
||||
// Convert each byte into a pair of hex digits
|
||||
for (byte b : bytes) {
|
||||
hex.append(HEX_CHARS[(b & 0xF0) >> 4])
|
||||
.append(HEX_CHARS[ b & 0x0F ]);
|
||||
}
|
||||
|
||||
// Return the string produced
|
||||
return hex.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username associated with this authorization.
|
||||
*
|
||||
* @return The username associated with this authorization.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username associated with this authorization.
|
||||
*
|
||||
* @param username The username to associate with this authorization.
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password associated with this authorization, which may be
|
||||
* encoded or hashed.
|
||||
*
|
||||
* @return The password associated with this authorization.
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password associated with this authorization, which must be
|
||||
* encoded using the encoding specified with setEncoding(). By default,
|
||||
* passwords are plain text.
|
||||
*
|
||||
* @param password Sets the password associated with this authorization.
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoding used to hash the password, if any.
|
||||
*
|
||||
* @return The encoding used to hash the password.
|
||||
*/
|
||||
public Encoding getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encoding which will be used to hash the password or when
|
||||
* comparing a given password for validation.
|
||||
*
|
||||
* @param encoding The encoding to use for password hashing.
|
||||
*/
|
||||
public void setEncoding(Encoding encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given username/password pair is authorized based on
|
||||
* the stored username and password. The password given must be plain text.
|
||||
* It will be hashed as necessary to perform the validation.
|
||||
*
|
||||
* @param username The username to validate.
|
||||
* @param password The password to validate.
|
||||
* @return true if the username/password pair given is authorized, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean validate(String username, String password) {
|
||||
|
||||
// If username matches
|
||||
if (username != null && password != null
|
||||
&& username.equals(this.username)) {
|
||||
|
||||
switch (encoding) {
|
||||
|
||||
// If plain text, just compare
|
||||
case PLAIN_TEXT:
|
||||
|
||||
// Compare plaintext
|
||||
return password.equals(this.password);
|
||||
|
||||
// If hased with MD5, hash password and compare
|
||||
case MD5:
|
||||
|
||||
// Compare hashed password
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
String hashedPassword = getHexString(digest.digest(password.getBytes("UTF-8")));
|
||||
return hashedPassword.equals(this.password.toUpperCase());
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // end validation check
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GuacamoleConfiguration having the given name and associated
|
||||
* with the username/password pair stored within this authorization.
|
||||
*
|
||||
* @param name The name of the GuacamoleConfiguration to return.
|
||||
* @return The GuacamoleConfiguration having the given name, or null if no
|
||||
* such GuacamoleConfiguration exists.
|
||||
*/
|
||||
public GuacamoleConfiguration getConfiguration(String name) {
|
||||
return configs.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given GuacamoleConfiguration to the set of stored configurations
|
||||
* under the given name.
|
||||
*
|
||||
* @param name The name to associate this GuacamoleConfiguration with.
|
||||
* @param config The GuacamoleConfiguration to store.
|
||||
*/
|
||||
public void addConfiguration(String name, GuacamoleConfiguration config) {
|
||||
configs.put(name, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of all stored GuacamoleConfigurations associated with the
|
||||
* username/password pair stored within this authorization, indexed by
|
||||
* configuration name.
|
||||
*
|
||||
* @return A Map of all stored GuacamoleConfigurations.
|
||||
*/
|
||||
public Map<String, GuacamoleConfiguration> getConfigurations() {
|
||||
return configs;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.auth;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Mapping of all usernames to corresponding authorizations.
|
||||
*
|
||||
* @author Mike Jumper
|
||||
*/
|
||||
public class UserMapping {
|
||||
|
||||
/**
|
||||
* All authorizations, indexed by username.
|
||||
*/
|
||||
private Map<String, Authorization> authorizations =
|
||||
new HashMap<String, Authorization>();
|
||||
|
||||
/**
|
||||
* Adds the given authorization to the user mapping.
|
||||
*
|
||||
* @param authorization The authorization to add to the user mapping.
|
||||
*/
|
||||
public void addAuthorization(Authorization authorization) {
|
||||
authorizations.put(authorization.getUsername(), authorization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorization corresponding to the user having the given
|
||||
* username, if any.
|
||||
*
|
||||
* @param username The username to find the authorization for.
|
||||
* @return The authorization corresponding to the user having the given
|
||||
* username, or null if no such authorization exists.
|
||||
*/
|
||||
public Authorization getAuthorization(String username) {
|
||||
return authorizations.get(username);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes which drive the default, basic authentication of the Guacamole
|
||||
* web application.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.auth;
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides a safe wrapper around an AuthenticationProvider subclass, such that
|
||||
* authentication attempts can cleanly fail, and errors can be properly logged,
|
||||
* even if the AuthenticationProvider cannot be instantiated.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class AuthenticationProviderFacade implements AuthenticationProvider {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private Logger logger = LoggerFactory.getLogger(AuthenticationProviderFacade.class);
|
||||
|
||||
/**
|
||||
* The underlying authentication provider, or null if the authentication
|
||||
* provider could not be instantiated.
|
||||
*/
|
||||
private final AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* The identifier to provide for the underlying authentication provider if
|
||||
* the authentication provider could not be loaded.
|
||||
*/
|
||||
private final String facadeIdentifier = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationProviderFacade which delegates all function
|
||||
* calls to an instance of the given AuthenticationProvider subclass. If
|
||||
* an instance of the given class cannot be created, creation of this
|
||||
* facade will still succeed, but its use will result in errors being
|
||||
* logged, and all authentication attempts will fail.
|
||||
*
|
||||
* @param authProviderClass
|
||||
* The AuthenticationProvider subclass to instantiate.
|
||||
*/
|
||||
public AuthenticationProviderFacade(Class<? extends AuthenticationProvider> authProviderClass) {
|
||||
|
||||
AuthenticationProvider instance = null;
|
||||
|
||||
try {
|
||||
// Attempt to instantiate the authentication provider
|
||||
instance = authProviderClass.getConstructor().newInstance();
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
logger.error("The authentication extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.");
|
||||
logger.debug("AuthenticationProvider is missing a default constructor.", e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
logger.error("The Java security mananager is preventing authentication extensions "
|
||||
+ "from being loaded. Please check the configuration of Java or your "
|
||||
+ "servlet container.");
|
||||
logger.debug("Creation of AuthenticationProvider disallowed by security manager.", e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
logger.error("The authentication extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.");
|
||||
logger.debug("AuthenticationProvider cannot be instantiated.", e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
logger.error("The authentication extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.");
|
||||
logger.debug("Default constructor of AuthenticationProvider is not public.", e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
logger.error("The authentication extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.");
|
||||
logger.debug("Default constructor of AuthenticationProvider cannot accept zero arguments.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
|
||||
// Obtain causing error - create relatively-informative stub error if cause is unknown
|
||||
Throwable cause = e.getCause();
|
||||
if (cause == null)
|
||||
cause = new GuacamoleException("Error encountered during initialization.");
|
||||
|
||||
logger.error("Authentication extension failed to start: {}", cause.getMessage());
|
||||
logger.debug("AuthenticationProvider instantiation failed.", e);
|
||||
|
||||
}
|
||||
|
||||
// Associate instance, if any
|
||||
authProvider = instance;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
|
||||
// Ignore auth attempts if no auth provider could be loaded
|
||||
if (authProvider == null) {
|
||||
logger.warn("The authentication system could not be loaded. Please check for errors earlier in the logs.");
|
||||
return facadeIdentifier;
|
||||
}
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.getIdentifier();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Ignore auth attempts if no auth provider could be loaded
|
||||
if (authProvider == null) {
|
||||
logger.warn("Authentication attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
|
||||
throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD);
|
||||
}
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.authenticateUser(credentials);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// Ignore auth attempts if no auth provider could be loaded
|
||||
if (authProvider == null) {
|
||||
logger.warn("Reauthentication attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
|
||||
throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD);
|
||||
}
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Ignore auth attempts if no auth provider could be loaded
|
||||
if (authProvider == null) {
|
||||
logger.warn("User data retrieval attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.getUserContext(authenticatedUser);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext updateUserContext(UserContext context,
|
||||
AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Ignore auth attempts if no auth provider could be loaded
|
||||
if (authProvider == null) {
|
||||
logger.warn("User data refresh attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.updateUserContext(context, authenticatedUser);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* A ClassLoader implementation which finds classes within .jar files within a
|
||||
* given directory.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class DirectoryClassLoader extends URLClassLoader {
|
||||
|
||||
/**
|
||||
* Returns an instance of DirectoryClassLoader configured to load .jar
|
||||
* files from the given directory. Calling this function multiple times
|
||||
* will not affect previously-returned instances of DirectoryClassLoader.
|
||||
*
|
||||
* @param dir
|
||||
* The directory from which .jar files should be read.
|
||||
*
|
||||
* @return
|
||||
* A DirectoryClassLoader instance which loads classes from the .jar
|
||||
* files in the given directory.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given file is not a directory, or the contents of the given
|
||||
* directory cannot be read.
|
||||
*/
|
||||
public static DirectoryClassLoader getInstance(final File dir)
|
||||
throws GuacamoleException {
|
||||
|
||||
try {
|
||||
// Attempt to create singleton classloader which loads classes from
|
||||
// all .jar's in the lib directory defined in guacamole.properties
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<DirectoryClassLoader>() {
|
||||
|
||||
@Override
|
||||
public DirectoryClassLoader run() throws GuacamoleException {
|
||||
return new DirectoryClassLoader(dir);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
catch (PrivilegedActionException e) {
|
||||
throw (GuacamoleException) e.getException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all .jar files within the given directory as an array of URLs.
|
||||
*
|
||||
* @param dir
|
||||
* The directory to retrieve all .jar files from.
|
||||
*
|
||||
* @return
|
||||
* An array of the URLs of all .jar files within the given directory.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given file is not a directory, or the contents of the given
|
||||
* directory cannot be read.
|
||||
*/
|
||||
private static URL[] getJarURLs(File dir) throws GuacamoleException {
|
||||
|
||||
// Validate directory is indeed a directory
|
||||
if (!dir.isDirectory())
|
||||
throw new GuacamoleException(dir + " is not a directory.");
|
||||
|
||||
// Get list of URLs for all .jar's in the lib directory
|
||||
Collection<URL> jarURLs = new ArrayList<URL>();
|
||||
File[] files = dir.listFiles(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
|
||||
// If it ends with .jar, accept the file
|
||||
return name.endsWith(".jar");
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Verify directory was successfully read
|
||||
if (files == null)
|
||||
throw new GuacamoleException("Unable to read contents of directory " + dir);
|
||||
|
||||
// Add the URL for each .jar to the jar URL list
|
||||
for (File file : files) {
|
||||
|
||||
try {
|
||||
jarURLs.add(file.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new GuacamoleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set delegate classloader to new URLClassLoader which loads from the .jars found above.
|
||||
URL[] urls = new URL[jarURLs.size()];
|
||||
return jarURLs.toArray(urls);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DirectoryClassLoader configured to load .jar files from
|
||||
* the given directory.
|
||||
*
|
||||
* @param dir
|
||||
* The directory from which .jar files should be read.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given file is not a directory, or the contents of the given
|
||||
* directory cannot be read.
|
||||
*/
|
||||
|
||||
private DirectoryClassLoader(File dir) throws GuacamoleException {
|
||||
super(getJarURLs(dir), DirectoryClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
import org.codehaus.jackson.JsonParseException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.basic.resource.ClassPathResource;
|
||||
import org.apache.guacamole.net.basic.resource.Resource;
|
||||
|
||||
/**
|
||||
* A Guacamole extension, which may provide custom authentication, static
|
||||
* files, theming/branding, etc.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class Extension {
|
||||
|
||||
/**
|
||||
* The Jackson parser for parsing the language JSON files.
|
||||
*/
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* The name of the manifest file that describes the contents of a
|
||||
* Guacamole extension.
|
||||
*/
|
||||
private static final String MANIFEST_NAME = "guac-manifest.json";
|
||||
|
||||
/**
|
||||
* The parsed manifest file of this extension, describing the location of
|
||||
* resources within the extension.
|
||||
*/
|
||||
private final ExtensionManifest manifest;
|
||||
|
||||
/**
|
||||
* The classloader to use when reading resources from this extension,
|
||||
* including classes and static files.
|
||||
*/
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* Map of all JavaScript resources defined within the extension, where each
|
||||
* key is the path to that resource within the extension.
|
||||
*/
|
||||
private final Map<String, Resource> javaScriptResources;
|
||||
|
||||
/**
|
||||
* Map of all CSS resources defined within the extension, where each key is
|
||||
* the path to that resource within the extension.
|
||||
*/
|
||||
private final Map<String, Resource> cssResources;
|
||||
|
||||
/**
|
||||
* Map of all HTML patch resources defined within the extension, where each
|
||||
* key is the path to that resource within the extension.
|
||||
*/
|
||||
private final Map<String, Resource> htmlResources;
|
||||
|
||||
/**
|
||||
* Map of all translation resources defined within the extension, where
|
||||
* each key is the path to that resource within the extension.
|
||||
*/
|
||||
private final Map<String, Resource> translationResources;
|
||||
|
||||
/**
|
||||
* Map of all resources defined within the extension which are not already
|
||||
* associated as JavaScript, CSS, or translation resources, where each key
|
||||
* is the path to that resource within the extension.
|
||||
*/
|
||||
private final Map<String, Resource> staticResources;
|
||||
|
||||
/**
|
||||
* The collection of all AuthenticationProvider classes defined within the
|
||||
* extension.
|
||||
*/
|
||||
private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses;
|
||||
|
||||
/**
|
||||
* The resource for the small favicon for the extension. If provided, this
|
||||
* will replace the default Guacamole icon.
|
||||
*/
|
||||
private final Resource smallIcon;
|
||||
|
||||
/**
|
||||
* The resource foe the large favicon for the extension. If provided, this
|
||||
* will replace the default Guacamole icon.
|
||||
*/
|
||||
private final Resource largeIcon;
|
||||
|
||||
/**
|
||||
* Returns a new map of all resources corresponding to the collection of
|
||||
* paths provided. Each resource will be associated with the given
|
||||
* mimetype, and stored in the map using its path as the key.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype to associate with each resource.
|
||||
*
|
||||
* @param paths
|
||||
* The paths corresponding to the resources desired.
|
||||
*
|
||||
* @return
|
||||
* A new, unmodifiable map of resources corresponding to the
|
||||
* collection of paths provided, where the key of each entry in the
|
||||
* map is the path for the resource stored in that entry.
|
||||
*/
|
||||
private Map<String, Resource> getClassPathResources(String mimetype, Collection<String> paths) {
|
||||
|
||||
// If no paths are provided, just return an empty map
|
||||
if (paths == null)
|
||||
return Collections.<String, Resource>emptyMap();
|
||||
|
||||
// Add classpath resource for each path provided
|
||||
Map<String, Resource> resources = new HashMap<String, Resource>(paths.size());
|
||||
for (String path : paths)
|
||||
resources.put(path, new ClassPathResource(classLoader, mimetype, path));
|
||||
|
||||
// Callers should not rely on modifying the result
|
||||
return Collections.unmodifiableMap(resources);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new map of all resources corresponding to the map of resource
|
||||
* paths provided. Each resource will be associated with the mimetype
|
||||
* stored in the given map using its path as the key.
|
||||
*
|
||||
* @param resourceTypes
|
||||
* A map of all paths to their corresponding mimetypes.
|
||||
*
|
||||
* @return
|
||||
* A new, unmodifiable map of resources corresponding to the
|
||||
* collection of paths provided, where the key of each entry in the
|
||||
* map is the path for the resource stored in that entry.
|
||||
*/
|
||||
private Map<String, Resource> getClassPathResources(Map<String, String> resourceTypes) {
|
||||
|
||||
// If no paths are provided, just return an empty map
|
||||
if (resourceTypes == null)
|
||||
return Collections.<String, Resource>emptyMap();
|
||||
|
||||
// Add classpath resource for each path/mimetype pair provided
|
||||
Map<String, Resource> resources = new HashMap<String, Resource>(resourceTypes.size());
|
||||
for (Map.Entry<String, String> resource : resourceTypes.entrySet()) {
|
||||
|
||||
// Get path and mimetype from entry
|
||||
String path = resource.getKey();
|
||||
String mimetype = resource.getValue();
|
||||
|
||||
// Store as path/resource pair
|
||||
resources.put(path, new ClassPathResource(classLoader, mimetype, path));
|
||||
|
||||
}
|
||||
|
||||
// Callers should not rely on modifying the result
|
||||
return Collections.unmodifiableMap(resources);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the AuthenticationProvider subclass having the given name. If
|
||||
* the class having the given name does not exist or isn't actually a
|
||||
* subclass of AuthenticationProvider, an exception will be thrown.
|
||||
*
|
||||
* @param name
|
||||
* The name of the AuthenticationProvider class to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The subclass of AuthenticationProvider having the given name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If no such class exists, or if the class with the given name is not
|
||||
* a subclass of AuthenticationProvider.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom()
|
||||
private Class<AuthenticationProvider> getAuthenticationProviderClass(String name)
|
||||
throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Get authentication provider class
|
||||
Class<?> authenticationProviderClass = classLoader.loadClass(name);
|
||||
|
||||
// Verify the located class is actually a subclass of AuthenticationProvider
|
||||
if (!AuthenticationProvider.class.isAssignableFrom(authenticationProviderClass))
|
||||
throw new GuacamoleServerException("Authentication providers MUST extend the AuthenticationProvider class.");
|
||||
|
||||
// Return located class
|
||||
return (Class<AuthenticationProvider>) authenticationProviderClass;
|
||||
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new GuacamoleException("Authentication provider class not found.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new collection of all AuthenticationProvider subclasses having
|
||||
* the given names. If any class does not exist or isn't actually a
|
||||
* subclass of AuthenticationProvider, an exception will be thrown, and
|
||||
* no further AuthenticationProvider classes will be loaded.
|
||||
*
|
||||
* @param names
|
||||
* The names of the AuthenticationProvider classes to retrieve.
|
||||
*
|
||||
* @return
|
||||
* A new collection of all AuthenticationProvider subclasses having the
|
||||
* given names.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If any given class does not exist, or if any given class is not a
|
||||
* subclass of AuthenticationProvider.
|
||||
*/
|
||||
private Collection<Class<AuthenticationProvider>> getAuthenticationProviderClasses(Collection<String> names)
|
||||
throws GuacamoleException {
|
||||
|
||||
// If no classnames are provided, just return an empty list
|
||||
if (names == null)
|
||||
return Collections.<Class<AuthenticationProvider>>emptyList();
|
||||
|
||||
// Define all auth provider classes
|
||||
Collection<Class<AuthenticationProvider>> classes = new ArrayList<Class<AuthenticationProvider>>(names.size());
|
||||
for (String name : names)
|
||||
classes.add(getAuthenticationProviderClass(name));
|
||||
|
||||
// Callers should not rely on modifying the result
|
||||
return Collections.unmodifiableCollection(classes);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given file as an extension, which must be a .jar containing
|
||||
* a guac-manifest.json file describing its contents.
|
||||
*
|
||||
* @param parent
|
||||
* The classloader to use as the parent for the isolated classloader of
|
||||
* this extension.
|
||||
*
|
||||
* @param file
|
||||
* The file to load as an extension.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the provided file is not a .jar file, does not contain the
|
||||
* guac-manifest.json, or if guac-manifest.json is invalid and cannot
|
||||
* be parsed.
|
||||
*/
|
||||
public Extension(final ClassLoader parent, final File file) throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Open extension
|
||||
ZipFile extension = new ZipFile(file);
|
||||
|
||||
try {
|
||||
|
||||
// Retrieve extension manifest
|
||||
ZipEntry manifestEntry = extension.getEntry(MANIFEST_NAME);
|
||||
if (manifestEntry == null)
|
||||
throw new GuacamoleServerException("Extension " + file.getName() + " is missing " + MANIFEST_NAME);
|
||||
|
||||
// Parse manifest
|
||||
manifest = mapper.readValue(extension.getInputStream(manifestEntry), ExtensionManifest.class);
|
||||
if (manifest == null)
|
||||
throw new GuacamoleServerException("Contents of " + MANIFEST_NAME + " must be a valid JSON object.");
|
||||
|
||||
}
|
||||
|
||||
// Always close zip file, if possible
|
||||
finally {
|
||||
extension.close();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Create isolated classloader for this extension
|
||||
classLoader = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>() {
|
||||
|
||||
@Override
|
||||
public ClassLoader run() throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Classloader must contain only the extension itself
|
||||
return new URLClassLoader(new URL[]{file.toURI().toURL()}, parent);
|
||||
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new GuacamoleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Rethrow any GuacamoleException
|
||||
catch (PrivilegedActionException e) {
|
||||
throw (GuacamoleException) e.getException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Abort load if not a valid zip file
|
||||
catch (ZipException e) {
|
||||
throw new GuacamoleServerException("Extension is not a valid zip file: " + file.getName(), e);
|
||||
}
|
||||
|
||||
// Abort if manifest cannot be parsed (invalid JSON)
|
||||
catch (JsonParseException e) {
|
||||
throw new GuacamoleServerException(MANIFEST_NAME + " is not valid JSON: " + file.getName(), e);
|
||||
}
|
||||
|
||||
// Abort if zip file cannot be read at all due to I/O errors
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Unable to read extension: " + file.getName(), e);
|
||||
}
|
||||
|
||||
// Define static resources
|
||||
cssResources = getClassPathResources("text/css", manifest.getCSSPaths());
|
||||
javaScriptResources = getClassPathResources("text/javascript", manifest.getJavaScriptPaths());
|
||||
htmlResources = getClassPathResources("text/html", manifest.getHTMLPaths());
|
||||
translationResources = getClassPathResources("application/json", manifest.getTranslationPaths());
|
||||
staticResources = getClassPathResources(manifest.getResourceTypes());
|
||||
|
||||
// Define authentication providers
|
||||
authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
|
||||
|
||||
// Get small icon resource if provided
|
||||
if (manifest.getSmallIcon() != null)
|
||||
smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon());
|
||||
else
|
||||
smallIcon = null;
|
||||
|
||||
// Get large icon resource if provided
|
||||
if (manifest.getLargeIcon() != null)
|
||||
largeIcon = new ClassPathResource(classLoader, "image/png", manifest.getLargeIcon());
|
||||
else
|
||||
largeIcon = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the Guacamole web application for which this
|
||||
* extension was built.
|
||||
*
|
||||
* @return
|
||||
* The version of the Guacamole web application for which this
|
||||
* extension was built.
|
||||
*/
|
||||
public String getGuacamoleVersion() {
|
||||
return manifest.getGuacamoleVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this extension, as declared in the extension's
|
||||
* manifest.
|
||||
*
|
||||
* @return
|
||||
* The name of this extension.
|
||||
*/
|
||||
public String getName() {
|
||||
return manifest.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace of this extension, as declared in the extension's
|
||||
* manifest.
|
||||
*
|
||||
* @return
|
||||
* The namespace of this extension.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return manifest.getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all declared JavaScript resources associated with this
|
||||
* extension, where the key of each entry in the map is the path to that
|
||||
* resource within the extension .jar. JavaScript resources are declared
|
||||
* within the extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared JavaScript resources associated with this extension.
|
||||
*/
|
||||
public Map<String, Resource> getJavaScriptResources() {
|
||||
return javaScriptResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all declared CSS resources associated with this
|
||||
* extension, where the key of each entry in the map is the path to that
|
||||
* resource within the extension .jar. CSS resources are declared within
|
||||
* the extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared CSS resources associated with this extension.
|
||||
*/
|
||||
public Map<String, Resource> getCSSResources() {
|
||||
return cssResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all declared HTML patch resources associated with this
|
||||
* extension, where the key of each entry in the map is the path to that
|
||||
* resource within the extension .jar. HTML patch resources are declared
|
||||
* within the extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared HTML patch resources associated with this extension.
|
||||
*/
|
||||
public Map<String, Resource> getHTMLResources() {
|
||||
return htmlResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all declared translation resources associated with this
|
||||
* extension, where the key of each entry in the map is the path to that
|
||||
* resource within the extension .jar. Translation resources are declared
|
||||
* within the extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared translation resources associated with this extension.
|
||||
*/
|
||||
public Map<String, Resource> getTranslationResources() {
|
||||
return translationResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all declared resources associated with this extension,
|
||||
* where these resources are not already associated as JavaScript, CSS, or
|
||||
* translation resources. The key of each entry in the map is the path to
|
||||
* that resource within the extension .jar. Static resources are declared
|
||||
* within the extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared static resources associated with this extension.
|
||||
*/
|
||||
public Map<String, Resource> getStaticResources() {
|
||||
return staticResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all declared authentication providers classes associated with
|
||||
* this extension. Authentication providers are declared within the
|
||||
* extension manifest.
|
||||
*
|
||||
* @return
|
||||
* All declared authentication provider classes with this extension.
|
||||
*/
|
||||
public Collection<Class<AuthenticationProvider>> getAuthenticationProviderClasses() {
|
||||
return authenticationProviderClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource for the small favicon for the extension. If
|
||||
* provided, this will replace the default Guacamole icon.
|
||||
*
|
||||
* @return
|
||||
* The resource for the small favicon.
|
||||
*/
|
||||
public Resource getSmallIcon() {
|
||||
return smallIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource for the large favicon for the extension. If
|
||||
* provided, this will replace the default Guacamole icon.
|
||||
*
|
||||
* @return
|
||||
* The resource for the large favicon.
|
||||
*/
|
||||
public Resource getLargeIcon() {
|
||||
return largeIcon;
|
||||
}
|
||||
|
||||
}
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
|
||||
/**
|
||||
* Java representation of the JSON manifest contained within every Guacamole
|
||||
* extension, identifying an extension and describing its contents.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ExtensionManifest {
|
||||
|
||||
/**
|
||||
* The version of Guacamole for which this extension was built.
|
||||
* Compatibility rules built into the web application will guard against
|
||||
* incompatible extensions being loaded.
|
||||
*/
|
||||
private String guacamoleVersion;
|
||||
|
||||
/**
|
||||
* The name of the extension associated with this manifest. The extension
|
||||
* name is human-readable, and used for display purposes only.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The namespace of the extension associated with this manifest. The
|
||||
* extension namespace is required for internal use, and is used wherever
|
||||
* extension-specific files or resources need to be isolated from those of
|
||||
* other extensions.
|
||||
*/
|
||||
private String namespace;
|
||||
|
||||
/**
|
||||
* The paths of all JavaScript resources within the .jar of the extension
|
||||
* associated with this manifest.
|
||||
*/
|
||||
private Collection<String> javaScriptPaths;
|
||||
|
||||
/**
|
||||
* The paths of all CSS resources within the .jar of the extension
|
||||
* associated with this manifest.
|
||||
*/
|
||||
private Collection<String> cssPaths;
|
||||
|
||||
/**
|
||||
* The paths of all HTML patch resources within the .jar of the extension
|
||||
* associated with this manifest.
|
||||
*/
|
||||
private Collection<String> htmlPaths;
|
||||
|
||||
/**
|
||||
* The paths of all translation JSON files within this extension, if any.
|
||||
*/
|
||||
private Collection<String> translationPaths;
|
||||
|
||||
/**
|
||||
* The mimetypes of all resources within this extension which are not
|
||||
* already declared as JavaScript, CSS, or translation resources, if any.
|
||||
* The key of each entry is the resource path, while the value is the
|
||||
* corresponding mimetype.
|
||||
*/
|
||||
private Map<String, String> resourceTypes;
|
||||
|
||||
/**
|
||||
* The names of all authentication provider classes within this extension,
|
||||
* if any.
|
||||
*/
|
||||
private Collection<String> authProviders;
|
||||
|
||||
/**
|
||||
* The path to the small favicon. If provided, this will replace the default
|
||||
* Guacamole icon.
|
||||
*/
|
||||
private String smallIcon;
|
||||
|
||||
/**
|
||||
* The path to the large favicon. If provided, this will replace the default
|
||||
* Guacamole icon.
|
||||
*/
|
||||
private String largeIcon;
|
||||
|
||||
/**
|
||||
* Returns the version of the Guacamole web application for which the
|
||||
* extension was built, such as "0.9.7".
|
||||
*
|
||||
* @return
|
||||
* The version of the Guacamole web application for which the extension
|
||||
* was built.
|
||||
*/
|
||||
public String getGuacamoleVersion() {
|
||||
return guacamoleVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the version of the Guacamole web application for which the
|
||||
* extension was built, such as "0.9.7".
|
||||
*
|
||||
* @param guacamoleVersion
|
||||
* The version of the Guacamole web application for which the extension
|
||||
* was built.
|
||||
*/
|
||||
public void setGuacamoleVersion(String guacamoleVersion) {
|
||||
this.guacamoleVersion = guacamoleVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension associated with this manifest. The
|
||||
* name is human-readable, for display purposes only, and is defined within
|
||||
* the manifest by the "name" property.
|
||||
*
|
||||
* @return
|
||||
* The name of the extension associated with this manifest.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the extension associated with this manifest. The name
|
||||
* is human-readable, for display purposes only, and is defined within the
|
||||
* manifest by the "name" property.
|
||||
*
|
||||
* @param name
|
||||
* The name of the extension associated with this manifest.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace of the extension associated with this manifest.
|
||||
* The namespace is required for internal use, and is used wherever
|
||||
* extension-specific files or resources need to be isolated from those of
|
||||
* other extensions. It is defined within the manifest by the "namespace"
|
||||
* property.
|
||||
*
|
||||
* @return
|
||||
* The namespace of the extension associated with this manifest.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the namespace of the extension associated with this manifest. The
|
||||
* namespace is required for internal use, and is used wherever extension-
|
||||
* specific files or resources need to be isolated from those of other
|
||||
* extensions. It is defined within the manifest by the "namespace"
|
||||
* property.
|
||||
*
|
||||
* @param namespace
|
||||
* The namespace of the extension associated with this manifest.
|
||||
*/
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to all JavaScript resources within the extension.
|
||||
* These paths are defined within the manifest by the "js" property as an
|
||||
* array of strings, where each string is a path relative to the root of
|
||||
* the extension .jar.
|
||||
*
|
||||
* @return
|
||||
* A collection of paths to all JavaScript resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("js")
|
||||
public Collection<String> getJavaScriptPaths() {
|
||||
return javaScriptPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the paths to all JavaScript resources within the extension. These
|
||||
* paths are defined within the manifest by the "js" property as an array
|
||||
* of strings, where each string is a path relative to the root of the
|
||||
* extension .jar.
|
||||
*
|
||||
* @param javaScriptPaths
|
||||
* A collection of paths to all JavaScript resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("js")
|
||||
public void setJavaScriptPaths(Collection<String> javaScriptPaths) {
|
||||
this.javaScriptPaths = javaScriptPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to all CSS resources within the extension. These paths
|
||||
* are defined within the manifest by the "js" property as an array of
|
||||
* strings, where each string is a path relative to the root of the
|
||||
* extension .jar.
|
||||
*
|
||||
* @return
|
||||
* A collection of paths to all CSS resources within the extension.
|
||||
*/
|
||||
@JsonProperty("css")
|
||||
public Collection<String> getCSSPaths() {
|
||||
return cssPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the paths to all CSS resources within the extension. These paths
|
||||
* are defined within the manifest by the "js" property as an array of
|
||||
* strings, where each string is a path relative to the root of the
|
||||
* extension .jar.
|
||||
*
|
||||
* @param cssPaths
|
||||
* A collection of paths to all CSS resources within the extension.
|
||||
*/
|
||||
@JsonProperty("css")
|
||||
public void setCSSPaths(Collection<String> cssPaths) {
|
||||
this.cssPaths = cssPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to all HTML patch resources within the extension. These
|
||||
* paths are defined within the manifest by the "html" property as an array
|
||||
* of strings, where each string is a path relative to the root of the
|
||||
* extension .jar.
|
||||
*
|
||||
* @return
|
||||
* A collection of paths to all HTML patch resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("html")
|
||||
public Collection<String> getHTMLPaths() {
|
||||
return htmlPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the paths to all HTML patch resources within the extension. These
|
||||
* paths are defined within the manifest by the "html" property as an array
|
||||
* of strings, where each string is a path relative to the root of the
|
||||
* extension .jar.
|
||||
*
|
||||
* @param htmlPatchPaths
|
||||
* A collection of paths to all HTML patch resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("html")
|
||||
public void setHTMLPaths(Collection<String> htmlPatchPaths) {
|
||||
this.htmlPaths = htmlPatchPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to all translation resources within the extension.
|
||||
* These paths are defined within the manifest by the "translations"
|
||||
* property as an array of strings, where each string is a path relative to
|
||||
* the root of the extension .jar.
|
||||
*
|
||||
* @return
|
||||
* A collection of paths to all translation resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("translations")
|
||||
public Collection<String> getTranslationPaths() {
|
||||
return translationPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the paths to all translation resources within the extension. These
|
||||
* paths are defined within the manifest by the "translations" property as
|
||||
* an array of strings, where each string is a path relative to the root of
|
||||
* the extension .jar.
|
||||
*
|
||||
* @param translationPaths
|
||||
* A collection of paths to all translation resources within the
|
||||
* extension.
|
||||
*/
|
||||
@JsonProperty("translations")
|
||||
public void setTranslationPaths(Collection<String> translationPaths) {
|
||||
this.translationPaths = translationPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all resources to their corresponding mimetypes, for all
|
||||
* resources not already declared as JavaScript, CSS, or translation
|
||||
* resources. These paths and corresponding types are defined within the
|
||||
* manifest by the "resources" property as an object, where each property
|
||||
* name is a path relative to the root of the extension .jar, and each
|
||||
* value is a mimetype.
|
||||
*
|
||||
* @return
|
||||
* A map of all resources within the extension to their corresponding
|
||||
* mimetypes.
|
||||
*/
|
||||
@JsonProperty("resources")
|
||||
public Map<String, String> getResourceTypes() {
|
||||
return resourceTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map of all resources to their corresponding mimetypes, for all
|
||||
* resources not already declared as JavaScript, CSS, or translation
|
||||
* resources. These paths and corresponding types are defined within the
|
||||
* manifest by the "resources" property as an object, where each property
|
||||
* name is a path relative to the root of the extension .jar, and each
|
||||
* value is a mimetype.
|
||||
*
|
||||
* @param resourceTypes
|
||||
* A map of all resources within the extension to their corresponding
|
||||
* mimetypes.
|
||||
*/
|
||||
@JsonProperty("resources")
|
||||
public void setResourceTypes(Map<String, String> resourceTypes) {
|
||||
this.resourceTypes = resourceTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the classnames of all authentication provider classes within the
|
||||
* extension. These classnames are defined within the manifest by the
|
||||
* "authProviders" property as an array of strings, where each string is an
|
||||
* authentication provider classname.
|
||||
*
|
||||
* @return
|
||||
* A collection of classnames of all authentication providers within
|
||||
* the extension.
|
||||
*/
|
||||
public Collection<String> getAuthProviders() {
|
||||
return authProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the classnames of all authentication provider classes within the
|
||||
* extension. These classnames are defined within the manifest by the
|
||||
* "authProviders" property as an array of strings, where each string is an
|
||||
* authentication provider classname.
|
||||
*
|
||||
* @param authProviders
|
||||
* A collection of classnames of all authentication providers within
|
||||
* the extension.
|
||||
*/
|
||||
public void setAuthProviders(Collection<String> authProviders) {
|
||||
this.authProviders = authProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the small favicon, relative to the root of the
|
||||
* extension.
|
||||
*
|
||||
* @return
|
||||
* The path to the small favicon.
|
||||
*/
|
||||
public String getSmallIcon() {
|
||||
return smallIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the small favicon. This will replace the default
|
||||
* Guacamole icon.
|
||||
*
|
||||
* @param smallIcon
|
||||
* The path to the small favicon.
|
||||
*/
|
||||
public void setSmallIcon(String smallIcon) {
|
||||
this.smallIcon = smallIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the large favicon, relative to the root of the
|
||||
* extension.
|
||||
*
|
||||
* @return
|
||||
* The path to the large favicon.
|
||||
*/
|
||||
public String getLargeIcon() {
|
||||
return largeIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the large favicon. This will replace the default
|
||||
* Guacamole icon.
|
||||
*
|
||||
* @param largeIcon
|
||||
* The path to the large favicon.
|
||||
*/
|
||||
public void setLargeIcon(String largeIcon) {
|
||||
this.largeIcon = largeIcon;
|
||||
}
|
||||
|
||||
}
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
||||
import org.apache.guacamole.net.basic.resource.Resource;
|
||||
import org.apache.guacamole.net.basic.resource.ResourceServlet;
|
||||
import org.apache.guacamole.net.basic.resource.SequenceResource;
|
||||
import org.apache.guacamole.net.basic.resource.WebApplicationResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A Guice Module which loads all extensions within the
|
||||
* GUACAMOLE_HOME/extensions directory, if any.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ExtensionModule extends ServletModule {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class);
|
||||
|
||||
/**
|
||||
* The version strings of all Guacamole versions whose extensions are
|
||||
* compatible with this release.
|
||||
*/
|
||||
private static final List<String> ALLOWED_GUACAMOLE_VERSIONS =
|
||||
Collections.unmodifiableList(Arrays.asList(
|
||||
"*",
|
||||
"0.9.9"
|
||||
));
|
||||
|
||||
/**
|
||||
* The name of the directory within GUACAMOLE_HOME containing any .jars
|
||||
* which should be included in the classpath of all extensions.
|
||||
*/
|
||||
private static final String LIB_DIRECTORY = "lib";
|
||||
|
||||
/**
|
||||
* The name of the directory within GUACAMOLE_HOME containing all
|
||||
* extensions.
|
||||
*/
|
||||
private static final String EXTENSIONS_DIRECTORY = "extensions";
|
||||
|
||||
/**
|
||||
* The string that the filenames of all extensions must end with to be
|
||||
* recognized as extensions.
|
||||
*/
|
||||
private static final String EXTENSION_SUFFIX = ".jar";
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* All currently-bound authentication providers, if any.
|
||||
*/
|
||||
private final List<AuthenticationProvider> boundAuthenticationProviders =
|
||||
new ArrayList<AuthenticationProvider>();
|
||||
|
||||
/**
|
||||
* Service for adding and retrieving language resources.
|
||||
*/
|
||||
private final LanguageResourceService languageResourceService;
|
||||
|
||||
/**
|
||||
* Service for adding and retrieving HTML patch resources.
|
||||
*/
|
||||
private final PatchResourceService patchResourceService;
|
||||
|
||||
/**
|
||||
* Returns the classloader that should be used as the parent classloader
|
||||
* for all extensions. If the GUACAMOLE_HOME/lib directory exists, this
|
||||
* will be a classloader that loads classes from within the .jar files in
|
||||
* that directory. Lacking the GUACAMOLE_HOME/lib directory, this will
|
||||
* simply be the classloader associated with the ExtensionModule class.
|
||||
*
|
||||
* @return
|
||||
* The classloader that should be used as the parent classloader for
|
||||
* all extensions.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the classloader.
|
||||
*/
|
||||
private ClassLoader getParentClassLoader() throws GuacamoleException {
|
||||
|
||||
// Retrieve lib directory
|
||||
File libDir = new File(environment.getGuacamoleHome(), LIB_DIRECTORY);
|
||||
|
||||
// If lib directory does not exist, use default class loader
|
||||
if (!libDir.isDirectory())
|
||||
return ExtensionModule.class.getClassLoader();
|
||||
|
||||
// Return classloader which loads classes from all .jars within the lib directory
|
||||
return DirectoryClassLoader.getInstance(libDir);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a module which loads all extensions within the
|
||||
* GUACAMOLE_HOME/extensions directory.
|
||||
*
|
||||
* @param environment
|
||||
* The environment to use when configuring authentication.
|
||||
*/
|
||||
public ExtensionModule(Environment environment) {
|
||||
this.environment = environment;
|
||||
this.languageResourceService = new LanguageResourceService(environment);
|
||||
this.patchResourceService = new PatchResourceService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of the now-deprecated "auth-provider" property from
|
||||
* guacamole.properties, returning the corresponding AuthenticationProvider
|
||||
* class. If no authentication provider could be read, or the property is
|
||||
* not present, null is returned.
|
||||
*
|
||||
* As this property is deprecated, this function will also log warning
|
||||
* messages if the property is actually specified.
|
||||
*
|
||||
* @return
|
||||
* The value of the deprecated "auth-provider" property, or null if the
|
||||
* property is not present.
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // We must continue to use this property until it is truly no longer supported
|
||||
private Class<AuthenticationProvider> getAuthProviderProperty() {
|
||||
|
||||
// Get and bind auth provider instance, if defined via property
|
||||
try {
|
||||
|
||||
// Use "auth-provider" property if present, but warn about deprecation
|
||||
Class<AuthenticationProvider> authenticationProvider = environment.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
|
||||
if (authenticationProvider != null)
|
||||
logger.warn("The \"auth-provider\" and \"lib-directory\" properties are now deprecated. Please use the \"extensions\" and \"lib\" directories within GUACAMOLE_HOME instead.");
|
||||
|
||||
return authenticationProvider;
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.warn("Value of deprecated \"auth-provider\" property within guacamole.properties is not valid: {}", e.getMessage());
|
||||
logger.debug("Error reading authentication provider from guacamole.properties.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given AuthenticationProvider class such that any service
|
||||
* requiring access to the AuthenticationProvider can obtain it via
|
||||
* injection, along with any other bound AuthenticationProviders.
|
||||
*
|
||||
* @param authenticationProvider
|
||||
* The AuthenticationProvider class to bind.
|
||||
*/
|
||||
private void bindAuthenticationProvider(Class<? extends AuthenticationProvider> authenticationProvider) {
|
||||
|
||||
// Bind authentication provider
|
||||
logger.debug("[{}] Binding AuthenticationProvider \"{}\".",
|
||||
boundAuthenticationProviders.size(), authenticationProvider.getName());
|
||||
boundAuthenticationProviders.add(new AuthenticationProviderFacade(authenticationProvider));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds each of the the given AuthenticationProvider classes such that any
|
||||
* service requiring access to the AuthenticationProvider can obtain it via
|
||||
* injection.
|
||||
*
|
||||
* @param authProviders
|
||||
* The AuthenticationProvider classes to bind.
|
||||
*/
|
||||
private void bindAuthenticationProviders(Collection<Class<AuthenticationProvider>> authProviders) {
|
||||
|
||||
// Bind each authentication provider within extension
|
||||
for (Class<AuthenticationProvider> authenticationProvider : authProviders)
|
||||
bindAuthenticationProvider(authenticationProvider);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all currently-bound AuthenticationProvider instances.
|
||||
*
|
||||
* @return
|
||||
* A List of all currently-bound AuthenticationProvider. The List is
|
||||
* not modifiable.
|
||||
*/
|
||||
@Provides
|
||||
public List<AuthenticationProvider> getAuthenticationProviders() {
|
||||
return Collections.unmodifiableList(boundAuthenticationProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves each of the given resources as a language resource. Language
|
||||
* resources are served from within the "/translations" directory as JSON
|
||||
* files, where the name of each JSON file is the language key.
|
||||
*
|
||||
* @param resources
|
||||
* A map of all language resources to serve, where the key of each
|
||||
* entry in the language key from which the name of the JSON file will
|
||||
* be derived.
|
||||
*/
|
||||
private void serveLanguageResources(Map<String, Resource> resources) {
|
||||
|
||||
// Add all resources to language resource service
|
||||
for (Map.Entry<String, Resource> translationResource : resources.entrySet()) {
|
||||
|
||||
// Get path and resource from path/resource pair
|
||||
String path = translationResource.getKey();
|
||||
Resource resource = translationResource.getValue();
|
||||
|
||||
// Derive key from path
|
||||
String languageKey = languageResourceService.getLanguageKey(path);
|
||||
if (languageKey == null) {
|
||||
logger.warn("Invalid language file name: \"{}\"", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add language resource
|
||||
languageResourceService.addLanguageResource(languageKey, resource);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves each of the given resources under the given prefix. The path of
|
||||
* each resource relative to the prefix is the key of its entry within the
|
||||
* map.
|
||||
*
|
||||
* @param prefix
|
||||
* The prefix under which each resource should be served.
|
||||
*
|
||||
* @param resources
|
||||
* A map of all resources to serve, where the key of each entry in the
|
||||
* map is the desired path of that resource relative to the prefix.
|
||||
*/
|
||||
private void serveStaticResources(String prefix, Map<String, Resource> resources) {
|
||||
|
||||
// Add all resources under given prefix
|
||||
for (Map.Entry<String, Resource> staticResource : resources.entrySet()) {
|
||||
|
||||
// Get path and resource from path/resource pair
|
||||
String path = staticResource.getKey();
|
||||
Resource resource = staticResource.getValue();
|
||||
|
||||
// Serve within namespace-derived path
|
||||
serve(prefix + path).with(new ResourceServlet(resource));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given version of Guacamole is compatible with this
|
||||
* version of Guacamole as far as extensions are concerned.
|
||||
*
|
||||
* @param guacamoleVersion
|
||||
* The version of Guacamole the extension was built for.
|
||||
*
|
||||
* @return
|
||||
* true if the given version of Guacamole is compatible with this
|
||||
* version of Guacamole, false otherwise.
|
||||
*/
|
||||
private boolean isCompatible(String guacamoleVersion) {
|
||||
return ALLOWED_GUACAMOLE_VERSIONS.contains(guacamoleVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all extensions within the GUACAMOLE_HOME/extensions directory, if
|
||||
* any, adding their static resource to the given resoure collections.
|
||||
*
|
||||
* @param javaScriptResources
|
||||
* A modifiable collection of static JavaScript resources which may
|
||||
* receive new JavaScript resources from extensions.
|
||||
*
|
||||
* @param cssResources
|
||||
* A modifiable collection of static CSS resources which may receive
|
||||
* new CSS resources from extensions.
|
||||
*/
|
||||
private void loadExtensions(Collection<Resource> javaScriptResources,
|
||||
Collection<Resource> cssResources) {
|
||||
|
||||
// Retrieve and validate extensions directory
|
||||
File extensionsDir = new File(environment.getGuacamoleHome(), EXTENSIONS_DIRECTORY);
|
||||
if (!extensionsDir.isDirectory())
|
||||
return;
|
||||
|
||||
// Retrieve list of all extension files within extensions directory
|
||||
File[] extensionFiles = extensionsDir.listFiles(new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isFile() && file.getName().endsWith(EXTENSION_SUFFIX);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Verify contents are accessible
|
||||
if (extensionFiles == null) {
|
||||
logger.warn("Although GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " exists, its contents cannot be read.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort files lexicographically
|
||||
Arrays.sort(extensionFiles);
|
||||
|
||||
// Load each extension within the extension directory
|
||||
for (File extensionFile : extensionFiles) {
|
||||
|
||||
logger.debug("Loading extension: \"{}\"", extensionFile.getName());
|
||||
|
||||
try {
|
||||
|
||||
// Load extension from file
|
||||
Extension extension = new Extension(getParentClassLoader(), extensionFile);
|
||||
|
||||
// Validate Guacamole version of extension
|
||||
if (!isCompatible(extension.getGuacamoleVersion())) {
|
||||
logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.",
|
||||
extension.getGuacamoleVersion(), extensionFile.getName());
|
||||
throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not "
|
||||
+ "compatible with this version of Guacamole.");
|
||||
}
|
||||
|
||||
// Add any JavaScript / CSS resources
|
||||
javaScriptResources.addAll(extension.getJavaScriptResources().values());
|
||||
cssResources.addAll(extension.getCSSResources().values());
|
||||
|
||||
// Attempt to load all authentication providers
|
||||
bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
|
||||
|
||||
// Add any translation resources
|
||||
serveLanguageResources(extension.getTranslationResources());
|
||||
|
||||
// Add all HTML patch resources
|
||||
patchResourceService.addPatchResources(extension.getHTMLResources().values());
|
||||
|
||||
// Add all static resources under namespace-derived prefix
|
||||
String staticResourcePrefix = "/app/ext/" + extension.getNamespace() + "/";
|
||||
serveStaticResources(staticResourcePrefix, extension.getStaticResources());
|
||||
|
||||
// Serve up the small favicon if provided
|
||||
if(extension.getSmallIcon() != null)
|
||||
serve("/images/logo-64.png").with(new ResourceServlet(extension.getSmallIcon()));
|
||||
|
||||
// Serve up the large favicon if provided
|
||||
if(extension.getLargeIcon()!= null)
|
||||
serve("/images/logo-144.png").with(new ResourceServlet(extension.getLargeIcon()));
|
||||
|
||||
// Log successful loading of extension by name
|
||||
logger.info("Extension \"{}\" loaded.", extension.getName());
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Extension \"{}\" could not be loaded: {}", extensionFile.getName(), e.getMessage());
|
||||
logger.debug("Unable to load extension.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
// Bind resource services
|
||||
bind(LanguageResourceService.class).toInstance(languageResourceService);
|
||||
bind(PatchResourceService.class).toInstance(patchResourceService);
|
||||
|
||||
// Load initial language resources from servlet context
|
||||
languageResourceService.addLanguageResources(getServletContext());
|
||||
|
||||
// Load authentication provider from guacamole.properties for sake of backwards compatibility
|
||||
Class<AuthenticationProvider> authProviderProperty = getAuthProviderProperty();
|
||||
if (authProviderProperty != null)
|
||||
bindAuthenticationProvider(authProviderProperty);
|
||||
|
||||
// Init JavaScript resources with base guacamole.min.js
|
||||
Collection<Resource> javaScriptResources = new ArrayList<Resource>();
|
||||
javaScriptResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.js"));
|
||||
|
||||
// Init CSS resources with base guacamole.min.css
|
||||
Collection<Resource> cssResources = new ArrayList<Resource>();
|
||||
cssResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.css"));
|
||||
|
||||
// Load all extensions
|
||||
loadExtensions(javaScriptResources, cssResources);
|
||||
|
||||
// Always bind basic auth last
|
||||
bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
|
||||
|
||||
// Dynamically generate app.js and app.css from extensions
|
||||
serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources)));
|
||||
serve("/app.css").with(new ResourceServlet(new SequenceResource(cssResources)));
|
||||
|
||||
// Dynamically serve all language resources
|
||||
for (Map.Entry<String, Resource> entry : languageResourceService.getLanguageResources().entrySet()) {
|
||||
|
||||
// Get language key/resource pair
|
||||
String languageKey = entry.getKey();
|
||||
Resource resource = entry.getValue();
|
||||
|
||||
// Serve resource within /translations
|
||||
serve("/translations/" + languageKey + ".json").with(new ResourceServlet(resource));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.ServletContext;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.node.JsonNodeFactory;
|
||||
import org.codehaus.jackson.node.ObjectNode;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
||||
import org.apache.guacamole.net.basic.resource.ByteArrayResource;
|
||||
import org.apache.guacamole.net.basic.resource.Resource;
|
||||
import org.apache.guacamole.net.basic.resource.WebApplicationResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service which provides access to all built-in languages as resources, and
|
||||
* allows other resources to be added or overlaid against existing resources.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class LanguageResourceService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(LanguageResourceService.class);
|
||||
|
||||
/**
|
||||
* The path to the translation folder within the webapp.
|
||||
*/
|
||||
private static final String TRANSLATION_PATH = "/translations";
|
||||
|
||||
/**
|
||||
* The JSON property for the human readable display name.
|
||||
*/
|
||||
private static final String LANGUAGE_DISPLAY_NAME_KEY = "NAME";
|
||||
|
||||
/**
|
||||
* The Jackson parser for parsing the language JSON files.
|
||||
*/
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* The regular expression to use for parsing the language key from the
|
||||
* filename.
|
||||
*/
|
||||
private static final Pattern LANGUAGE_KEY_PATTERN = Pattern.compile(".*/([a-z]+(_[A-Z]+)?)\\.json");
|
||||
|
||||
/**
|
||||
* The set of all language keys which are explicitly listed as allowed
|
||||
* within guacamole.properties, or null if all defined languages should be
|
||||
* allowed.
|
||||
*/
|
||||
private final Set<String> allowedLanguages;
|
||||
|
||||
/**
|
||||
* Map of all language resources by language key. Language keys are
|
||||
* language and country code pairs, separated by an underscore, like
|
||||
* "en_US". The country code and underscore SHOULD be omitted in the case
|
||||
* that only one dialect of that language is defined, or in the case of the
|
||||
* most universal or well-supported of all supported dialects of that
|
||||
* language.
|
||||
*/
|
||||
private final Map<String, Resource> resources = new HashMap<String, Resource>();
|
||||
|
||||
/**
|
||||
* Creates a new service for tracking and parsing available translations
|
||||
* which reads its configuration from the given environment.
|
||||
*
|
||||
* @param environment
|
||||
* The environment from which the configuration properties of this
|
||||
* service should be read.
|
||||
*/
|
||||
public LanguageResourceService(Environment environment) {
|
||||
|
||||
Set<String> parsedAllowedLanguages;
|
||||
|
||||
// Parse list of available languages from properties
|
||||
try {
|
||||
parsedAllowedLanguages = environment.getProperty(BasicGuacamoleProperties.ALLOWED_LANGUAGES);
|
||||
logger.debug("Available languages will be restricted to: {}", parsedAllowedLanguages);
|
||||
}
|
||||
|
||||
// Warn of failure to parse
|
||||
catch (GuacamoleException e) {
|
||||
parsedAllowedLanguages = null;
|
||||
logger.error("Unable to parse list of allowed languages: {}", e.getMessage());
|
||||
logger.debug("Error parsing list of allowed languages.", e);
|
||||
}
|
||||
|
||||
this.allowedLanguages = parsedAllowedLanguages;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a language key from the filename within the given path, if
|
||||
* possible. If the filename is not a valid language key, null is returned.
|
||||
*
|
||||
* @param path
|
||||
* The path containing the filename to derive the language key from.
|
||||
*
|
||||
* @return
|
||||
* The derived language key, or null if the filename is not a valid
|
||||
* language key.
|
||||
*/
|
||||
public String getLanguageKey(String path) {
|
||||
|
||||
// Parse language key from filename
|
||||
Matcher languageKeyMatcher = LANGUAGE_KEY_PATTERN.matcher(path);
|
||||
if (!languageKeyMatcher.matches())
|
||||
return null;
|
||||
|
||||
// Return parsed key
|
||||
return languageKeyMatcher.group(1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given JSON objects. Any leaf node in overlay will overwrite
|
||||
* the corresponding path in original.
|
||||
*
|
||||
* @param original
|
||||
* The original JSON object to which changes should be applied.
|
||||
*
|
||||
* @param overlay
|
||||
* The JSON object containing changes that should be applied.
|
||||
*
|
||||
* @return
|
||||
* The newly constructed JSON object that is the result of merging
|
||||
* original and overlay.
|
||||
*/
|
||||
private JsonNode mergeTranslations(JsonNode original, JsonNode overlay) {
|
||||
|
||||
// If we are at a leaf node, the result of merging is simply the overlay
|
||||
if (!overlay.isObject() || original == null)
|
||||
return overlay;
|
||||
|
||||
// Create mutable copy of original
|
||||
ObjectNode newNode = JsonNodeFactory.instance.objectNode();
|
||||
Iterator<String> fieldNames = original.getFieldNames();
|
||||
while (fieldNames.hasNext()) {
|
||||
String fieldName = fieldNames.next();
|
||||
newNode.put(fieldName, original.get(fieldName));
|
||||
}
|
||||
|
||||
// Merge each field
|
||||
fieldNames = overlay.getFieldNames();
|
||||
while (fieldNames.hasNext()) {
|
||||
String fieldName = fieldNames.next();
|
||||
newNode.put(fieldName, mergeTranslations(original.get(fieldName), overlay.get(fieldName)));
|
||||
}
|
||||
|
||||
return newNode;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given language resource, returning the resulting JsonNode.
|
||||
* If the resource cannot be read because it does not exist, null is
|
||||
* returned.
|
||||
*
|
||||
* @param resource
|
||||
* The language resource to parse. Language resources must have the
|
||||
* mimetype "application/json".
|
||||
*
|
||||
* @return
|
||||
* A JsonNode representing the root of the parsed JSON tree, or null if
|
||||
* the given resource does not exist.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an error occurs while parsing the resource as JSON.
|
||||
*/
|
||||
private JsonNode parseLanguageResource(Resource resource) throws IOException {
|
||||
|
||||
// Get resource stream
|
||||
InputStream stream = resource.asStream();
|
||||
if (stream == null)
|
||||
return null;
|
||||
|
||||
// Parse JSON tree
|
||||
try {
|
||||
JsonNode tree = mapper.readTree(stream);
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Ensure stream is always closed
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a language having the given key should be allowed to be
|
||||
* loaded. If language availability restrictions are imposed through
|
||||
* guacamole.properties, this may return false in some cases. By default,
|
||||
* this function will always return true. Note that just because a language
|
||||
* key is allowed to be loaded does not imply that the language key is
|
||||
* valid.
|
||||
*
|
||||
* @param languageKey
|
||||
* The language key of the language to test.
|
||||
*
|
||||
* @return
|
||||
* true if the given language key should be allowed to be loaded, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean isLanguageAllowed(String languageKey) {
|
||||
|
||||
// If no list is provided, all languages are implicitly available
|
||||
if (allowedLanguages == null)
|
||||
return true;
|
||||
|
||||
return allowedLanguages.contains(languageKey);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overlays the given language resource, which need not exist in
|
||||
* the ServletContext. If a language resource is already defined for the
|
||||
* given language key, the strings from the given resource will be overlaid
|
||||
* on top of the existing strings, augmenting or overriding the available
|
||||
* strings for that language.
|
||||
*
|
||||
* @param key
|
||||
* The language key of the resource being added. Language keys are
|
||||
* pairs consisting of a language code followed by an underscore and
|
||||
* country code, such as "en_US".
|
||||
*
|
||||
* @param resource
|
||||
* The language resource to add. This resource must have the mimetype
|
||||
* "application/json".
|
||||
*/
|
||||
public void addLanguageResource(String key, Resource resource) {
|
||||
|
||||
// Skip loading of language if not allowed
|
||||
if (!isLanguageAllowed(key)) {
|
||||
logger.debug("OMITTING language: \"{}\"", key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge language resources if already defined
|
||||
Resource existing = resources.get(key);
|
||||
if (existing != null) {
|
||||
|
||||
try {
|
||||
|
||||
// Read the original language resource
|
||||
JsonNode existingTree = parseLanguageResource(existing);
|
||||
if (existingTree == null) {
|
||||
logger.warn("Base language resource \"{}\" does not exist.", key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read new language resource
|
||||
JsonNode resourceTree = parseLanguageResource(resource);
|
||||
if (resourceTree == null) {
|
||||
logger.warn("Overlay language resource \"{}\" does not exist.", key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge the language resources
|
||||
JsonNode mergedTree = mergeTranslations(existingTree, resourceTree);
|
||||
resources.put(key, new ByteArrayResource("application/json", mapper.writeValueAsBytes(mergedTree)));
|
||||
|
||||
logger.debug("Merged strings with existing language: \"{}\"", key);
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Unable to merge language resource \"{}\": {}", key, e.getMessage());
|
||||
logger.debug("Error merging language resource.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, add new language resource
|
||||
else {
|
||||
resources.put(key, resource);
|
||||
logger.debug("Added language: \"{}\"", key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overlays all languages defined within the /translations
|
||||
* directory of the given ServletContext. If no such language files exist,
|
||||
* nothing is done. If a language is already defined, the strings from the
|
||||
* will be overlaid on top of the existing strings, augmenting or
|
||||
* overriding the available strings for that language. The language key
|
||||
* for each language file is derived from the filename.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext from which language files should be loaded.
|
||||
*/
|
||||
public void addLanguageResources(ServletContext context) {
|
||||
|
||||
// Get the paths of all the translation files
|
||||
Set<?> resourcePaths = context.getResourcePaths(TRANSLATION_PATH);
|
||||
|
||||
// If no translation files found, nothing to add
|
||||
if (resourcePaths == null)
|
||||
return;
|
||||
|
||||
// Iterate through all the found language files and add them to the map
|
||||
for (Object resourcePathObject : resourcePaths) {
|
||||
|
||||
// Each resource path is guaranteed to be a string
|
||||
String resourcePath = (String) resourcePathObject;
|
||||
|
||||
// Parse language key from path
|
||||
String languageKey = getLanguageKey(resourcePath);
|
||||
if (languageKey == null) {
|
||||
logger.warn("Invalid language file name: \"{}\"", resourcePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add/overlay new resource
|
||||
addLanguageResource(
|
||||
languageKey,
|
||||
new WebApplicationResource(context, "application/json", resourcePath)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all unique language keys currently associated with
|
||||
* language resources stored in this service. The returned set cannot be
|
||||
* modified.
|
||||
*
|
||||
* @return
|
||||
* A set of all unique language keys currently associated with this
|
||||
* service.
|
||||
*/
|
||||
public Set<String> getLanguageKeys() {
|
||||
return Collections.unmodifiableSet(resources.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all languages currently associated with this service,
|
||||
* where the key of each map entry is the language key. The returned map
|
||||
* cannot be modified.
|
||||
*
|
||||
* @return
|
||||
* A map of all languages currently associated with this service.
|
||||
*/
|
||||
public Map<String, Resource> getLanguageResources() {
|
||||
return Collections.unmodifiableMap(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping of all language keys to their corresponding human-
|
||||
* readable language names. If an error occurs while parsing a language
|
||||
* resource, its key/name pair will simply be omitted. The returned map
|
||||
* cannot be modified.
|
||||
*
|
||||
* @return
|
||||
* A map of all language keys and their corresponding human-readable
|
||||
* names.
|
||||
*/
|
||||
public Map<String, String> getLanguageNames() {
|
||||
|
||||
Map<String, String> languageNames = new HashMap<String, String>();
|
||||
|
||||
// For each language key/resource pair
|
||||
for (Map.Entry<String, Resource> entry : resources.entrySet()) {
|
||||
|
||||
// Get language key and resource
|
||||
String languageKey = entry.getKey();
|
||||
Resource resource = entry.getValue();
|
||||
|
||||
// Get stream for resource
|
||||
InputStream resourceStream = resource.asStream();
|
||||
if (resourceStream == null) {
|
||||
logger.warn("Expected language resource does not exist: \"{}\".", languageKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get name node of language
|
||||
try {
|
||||
JsonNode tree = mapper.readTree(resourceStream);
|
||||
JsonNode nameNode = tree.get(LANGUAGE_DISPLAY_NAME_KEY);
|
||||
|
||||
// Attempt to read language name from node
|
||||
String languageName;
|
||||
if (nameNode == null || (languageName = nameNode.getTextValue()) == null) {
|
||||
logger.warn("Root-level \"" + LANGUAGE_DISPLAY_NAME_KEY + "\" string missing or invalid in language \"{}\"", languageKey);
|
||||
languageName = languageKey;
|
||||
}
|
||||
|
||||
// Add language key/name pair to map
|
||||
languageNames.put(languageKey, languageName);
|
||||
|
||||
}
|
||||
|
||||
// Continue with next language if unable to read
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to read language resource \"{}\".", languageKey);
|
||||
logger.debug("Error reading language resource.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(languageNames);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.apache.guacamole.net.basic.extension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.net.basic.resource.Resource;
|
||||
|
||||
/**
|
||||
* Service which provides access to all HTML patches as resources, and allows
|
||||
* other patch resources to be added.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class PatchResourceService {
|
||||
|
||||
/**
|
||||
* A list of all HTML patch resources currently defined, in the order they
|
||||
* should be applied.
|
||||
*/
|
||||
private final List<Resource> resources = new ArrayList<Resource>();
|
||||
|
||||
/**
|
||||
* Adds the given HTML patch resource such that it will apply to the
|
||||
* Guacamole UI. The patch will be applied by the JavaScript side of the
|
||||
* web application in the order that addPatchResource() is invoked.
|
||||
*
|
||||
* @param resource
|
||||
* The HTML patch resource to add. This resource must have the mimetype
|
||||
* "text/html".
|
||||
*/
|
||||
public void addPatchResource(Resource resource) {
|
||||
resources.add(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given HTML patch resources such that they will apply to the
|
||||
* Guacamole UI. The patches will be applied by the JavaScript side of the
|
||||
* web application in the order provided.
|
||||
*
|
||||
* @param resources
|
||||
* The HTML patch resources to add. Each resource must have the
|
||||
* mimetype "text/html".
|
||||
*/
|
||||
public void addPatchResources(Collection<Resource> resources) {
|
||||
for (Resource resource : resources)
|
||||
addPatchResource(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all HTML patches currently associated with this
|
||||
* service, in the order they should be applied. The returned list cannot
|
||||
* be modified.
|
||||
*
|
||||
* @return
|
||||
* A list of all HTML patches currently associated with this service.
|
||||
*/
|
||||
public List<Resource> getPatchResources() {
|
||||
return Collections.unmodifiableList(resources);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 which represent and facilitate the loading of extensions to the
|
||||
* Guacamole web application.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.extension;
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.log;
|
||||
|
||||
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 org.apache.guacamole.environment.Environment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Initializes the logging subsystem.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class LogModule extends AbstractModule {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(LogModule.class);
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* Creates a new LogModule which uses the given environment to determine
|
||||
* the logging configuration.
|
||||
*
|
||||
* @param environment
|
||||
* The environment to use when configuring logging.
|
||||
*/
|
||||
public LogModule(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
// Only load logback configuration if GUACAMOLE_HOME exists
|
||||
File guacamoleHome = environment.getGuacamoleHome();
|
||||
if (!guacamoleHome.isDirectory())
|
||||
return;
|
||||
|
||||
// Check for custom logback.xml
|
||||
File logbackConfiguration = new File(guacamoleHome, "logback.xml");
|
||||
if (!logbackConfiguration.exists())
|
||||
return;
|
||||
|
||||
logger.info("Loading logback configuration from \"{}\".", logbackConfiguration);
|
||||
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.reset();
|
||||
|
||||
try {
|
||||
|
||||
// Initialize logback
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
configurator.setContext(context);
|
||||
configurator.doConfigure(logbackConfiguration);
|
||||
|
||||
// Dump any errors that occur during logback init
|
||||
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
|
||||
|
||||
}
|
||||
catch (JoranException e) {
|
||||
logger.error("Initialization of logback failed: {}", e.getMessage());
|
||||
logger.debug("Unable to load logback configuration..", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes specific to the general-purpose web application implemented by
|
||||
* the Guacamole project using the Guacamole APIs.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic;
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.properties;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.properties.GuacamoleProperty;
|
||||
|
||||
/**
|
||||
* A GuacamoleProperty whose value is the name of a class to use to
|
||||
* authenticate users. This class must implement AuthenticationProvider. Use
|
||||
* of this property type is deprecated in favor of the
|
||||
* GUACAMOLE_HOME/extensions directory.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AuthenticationProviderProperty implements GuacamoleProperty<Class<AuthenticationProvider>> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // Explicitly checked within by isAssignableFrom()
|
||||
public Class<AuthenticationProvider> parseValue(String authProviderClassName) throws GuacamoleException {
|
||||
|
||||
// If no property provided, return null.
|
||||
if (authProviderClassName == null)
|
||||
return null;
|
||||
|
||||
// Get auth provider instance
|
||||
try {
|
||||
|
||||
// Get authentication provider class
|
||||
Class<?> authProviderClass = org.apache.guacamole.net.basic.GuacamoleClassLoader.getInstance().loadClass(authProviderClassName);
|
||||
|
||||
// Verify the located class is actually a subclass of AuthenticationProvider
|
||||
if (!AuthenticationProvider.class.isAssignableFrom(authProviderClass))
|
||||
throw new GuacamoleException("Specified authentication provider class is not a AuthenticationProvider.");
|
||||
|
||||
// Return located class
|
||||
return (Class<AuthenticationProvider>) authProviderClass;
|
||||
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new GuacamoleException("Authentication provider class not found", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.properties;
|
||||
|
||||
import org.apache.guacamole.properties.FileGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
||||
|
||||
/**
|
||||
* Properties used by the default Guacamole web application.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class BasicGuacamoleProperties {
|
||||
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
private BasicGuacamoleProperties() {}
|
||||
|
||||
/**
|
||||
* The authentication provider to user when retrieving the authorized
|
||||
* configurations of a user. This property is currently supported, but
|
||||
* deprecated in favor of the GUACAMOLE_HOME/extensions directory.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final AuthenticationProviderProperty AUTH_PROVIDER = new AuthenticationProviderProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "auth-provider"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The directory to search for authentication provider classes. This
|
||||
* property is currently supported, but deprecated in favor of the
|
||||
* GUACAMOLE_HOME/lib directory.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final FileGuacamoleProperty LIB_DIRECTORY = new FileGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "lib-directory"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The session timeout for the API, in minutes.
|
||||
*/
|
||||
public static final IntegerGuacamoleProperty API_SESSION_TIMEOUT = new IntegerGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "api-session-timeout"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Comma-separated list of all allowed languages, where each language is
|
||||
* represented by a language key, such as "en" or "en_US". If specified,
|
||||
* only languages within this list will be listed as available by the REST
|
||||
* service.
|
||||
*/
|
||||
public static final StringSetProperty ALLOWED_LANGUAGES = new StringSetProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "allowed-languages"; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.properties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.properties.GuacamoleProperty;
|
||||
|
||||
/**
|
||||
* A GuacamoleProperty whose value is a Set of unique Strings. The string value
|
||||
* parsed to produce this set is a comma-delimited list. Duplicate values are
|
||||
* ignored, as is any whitespace following delimiters. To maintain
|
||||
* compatibility with the behavior of Java properties in general, only
|
||||
* whitespace at the beginning of each value is ignored; trailing whitespace
|
||||
* becomes part of the value.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class StringSetProperty implements GuacamoleProperty<Set<String>> {
|
||||
|
||||
/**
|
||||
* A pattern which matches against the delimiters between values. This is
|
||||
* currently simply a comma and any following whitespace. Parts of the
|
||||
* input string which match this pattern will not be included in the parsed
|
||||
* result.
|
||||
*/
|
||||
private static final Pattern DELIMITER_PATTERN = Pattern.compile(",\\s*");
|
||||
|
||||
@Override
|
||||
public Set<String> parseValue(String values) throws GuacamoleException {
|
||||
|
||||
// If no property provided, return null.
|
||||
if (values == null)
|
||||
return null;
|
||||
|
||||
// Split string into a set of individual values
|
||||
List<String> valueList = Arrays.asList(DELIMITER_PATTERN.split(values));
|
||||
return new HashSet<String>(valueList);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes related to the properties which the Guacamole web application
|
||||
* (and stock parts of it) read from guacamole.properties.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.properties;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
/**
|
||||
* Base abstract resource implementation which provides an associated mimetype,
|
||||
* and modification time. Classes which extend AbstractResource must provide
|
||||
* their own InputStream, however.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class AbstractResource implements Resource {
|
||||
|
||||
/**
|
||||
* The mimetype of this resource.
|
||||
*/
|
||||
private final String mimetype;
|
||||
|
||||
/**
|
||||
* The time this resource was last modified, in milliseconds since midnight
|
||||
* of January 1, 1970 UTC.
|
||||
*/
|
||||
private final long lastModified;
|
||||
|
||||
/**
|
||||
* Initializes this AbstractResource with the given mimetype and
|
||||
* modification time.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of this resource.
|
||||
*
|
||||
* @param lastModified
|
||||
* The time this resource was last modified, in milliseconds since
|
||||
* midnight of January 1, 1970 UTC.
|
||||
*/
|
||||
public AbstractResource(String mimetype, long lastModified) {
|
||||
this.mimetype = mimetype;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this AbstractResource with the given mimetype. The
|
||||
* modification time of the resource is set to the current system time.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of this resource.
|
||||
*/
|
||||
public AbstractResource(String mimetype) {
|
||||
this(mimetype, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return mimetype;
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A resource which contains a defined byte array.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ByteArrayResource extends AbstractResource {
|
||||
|
||||
/**
|
||||
* The bytes contained by this resource.
|
||||
*/
|
||||
private final byte[] bytes;
|
||||
|
||||
/**
|
||||
* Creates a new ByteArrayResource which provides access to the given byte
|
||||
* array. Changes to the given byte array will affect this resource even
|
||||
* after the resource is created. Changing the byte array while an input
|
||||
* stream from this resource is in use has undefined behavior.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param bytes
|
||||
* The bytes that this resource should contain.
|
||||
*/
|
||||
public ByteArrayResource(String mimetype, byte[] bytes) {
|
||||
super(mimetype);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A resource which is located within the classpath of an arbitrary
|
||||
* ClassLoader.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ClassPathResource extends AbstractResource {
|
||||
|
||||
/**
|
||||
* The classloader to use when reading this resource.
|
||||
*/
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* The path of this resource relative to the classloader.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Creates a new ClassPathResource which uses the given ClassLoader to
|
||||
* read the resource having the given path.
|
||||
*
|
||||
* @param classLoader
|
||||
* The ClassLoader to use when reading the resource.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param path
|
||||
* The path of the resource relative to the given ClassLoader.
|
||||
*/
|
||||
public ClassPathResource(ClassLoader classLoader, String mimetype, String path) {
|
||||
super(mimetype);
|
||||
this.classLoader = classLoader;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ClassPathResource which uses the ClassLoader associated
|
||||
* with the ClassPathResource class to read the resource having the given
|
||||
* path.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param path
|
||||
* The path of the resource relative to the ClassLoader associated
|
||||
* with the ClassPathResource class.
|
||||
*/
|
||||
public ClassPathResource(String mimetype, String path) {
|
||||
this(ClassPathResource.class.getClassLoader(), mimetype, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asStream() {
|
||||
return classLoader.getResourceAsStream(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An arbitrary resource that can be served to a user via HTTP. Resources are
|
||||
* anonymous but have a defined mimetype and corresponding input stream.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public interface Resource {
|
||||
|
||||
/**
|
||||
* Returns the mimetype of this resource. This function MUST always return
|
||||
* a value. If the type is unknown, return "application/octet-stream".
|
||||
*
|
||||
* @return
|
||||
* The mimetype of this resource.
|
||||
*/
|
||||
String getMimeType();
|
||||
|
||||
/**
|
||||
* Returns the time the resource was last modified in milliseconds since
|
||||
* midnight of January 1, 1970 UTC.
|
||||
*
|
||||
* @return
|
||||
* The time the resource was last modified, in milliseconds.
|
||||
*/
|
||||
long getLastModified();
|
||||
|
||||
/**
|
||||
* Returns an InputStream which reads the contents of this resource,
|
||||
* starting with the first byte. Reading from the returned InputStream will
|
||||
* not affect reads from other InputStreams returned by other calls to
|
||||
* asStream(). The returned InputStream must be manually closed when no
|
||||
* longer needed. If the resource is unexpectedly unavailable, this will
|
||||
* return null.
|
||||
*
|
||||
* @return
|
||||
* An InputStream which reads the contents of this resource, starting
|
||||
* with the first byte, or null if the resource is unavailable.
|
||||
*/
|
||||
InputStream asStream();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Servlet which serves a given resource for all HTTP GET requests. The HEAD
|
||||
* method is correctly supported, and HTTP 304 ("Not Modified") responses will
|
||||
* be properly returned for GET requests depending on the last time the
|
||||
* resource was modified.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ResourceServlet extends HttpServlet {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(ResourceServlet.class);
|
||||
|
||||
/**
|
||||
* The size of the buffer to use when transferring data from the input
|
||||
* stream of a resource to the output stream of a request.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 10240;
|
||||
|
||||
/**
|
||||
* The resource to serve for every GET request.
|
||||
*/
|
||||
private final Resource resource;
|
||||
|
||||
/**
|
||||
* Creates a new ResourceServlet which serves the given Resource for all
|
||||
* HTTP GET requests.
|
||||
*
|
||||
* @param resource
|
||||
* The Resource to serve.
|
||||
*/
|
||||
public ResourceServlet(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHead(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Set last modified and content type headers
|
||||
response.addDateHeader("Last-Modified", resource.getLastModified());
|
||||
response.setContentType(resource.getMimeType());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Get input stream from resource
|
||||
InputStream input = resource.asStream();
|
||||
|
||||
// If resource does not exist, return not found
|
||||
if (input == null) {
|
||||
logger.debug("Resource does not exist: \"{}\"", request.getServletPath());
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Write headers
|
||||
doHead(request, response);
|
||||
|
||||
// If not modified since "If-Modified-Since" header, return not modified
|
||||
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
|
||||
if (resource.getLastModified() - ifModifiedSince < 1000) {
|
||||
logger.debug("Resource not modified: \"{}\"", request.getServletPath());
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
|
||||
int length;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
// Write resource to response body
|
||||
OutputStream output = response.getOutputStream();
|
||||
while ((length = input.read(buffer)) != -1)
|
||||
output.write(buffer, 0, length);
|
||||
|
||||
}
|
||||
|
||||
// Ensure input stream is always closed
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A resource which is the logical concatenation of other resources.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class SequenceResource extends AbstractResource {
|
||||
|
||||
/**
|
||||
* The resources to be concatenated.
|
||||
*/
|
||||
private final Iterable<Resource> resources;
|
||||
|
||||
/**
|
||||
* Returns the mimetype of the first resource in the given Iterable, or
|
||||
* "application/octet-stream" if no resources are provided.
|
||||
*
|
||||
* @param resources
|
||||
* The resources from which the mimetype should be retrieved.
|
||||
*
|
||||
* @return
|
||||
* The mimetype of the first resource, or "application/octet-stream"
|
||||
* if no resources were provided.
|
||||
*/
|
||||
private static String getMimeType(Iterable<Resource> resources) {
|
||||
|
||||
// If no resources, just assume application/octet-stream
|
||||
Iterator<Resource> resourceIterator = resources.iterator();
|
||||
if (!resourceIterator.hasNext())
|
||||
return "application/octet-stream";
|
||||
|
||||
// Return mimetype of first resource
|
||||
return resourceIterator.next().getMimeType();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SequenceResource as the logical concatenation of the
|
||||
* given resources. Each resource is concatenated in iteration order as
|
||||
* needed when reading from the input stream of the SequenceResource.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param resources
|
||||
* The resources to concatenate within the InputStream of this
|
||||
* SequenceResource.
|
||||
*/
|
||||
public SequenceResource(String mimetype, Iterable<Resource> resources) {
|
||||
super(mimetype);
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SequenceResource as the logical concatenation of the
|
||||
* given resources. Each resource is concatenated in iteration order as
|
||||
* needed when reading from the input stream of the SequenceResource. The
|
||||
* mimetype of the resulting concatenation is derived from the first
|
||||
* resource.
|
||||
*
|
||||
* @param resources
|
||||
* The resources to concatenate within the InputStream of this
|
||||
* SequenceResource.
|
||||
*/
|
||||
public SequenceResource(Iterable<Resource> resources) {
|
||||
super(getMimeType(resources));
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SequenceResource as the logical concatenation of the
|
||||
* given resources. Each resource is concatenated in iteration order as
|
||||
* needed when reading from the input stream of the SequenceResource.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param resources
|
||||
* The resources to concatenate within the InputStream of this
|
||||
* SequenceResource.
|
||||
*/
|
||||
public SequenceResource(String mimetype, Resource... resources) {
|
||||
this(mimetype, Arrays.asList(resources));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SequenceResource as the logical concatenation of the
|
||||
* given resources. Each resource is concatenated in iteration order as
|
||||
* needed when reading from the input stream of the SequenceResource. The
|
||||
* mimetype of the resulting concatenation is derived from the first
|
||||
* resource.
|
||||
*
|
||||
* @param resources
|
||||
* The resources to concatenate within the InputStream of this
|
||||
* SequenceResource.
|
||||
*/
|
||||
public SequenceResource(Resource... resources) {
|
||||
this(Arrays.asList(resources));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asStream() {
|
||||
return new SequenceInputStream(new Enumeration<InputStream>() {
|
||||
|
||||
/**
|
||||
* Iterator over all resources associated with this
|
||||
* SequenceResource.
|
||||
*/
|
||||
private final Iterator<Resource> resourceIterator = resources.iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return resourceIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream nextElement() {
|
||||
return resourceIterator.next().asStream();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
/**
|
||||
* A resource which is located within the classpath associated with another
|
||||
* class.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebApplicationResource extends AbstractResource {
|
||||
|
||||
/**
|
||||
* The servlet context to use when reading the resource and, if necessary,
|
||||
* when determining the mimetype of the resource.
|
||||
*/
|
||||
private final ServletContext context;
|
||||
|
||||
/**
|
||||
* The path of this resource relative to the ServletContext.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Derives a mimetype from the filename within the given path using the
|
||||
* given ServletContext, if possible.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext to use to derive the mimetype.
|
||||
*
|
||||
* @param path
|
||||
* The path to derive the mimetype from.
|
||||
*
|
||||
* @return
|
||||
* An appropriate mimetype based on the name of the file in the path,
|
||||
* or "application/octet-stream" if no mimetype could be determined.
|
||||
*/
|
||||
private static String getMimeType(ServletContext context, String path) {
|
||||
|
||||
// If mimetype is known, use defined mimetype
|
||||
String mimetype = context.getMimeType(path);
|
||||
if (mimetype != null)
|
||||
return mimetype;
|
||||
|
||||
// Otherwise, default to application/octet-stream
|
||||
return "application/octet-stream";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WebApplicationResource which serves the resource at the
|
||||
* given path relative to the given ServletContext. Rather than deriving
|
||||
* the mimetype of the resource from the filename within the path, the
|
||||
* mimetype given is used.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext to use when reading the resource.
|
||||
*
|
||||
* @param mimetype
|
||||
* The mimetype of the resource.
|
||||
*
|
||||
* @param path
|
||||
* The path of the resource relative to the given ServletContext.
|
||||
*/
|
||||
public WebApplicationResource(ServletContext context, String mimetype, String path) {
|
||||
super(mimetype);
|
||||
this.context = context;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WebApplicationResource which serves the resource at the
|
||||
* given path relative to the given ServletContext. The mimetype of the
|
||||
* resource is automatically determined based on the filename within the
|
||||
* path.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext to use when reading the resource and deriving the
|
||||
* mimetype.
|
||||
*
|
||||
* @param path
|
||||
* The path of the resource relative to the given ServletContext.
|
||||
*/
|
||||
public WebApplicationResource(ServletContext context, String path) {
|
||||
this(context, getMimeType(context, path), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asStream() {
|
||||
return context.getResourceAsStream(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 which describe and provide access to arbitrary resources, such as
|
||||
* the contents of the classpath of a classloader, or files within the web
|
||||
* application itself.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.resource;
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.guacamole.form.Field;
|
||||
|
||||
/**
|
||||
* Describes an error that occurred within a REST endpoint.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIError {
|
||||
|
||||
/**
|
||||
* The error message.
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* All expected request parameters, if any, as a collection of fields.
|
||||
*/
|
||||
private final Collection<Field> expected;
|
||||
|
||||
/**
|
||||
* The type of error that occurred.
|
||||
*/
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* All possible types of REST API errors.
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
/**
|
||||
* The requested operation could not be performed because the request
|
||||
* itself was malformed.
|
||||
*/
|
||||
BAD_REQUEST(Response.Status.BAD_REQUEST),
|
||||
|
||||
/**
|
||||
* The credentials provided were invalid.
|
||||
*/
|
||||
INVALID_CREDENTIALS(Response.Status.FORBIDDEN),
|
||||
|
||||
/**
|
||||
* The credentials provided were not necessarily invalid, but were not
|
||||
* sufficient to determine validity.
|
||||
*/
|
||||
INSUFFICIENT_CREDENTIALS(Response.Status.FORBIDDEN),
|
||||
|
||||
/**
|
||||
* An internal server error has occurred.
|
||||
*/
|
||||
INTERNAL_ERROR(Response.Status.INTERNAL_SERVER_ERROR),
|
||||
|
||||
/**
|
||||
* An object related to the request does not exist.
|
||||
*/
|
||||
NOT_FOUND(Response.Status.NOT_FOUND),
|
||||
|
||||
/**
|
||||
* Permission was denied to perform the requested operation.
|
||||
*/
|
||||
PERMISSION_DENIED(Response.Status.FORBIDDEN);
|
||||
|
||||
/**
|
||||
* The HTTP status associated with this error type.
|
||||
*/
|
||||
private final Response.Status status;
|
||||
|
||||
/**
|
||||
* Defines a new error type associated with the given HTTP status.
|
||||
*
|
||||
* @param status
|
||||
* The HTTP status to associate with the error type.
|
||||
*/
|
||||
Type(Response.Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP status associated with this error type.
|
||||
*
|
||||
* @return
|
||||
* The HTTP status associated with this error type.
|
||||
*/
|
||||
public Response.Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new APIError with the specified error message.
|
||||
*
|
||||
* @param type
|
||||
* The type of error that occurred.
|
||||
*
|
||||
* @param message
|
||||
* The error message.
|
||||
*/
|
||||
public APIError(Type type, String message) {
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.expected = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new APIError with the specified error message and parameter
|
||||
* information.
|
||||
*
|
||||
* @param type
|
||||
* The type of error that occurred.
|
||||
*
|
||||
* @param message
|
||||
* The error message.
|
||||
*
|
||||
* @param expected
|
||||
* All parameters expected in the original request, or now required as
|
||||
* a result of the original request, as a collection of fields.
|
||||
*/
|
||||
public APIError(Type type, String message, Collection<Field> expected) {
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of error that occurred.
|
||||
*
|
||||
* @return
|
||||
* The type of error that occurred.
|
||||
*/
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all required parameters, where each parameter is
|
||||
* represented by a field.
|
||||
*
|
||||
* @return
|
||||
* A collection of all required parameters.
|
||||
*/
|
||||
public Collection<Field> getExpected() {
|
||||
return expected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable error message describing the error that
|
||||
* occurred.
|
||||
*
|
||||
* @return
|
||||
* A human-readable error message.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.guacamole.form.Field;
|
||||
|
||||
/**
|
||||
* An exception that will result in the given error error information being
|
||||
* returned from the API layer. All error messages have the same format which
|
||||
* is defined by APIError.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIException extends WebApplicationException {
|
||||
|
||||
/**
|
||||
* Construct a new APIException with the given error. All information
|
||||
* associated with this new exception will be extracted from the given
|
||||
* APIError.
|
||||
*
|
||||
* @param error
|
||||
* The error that occurred.
|
||||
*/
|
||||
public APIException(APIError error) {
|
||||
super(Response.status(error.getType().getStatus()).entity(error).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new APIException with the given type and message. The
|
||||
* corresponding APIError will be created from the provided information.
|
||||
*
|
||||
* @param type
|
||||
* The type of error that occurred.
|
||||
*
|
||||
* @param message
|
||||
* A human-readable message describing the error.
|
||||
*/
|
||||
public APIException(APIError.Type type, String message) {
|
||||
this(new APIError(type, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new APIException with the given type, message, and parameter
|
||||
* information. The corresponding APIError will be created from the
|
||||
* provided information.
|
||||
*
|
||||
* @param type
|
||||
* The type of error that occurred.
|
||||
*
|
||||
* @param message
|
||||
* A human-readable message describing the error.
|
||||
*
|
||||
* @param expected
|
||||
* All parameters expected in the original request, or now required as
|
||||
* a result of the original request, as a collection of fields.
|
||||
*/
|
||||
public APIException(APIError.Type type, String message, Collection<Field> expected) {
|
||||
this(new APIError(type, message, expected));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.apache.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
/**
|
||||
* Wrapper for HttpServletRequest which uses a given MultivaluedMap to provide
|
||||
* the values of all request parameters.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIRequest extends HttpServletRequestWrapper {
|
||||
|
||||
/**
|
||||
* Map of all request parameter names to their corresponding values.
|
||||
*/
|
||||
private final Map<String, String[]> parameters;
|
||||
|
||||
/**
|
||||
* Wraps the given HttpServletRequest, using the given MultivaluedMap to
|
||||
* provide all request parameters. All HttpServletRequest functions which
|
||||
* do not deal with parameter names and values are delegated to the wrapped
|
||||
* request.
|
||||
*
|
||||
* @param request
|
||||
* The HttpServletRequest to wrap.
|
||||
*
|
||||
* @param parameters
|
||||
* All request parameters.
|
||||
*/
|
||||
public APIRequest(HttpServletRequest request,
|
||||
MultivaluedMap<String, String> parameters) {
|
||||
|
||||
super(request);
|
||||
|
||||
// Copy parameters from given MultivaluedMap
|
||||
this.parameters = new HashMap<String, String[]>(parameters.size());
|
||||
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
|
||||
|
||||
// Get parameter name and all corresponding values
|
||||
String name = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
|
||||
// Add parameters to map
|
||||
this.parameters.put(name, values.toArray(new String[values.size()]));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return parameters.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(parameters.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
return Collections.unmodifiableMap(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
|
||||
// If no such parameter exists, just return null
|
||||
String[] values = getParameterValues(name);
|
||||
if (values == null)
|
||||
return null;
|
||||
|
||||
// Otherwise, return first value
|
||||
return values[0];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup;
|
||||
|
||||
/**
|
||||
* Provides easy access and automatic error handling for retrieval of objects,
|
||||
* such as users, connections, or connection groups. REST API semantics, such
|
||||
* as the special root connection group identifier, are also handled
|
||||
* automatically.
|
||||
*/
|
||||
public class ObjectRetrievalService {
|
||||
|
||||
/**
|
||||
* Retrieves a single UserContext from the given GuacamoleSession, which
|
||||
* may contain multiple UserContexts.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the UserContext from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext being retrieved. Only one UserContext per User per
|
||||
* AuthenticationProvider can exist.
|
||||
*
|
||||
* @return
|
||||
* The UserContext that was created by the AuthenticationProvider
|
||||
* having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the UserContext, or if the
|
||||
* UserContext does not exist.
|
||||
*/
|
||||
public UserContext retrieveUserContext(GuacamoleSession session,
|
||||
String authProviderIdentifier) throws GuacamoleException {
|
||||
|
||||
// Get list of UserContexts
|
||||
List<UserContext> userContexts = session.getUserContexts();
|
||||
|
||||
// Locate and return the UserContext associated with the
|
||||
// AuthenticationProvider having the given identifier, if any
|
||||
for (UserContext userContext : userContexts) {
|
||||
|
||||
// Get AuthenticationProvider associated with current UserContext
|
||||
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
|
||||
|
||||
// If AuthenticationProvider identifier matches, done
|
||||
if (authProvider.getIdentifier().equals(authProviderIdentifier))
|
||||
return userContext;
|
||||
|
||||
}
|
||||
|
||||
throw new GuacamoleResourceNotFoundException("Session not associated with authentication provider \"" + authProviderIdentifier + "\".");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single user from the given user context.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context to retrieve the user from.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the user to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The user having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user, or if the
|
||||
* user does not exist.
|
||||
*/
|
||||
public User retrieveUser(UserContext userContext,
|
||||
String identifier) throws GuacamoleException {
|
||||
|
||||
// Get user directory
|
||||
Directory<User> directory = userContext.getUserDirectory();
|
||||
|
||||
// Pull specified user
|
||||
User user = directory.get(identifier);
|
||||
if (user == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such user: \"" + identifier + "\"");
|
||||
|
||||
return user;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single user from the given GuacamoleSession.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the user from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the user should be retrieved. Only one
|
||||
* UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the user to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The user having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user, or if the
|
||||
* user does not exist.
|
||||
*/
|
||||
public User retrieveUser(GuacamoleSession session, String authProviderIdentifier,
|
||||
String identifier) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveUser(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection from the given user context.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context to retrieve the connection from.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection, or if the
|
||||
* connection does not exist.
|
||||
*/
|
||||
public Connection retrieveConnection(UserContext userContext,
|
||||
String identifier) throws GuacamoleException {
|
||||
|
||||
// Get connection directory
|
||||
Directory<Connection> directory = userContext.getConnectionDirectory();
|
||||
|
||||
// Pull specified connection
|
||||
Connection connection = directory.get(identifier);
|
||||
if (connection == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such connection: \"" + identifier + "\"");
|
||||
|
||||
return connection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection from the given GuacamoleSession.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the connection from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the connection should be retrieved. Only one
|
||||
* UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection, or if the
|
||||
* connection does not exist.
|
||||
*/
|
||||
public Connection retrieveConnection(GuacamoleSession session,
|
||||
String authProviderIdentifier, String identifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveConnection(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection group from the given user context. If
|
||||
* the given identifier the REST API root identifier, the root connection
|
||||
* group will be returned. The underlying authentication provider may
|
||||
* additionally use a different identifier for root.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context to retrieve the connection group from.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection group to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection group having the given identifier, or the root
|
||||
* connection group if the identifier the root identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection group, or if the
|
||||
* connection group does not exist.
|
||||
*/
|
||||
public ConnectionGroup retrieveConnectionGroup(UserContext userContext,
|
||||
String identifier) throws GuacamoleException {
|
||||
|
||||
// Use root group if identifier is the standard root identifier
|
||||
if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER))
|
||||
return userContext.getRootConnectionGroup();
|
||||
|
||||
// Pull specified connection group otherwise
|
||||
Directory<ConnectionGroup> directory = userContext.getConnectionGroupDirectory();
|
||||
ConnectionGroup connectionGroup = directory.get(identifier);
|
||||
|
||||
if (connectionGroup == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\"");
|
||||
|
||||
return connectionGroup;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection group from the given GuacamoleSession. If
|
||||
* the given identifier is the REST API root identifier, the root
|
||||
* connection group will be returned. The underlying authentication
|
||||
* provider may additionally use a different identifier for root.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the connection group from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the connection group should be retrieved.
|
||||
* Only one UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection group to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection group having the given identifier, or the root
|
||||
* connection group if the identifier is the root identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection group, or if the
|
||||
* connection group does not exist.
|
||||
*/
|
||||
public ConnectionGroup retrieveConnectionGroup(GuacamoleSession session,
|
||||
String authProviderIdentifier, String identifier) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveConnectionGroup(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.apache.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 {}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.GuacamoleSecurityException;
|
||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A method interceptor which wraps custom exception handling around methods
|
||||
* which can throw GuacamoleExceptions and which are exposed through the REST
|
||||
* interface. The various types of GuacamoleExceptions are automatically
|
||||
* translated into appropriate HTTP responses, including JSON describing the
|
||||
* error that occurred.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class RESTExceptionWrapper implements MethodInterceptor {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(RESTExceptionWrapper.class);
|
||||
|
||||
/**
|
||||
* Service for authenticating users and managing their Guacamole sessions.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Determines whether the given set of annotations describes an HTTP
|
||||
* request parameter of the given name. For a parameter to be associated
|
||||
* with an HTTP request parameter, it must be annotated with either the
|
||||
* <code>@QueryParam</code> or <code>@FormParam</code> annotations.
|
||||
*
|
||||
* @param annotations
|
||||
* The annotations associated with the Java parameter being checked.
|
||||
*
|
||||
* @param name
|
||||
* The name of the HTTP request parameter.
|
||||
*
|
||||
* @return
|
||||
* true if the given set of annotations describes an HTTP request
|
||||
* parameter having the given name, false otherwise.
|
||||
*/
|
||||
private boolean isRequestParameter(Annotation[] annotations, String name) {
|
||||
|
||||
// Search annotations for associated HTTP parameters
|
||||
for (Annotation annotation : annotations) {
|
||||
|
||||
// Check if parameter is associated with the HTTP query string
|
||||
if (annotation instanceof QueryParam && name.equals(((QueryParam) annotation).value()))
|
||||
return true;
|
||||
|
||||
// Failing that, check whether the parameter is associated with the
|
||||
// HTTP request body
|
||||
if (annotation instanceof FormParam && name.equals(((FormParam) annotation).value()))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// No parameter annotations are present
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication token that was passed in the given method
|
||||
* invocation. If the given method invocation is not associated with an
|
||||
* HTTP request (it lacks the appropriate JAX-RS annotations) or there is
|
||||
* no authentication token, null is returned.
|
||||
*
|
||||
* @param invocation
|
||||
* The method invocation whose corresponding authentication token
|
||||
* should be determined.
|
||||
*
|
||||
* @return
|
||||
* The authentication token passed in the given method invocation, or
|
||||
* null if there is no such token.
|
||||
*/
|
||||
private String getAuthenticationToken(MethodInvocation invocation) {
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
|
||||
// Get the types and annotations associated with each parameter
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
// The Java standards require these to be parallel arrays
|
||||
assert(parameterAnnotations.length == parameterTypes.length);
|
||||
|
||||
// Iterate through all parameters, looking for the authentication token
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
|
||||
// Only inspect String parameters
|
||||
Class<?> parameterType = parameterTypes[i];
|
||||
if (parameterType != String.class)
|
||||
continue;
|
||||
|
||||
// Parameter must be declared as a REST service parameter
|
||||
Annotation[] annotations = parameterAnnotations[i];
|
||||
if (!isRequestParameter(annotations, "token"))
|
||||
continue;
|
||||
|
||||
// The token parameter has been found - return its value
|
||||
Object[] args = invocation.getArguments();
|
||||
return (String) args[i];
|
||||
|
||||
}
|
||||
|
||||
// No token parameter is defined
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
|
||||
try {
|
||||
|
||||
// Invoke wrapped method
|
||||
try {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// Ensure any associated session is invalidated if unauthorized
|
||||
catch (GuacamoleUnauthorizedException e) {
|
||||
|
||||
// Pull authentication token from request
|
||||
String token = getAuthenticationToken(invocation);
|
||||
|
||||
// If there is an associated auth token, invalidate it
|
||||
if (authenticationService.destroyGuacamoleSession(token))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\".", token);
|
||||
|
||||
// Continue with exception processing
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Additional credentials are needed
|
||||
catch (GuacamoleInsufficientCredentialsException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Permission denied.";
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.INSUFFICIENT_CREDENTIALS,
|
||||
message,
|
||||
e.getCredentialsInfo().getFields()
|
||||
);
|
||||
}
|
||||
|
||||
// The provided credentials are wrong
|
||||
catch (GuacamoleInvalidCredentialsException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Permission denied.";
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.INVALID_CREDENTIALS,
|
||||
message,
|
||||
e.getCredentialsInfo().getFields()
|
||||
);
|
||||
}
|
||||
|
||||
// Generic permission denied
|
||||
catch (GuacamoleSecurityException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Permission denied.";
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.PERMISSION_DENIED,
|
||||
message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Arbitrary resource not found
|
||||
catch (GuacamoleResourceNotFoundException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Not found.";
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.NOT_FOUND,
|
||||
message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Arbitrary bad requests
|
||||
catch (GuacamoleClientException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Invalid request.";
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.BAD_REQUEST,
|
||||
message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// All other errors
|
||||
catch (GuacamoleException e) {
|
||||
|
||||
// Generate default message
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Unexpected server error.";
|
||||
|
||||
// Ensure internal errors are logged at the debug level
|
||||
logger.debug("Unexpected exception in REST endpoint.", e);
|
||||
|
||||
throw new APIException(
|
||||
APIError.Type.INTERNAL_ERROR,
|
||||
message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import com.google.inject.matcher.AbstractMatcher;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* A Guice Matcher which matches only methods which throw GuacamoleException
|
||||
* (or a subclass thereof) and are explicitly annotated as with an HTTP method
|
||||
* annotation like <code>@GET</code> or <code>@POST</code>. Any method which
|
||||
* throws GuacamoleException and is annotated with an annotation that is
|
||||
* annotated with <code>@HttpMethod</code> will match.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class RESTMethodMatcher extends AbstractMatcher<Method> {
|
||||
|
||||
/**
|
||||
* Returns whether the given method throws the specified exception type,
|
||||
* including any subclasses of that type.
|
||||
*
|
||||
* @param method
|
||||
* The method to test.
|
||||
*
|
||||
* @param exceptionType
|
||||
* The exception type to test for.
|
||||
*
|
||||
* @return
|
||||
* true if the given method throws an exception of the specified type,
|
||||
* false otherwise.
|
||||
*/
|
||||
private boolean methodThrowsException(Method method,
|
||||
Class<? extends Exception> exceptionType) {
|
||||
|
||||
// Check whether the method throws an exception of the specified type
|
||||
for (Class<?> thrownType : method.getExceptionTypes()) {
|
||||
if (exceptionType.isAssignableFrom(thrownType))
|
||||
return true;
|
||||
}
|
||||
|
||||
// No such exception is declared to be thrown
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given method is annotated as a REST method. A REST
|
||||
* method is annotated with an annotation which is annotated with
|
||||
* <code>@HttpMethod</code>.
|
||||
*
|
||||
* @param method
|
||||
* The method to test.
|
||||
*
|
||||
* @return
|
||||
* true if the given method is annotated as a REST method, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean isRESTMethod(Method method) {
|
||||
|
||||
// Check whether the required REST annotations are present
|
||||
for (Annotation annotation : method.getAnnotations()) {
|
||||
|
||||
// A method is a REST method if it is annotated with @HttpMethod
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
if (annotationType.isAnnotationPresent(HttpMethod.class))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// The method is not an HTTP method
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
|
||||
// Guacamole REST methods are REST methods which throw
|
||||
// GuacamoleExceptions
|
||||
return isRESTMethod(method)
|
||||
&& methodThrowsException(method, GuacamoleException.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest;
|
||||
|
||||
import com.google.inject.Scopes;
|
||||
import com.google.inject.matcher.Matchers;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
|
||||
import org.apache.guacamole.net.basic.rest.auth.TokenRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.connection.ConnectionRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.activeconnection.ActiveConnectionRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthTokenGenerator;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator;
|
||||
import org.apache.guacamole.net.basic.rest.auth.TokenSessionMap;
|
||||
import org.apache.guacamole.net.basic.rest.history.HistoryRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.language.LanguageRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.patch.PatchRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.schema.SchemaRESTService;
|
||||
import org.apache.guacamole.net.basic.rest.user.UserRESTService;
|
||||
|
||||
/**
|
||||
* A Guice Module to set up the servlet mappings and authentication-specific
|
||||
* dependency injection for the Guacamole REST API.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class RESTServiceModule extends ServletModule {
|
||||
|
||||
/**
|
||||
* Singleton instance of TokenSessionMap.
|
||||
*/
|
||||
private final TokenSessionMap tokenSessionMap;
|
||||
|
||||
/**
|
||||
* Creates a module which handles binding of REST services and related
|
||||
* authentication objects, including the singleton TokenSessionMap.
|
||||
*
|
||||
* @param tokenSessionMap
|
||||
* An instance of TokenSessionMap to inject as a singleton wherever
|
||||
* needed.
|
||||
*/
|
||||
public RESTServiceModule(TokenSessionMap tokenSessionMap) {
|
||||
this.tokenSessionMap = tokenSessionMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
// Bind session map
|
||||
bind(TokenSessionMap.class).toInstance(tokenSessionMap);
|
||||
|
||||
// Bind low-level services
|
||||
bind(AuthenticationService.class);
|
||||
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
||||
|
||||
// Automatically translate GuacamoleExceptions for REST methods
|
||||
MethodInterceptor interceptor = new RESTExceptionWrapper();
|
||||
requestInjection(interceptor);
|
||||
bindInterceptor(Matchers.any(), new RESTMethodMatcher(), interceptor);
|
||||
|
||||
// Bind convenience services used by the REST API
|
||||
bind(ObjectRetrievalService.class);
|
||||
|
||||
// Set up the API endpoints
|
||||
bind(ActiveConnectionRESTService.class);
|
||||
bind(ConnectionGroupRESTService.class);
|
||||
bind(ConnectionRESTService.class);
|
||||
bind(HistoryRESTService.class);
|
||||
bind(LanguageRESTService.class);
|
||||
bind(PatchRESTService.class);
|
||||
bind(SchemaRESTService.class);
|
||||
bind(TokenRESTService.class);
|
||||
bind(UserRESTService.class);
|
||||
|
||||
// Set up the servlet and JSON mappings
|
||||
bind(GuiceContainer.class);
|
||||
bind(JacksonJsonProvider.class).in(Scopes.SINGLETON);
|
||||
serve("/api/*").with(GuiceContainer.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.activeconnection;
|
||||
|
||||
import java.util.Date;
|
||||
import org.apache.guacamole.net.auth.ActiveConnection;
|
||||
|
||||
/**
|
||||
* Information related to active connections which may be exposed through the
|
||||
* REST endpoints.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIActiveConnection {
|
||||
|
||||
/**
|
||||
* The identifier of the active connection itself.
|
||||
*/
|
||||
private final String identifier;
|
||||
|
||||
/**
|
||||
* The identifier of the connection associated with this
|
||||
* active connection.
|
||||
*/
|
||||
private final String connectionIdentifier;
|
||||
|
||||
/**
|
||||
* The date and time the connection began.
|
||||
*/
|
||||
private final Date startDate;
|
||||
|
||||
/**
|
||||
* The host from which the connection originated, if known.
|
||||
*/
|
||||
private final String remoteHost;
|
||||
|
||||
/**
|
||||
* The name of the user who used or is using the connection.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Creates a new APIActiveConnection, copying the information from the given
|
||||
* active connection.
|
||||
*
|
||||
* @param connection
|
||||
* The active connection to copy data from.
|
||||
*/
|
||||
public APIActiveConnection(ActiveConnection connection) {
|
||||
this.identifier = connection.getIdentifier();
|
||||
this.connectionIdentifier = connection.getConnectionIdentifier();
|
||||
this.startDate = connection.getStartDate();
|
||||
this.remoteHost = connection.getRemoteHost();
|
||||
this.username = connection.getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the connection associated with this tunnel.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the connection associated with this tunnel.
|
||||
*/
|
||||
public String getConnectionIdentifier() {
|
||||
return connectionIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time the connection began.
|
||||
*
|
||||
* @return
|
||||
* The date and time the connection began.
|
||||
*/
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote host from which this connection originated.
|
||||
*
|
||||
* @return
|
||||
* The remote host from which this connection originated.
|
||||
*/
|
||||
public String getRemoteHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the user who used or is using the connection at the
|
||||
* times given by this tunnel.
|
||||
*
|
||||
* @return
|
||||
* The name of the user who used or is using the associated connection.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the active connection itself. This is
|
||||
* distinct from the connection identifier, and uniquely identifies a
|
||||
* specific use of a connection.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the active connection.
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.activeconnection;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Collection;
|
||||
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.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleUnsupportedException;
|
||||
import org.apache.guacamole.net.auth.ActiveConnection;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.APIPatch;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.PATCH;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A REST Service for retrieving and managing the tunnels of active connections.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/data/{dataSource}/activeConnections")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ActiveConnectionRESTService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(ActiveConnectionRESTService.class);
|
||||
|
||||
/**
|
||||
* A service for authenticating users from auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Gets a list of active connections in the system, filtering the returned
|
||||
* list by the given permissions, if specified.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the active connections to be retrieved.
|
||||
*
|
||||
* @param permissions
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for a user to appear in the result.
|
||||
* If null, no filtering will be performed.
|
||||
*
|
||||
* @return
|
||||
* A list of all active connections. If a permission was specified,
|
||||
* this list will contain only those active connections for which the
|
||||
* current user has that permission.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error is encountered while retrieving active connections.
|
||||
*/
|
||||
@GET
|
||||
public Map<String, APIActiveConnection> getActiveConnections(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
User self = userContext.self();
|
||||
|
||||
// Do not filter on permissions if no permissions are specified
|
||||
if (permissions != null && permissions.isEmpty())
|
||||
permissions = null;
|
||||
|
||||
// An admin user has access to any connection
|
||||
SystemPermissionSet systemPermissions = self.getSystemPermissions();
|
||||
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
|
||||
|
||||
// Get the directory
|
||||
Directory<ActiveConnection> activeConnectionDirectory = userContext.getActiveConnectionDirectory();
|
||||
|
||||
// Filter connections, if requested
|
||||
Collection<String> activeConnectionIdentifiers = activeConnectionDirectory.getIdentifiers();
|
||||
if (!isAdmin && permissions != null) {
|
||||
ObjectPermissionSet activeConnectionPermissions = self.getActiveConnectionPermissions();
|
||||
activeConnectionIdentifiers = activeConnectionPermissions.getAccessibleObjects(permissions, activeConnectionIdentifiers);
|
||||
}
|
||||
|
||||
// Retrieve all active connections , converting to API active connections
|
||||
Map<String, APIActiveConnection> apiActiveConnections = new HashMap<String, APIActiveConnection>();
|
||||
for (ActiveConnection activeConnection : activeConnectionDirectory.getAll(activeConnectionIdentifiers))
|
||||
apiActiveConnections.put(activeConnection.getIdentifier(), new APIActiveConnection(activeConnection));
|
||||
|
||||
return apiActiveConnections;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given active connection patches. This operation currently
|
||||
* only supports deletion of active connections through the "remove" patch
|
||||
* operation. Deleting an active connection effectively kills the
|
||||
* connection. The path of each patch operation is of the form "/ID"
|
||||
* where ID is the identifier of the active connection being modified.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the active connections to be deleted.
|
||||
*
|
||||
* @param patches
|
||||
* The active connection patches to apply for this request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while deleting the active connections.
|
||||
*/
|
||||
@PATCH
|
||||
public void patchTunnels(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
List<APIPatch<String>> patches) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<ActiveConnection> activeConnectionDirectory = userContext.getActiveConnectionDirectory();
|
||||
|
||||
// Close each connection listed for removal
|
||||
for (APIPatch<String> patch : patches) {
|
||||
|
||||
// Only remove is supported
|
||||
if (patch.getOp() != APIPatch.Operation.remove)
|
||||
throw new GuacamoleUnsupportedException("Only the \"remove\" operation is supported when patching active connections.");
|
||||
|
||||
// Retrieve and validate path
|
||||
String path = patch.getPath();
|
||||
if (!path.startsWith("/"))
|
||||
throw new GuacamoleClientException("Patch paths must start with \"/\".");
|
||||
|
||||
// Close connection
|
||||
activeConnectionDirectory.remove(path.substring(1));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
/**
|
||||
* A simple object to represent an auth token/username pair in the API.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class APIAuthenticationResponse {
|
||||
|
||||
/**
|
||||
* The auth token.
|
||||
*/
|
||||
private final String authToken;
|
||||
|
||||
|
||||
/**
|
||||
* The username of the user that authenticated.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source from which this user account
|
||||
* came. Although this user account may exist across several data sources
|
||||
* (AuthenticationProviders), this will be the unique identifier of the
|
||||
* AuthenticationProvider that authenticated this user for the current
|
||||
* session.
|
||||
*/
|
||||
private final String dataSource;
|
||||
|
||||
/**
|
||||
* Returns the unique authentication token which identifies the current
|
||||
* session.
|
||||
*
|
||||
* @return
|
||||
* The user's authentication token.
|
||||
*/
|
||||
public String getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user identified by the authentication token associated with
|
||||
* the current session.
|
||||
*
|
||||
* @return
|
||||
* The user identified by this authentication token.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the data source associated with the user
|
||||
* account associated with this auth token.
|
||||
*
|
||||
* @return
|
||||
* The unique identifier of the data source associated with the user
|
||||
* account associated with this auth token.
|
||||
*/
|
||||
public String getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new APIAuthToken Object with the given auth token.
|
||||
*
|
||||
* @param dataSource
|
||||
* The unique identifier of the AuthenticationProvider which
|
||||
* authenticated the user.
|
||||
*
|
||||
* @param authToken
|
||||
* The auth token to create the new APIAuthToken with.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user owning the given token.
|
||||
*/
|
||||
public APIAuthenticationResponse(String dataSource, String authToken, String username) {
|
||||
this.dataSource = dataSource;
|
||||
this.authToken = authToken;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple object which describes the result of an authentication operation,
|
||||
* including the resulting token.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIAuthenticationResult {
|
||||
|
||||
/**
|
||||
* The unique token generated for the user that authenticated.
|
||||
*/
|
||||
private final String authToken;
|
||||
|
||||
/**
|
||||
* The username of the user that authenticated.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source from which this user account
|
||||
* came. Although this user account may exist across several data sources
|
||||
* (AuthenticationProviders), this will be the unique identifier of the
|
||||
* AuthenticationProvider that authenticated this user for the current
|
||||
* session.
|
||||
*/
|
||||
private final String dataSource;
|
||||
|
||||
/**
|
||||
* The identifiers of all data sources available to this user.
|
||||
*/
|
||||
private final List<String> availableDataSources;
|
||||
|
||||
/**
|
||||
* Returns the unique authentication token which identifies the current
|
||||
* session.
|
||||
*
|
||||
* @return
|
||||
* The user's authentication token.
|
||||
*/
|
||||
public String getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user identified by the authentication token associated with
|
||||
* the current session.
|
||||
*
|
||||
* @return
|
||||
* The user identified by this authentication token.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the data source associated with the user
|
||||
* account associated with the current session.
|
||||
*
|
||||
* @return
|
||||
* The unique identifier of the data source associated with the user
|
||||
* account associated with the current session.
|
||||
*/
|
||||
public String getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifiers of all data sources available to the user
|
||||
* associated with the current session.
|
||||
*
|
||||
* @return
|
||||
* The identifiers of all data sources available to the user associated
|
||||
* with the current session.
|
||||
*/
|
||||
public List<String> getAvailableDataSources() {
|
||||
return availableDataSources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new APIAuthenticationResult object containing the given data.
|
||||
*
|
||||
* @param authToken
|
||||
* The unique token generated for the user that authenticated, to be
|
||||
* used for the duration of their session.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user owning the given token.
|
||||
*
|
||||
* @param dataSource
|
||||
* The unique identifier of the AuthenticationProvider which
|
||||
* authenticated the user.
|
||||
*
|
||||
* @param availableDataSources
|
||||
* The unique identifier of all AuthenticationProviders to which the
|
||||
* user now has access.
|
||||
*/
|
||||
public APIAuthenticationResult(String authToken, String username,
|
||||
String dataSource, List<String> availableDataSources) {
|
||||
this.authToken = authToken;
|
||||
this.username = username;
|
||||
this.dataSource = dataSource;
|
||||
this.availableDataSources = Collections.unmodifiableList(availableDataSources);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -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.apache.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();
|
||||
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleSecurityException;
|
||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service for performing authentication checks in REST endpoints.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class AuthenticationService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
@Inject
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* All configured authentication providers which can be used to
|
||||
* authenticate users or retrieve data associated with authenticated users.
|
||||
*/
|
||||
@Inject
|
||||
private List<AuthenticationProvider> authProviders;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts authentication against all AuthenticationProviders, in order,
|
||||
* using the provided credentials. The first authentication failure takes
|
||||
* priority, but remaining AuthenticationProviders are attempted. If any
|
||||
* AuthenticationProvider succeeds, the resulting AuthenticatedUser is
|
||||
* returned, and no further AuthenticationProviders are tried.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use for authentication.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser given by the highest-priority
|
||||
* AuthenticationProvider for which the given credentials are valid.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given credentials are not valid for any
|
||||
* AuthenticationProvider, or if an error occurs while authenticating
|
||||
* the user.
|
||||
*/
|
||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleCredentialsException authFailure = null;
|
||||
|
||||
// Attempt authentication against each AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Attempt authentication
|
||||
try {
|
||||
AuthenticatedUser authenticatedUser = authProvider.authenticateUser(credentials);
|
||||
if (authenticatedUser != null)
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
// First failure takes priority for now
|
||||
catch (GuacamoleCredentialsException e) {
|
||||
if (authFailure == null)
|
||||
authFailure = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If a specific failure occured, rethrow that
|
||||
if (authFailure != null)
|
||||
throw authFailure;
|
||||
|
||||
// Otherwise, request standard username/password
|
||||
throw new GuacamoleInvalidCredentialsException(
|
||||
"Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-authenticates the given AuthenticatedUser against the
|
||||
* AuthenticationProvider that originally created it, using the given
|
||||
* Credentials.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser to re-authenticate.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to re-authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* A AuthenticatedUser which may have been updated due to re-
|
||||
* authentication.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents the user from being re-authenticated.
|
||||
*/
|
||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// Get original AuthenticationProvider
|
||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||
|
||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
if (authenticatedUser == null)
|
||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthenticatedUser associated with the given session and
|
||||
* credentials, performing a fresh authentication and creating a new
|
||||
* AuthenticatedUser if necessary.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser associated with the given session and
|
||||
* credentials.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while authenticating or re-authenticating the
|
||||
* user.
|
||||
*/
|
||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null)
|
||||
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
||||
|
||||
// Otherwise, attempt authentication as a new user
|
||||
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(),
|
||||
getLoggableAddress(credentials.getRequest()));
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
// Log and rethrow any authentication errors
|
||||
catch (GuacamoleException e) {
|
||||
|
||||
// Get request and username for sake of logging
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
String username = credentials.getUsername();
|
||||
|
||||
// Log authentication failures with associated usernames
|
||||
if (username != null) {
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||
getLoggableAddress(request), username);
|
||||
}
|
||||
|
||||
// Log anonymous authentication failures
|
||||
else if (logger.isDebugEnabled())
|
||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||
getLoggableAddress(request));
|
||||
|
||||
// Rethrow exception
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with the given AuthenticatedUser,
|
||||
* updating existing UserContexts, if any. If no UserContexts are yet
|
||||
* associated with the given AuthenticatedUser, new UserContexts are
|
||||
* generated by polling each available AuthenticationProvider.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser that has successfully authenticated or re-
|
||||
* authenticated.
|
||||
*
|
||||
* @return
|
||||
* A List of all UserContexts associated with the given
|
||||
* AuthenticatedUser.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating or updating any UserContext.
|
||||
*/
|
||||
private List<UserContext> getUserContexts(GuacamoleSession existingSession,
|
||||
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||
|
||||
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
||||
|
||||
// If UserContexts already exist, update them and add to the list
|
||||
if (existingSession != null) {
|
||||
|
||||
// Update all old user contexts
|
||||
List<UserContext> oldUserContexts = existingSession.getUserContexts();
|
||||
for (UserContext oldUserContext : oldUserContexts) {
|
||||
|
||||
// Update existing UserContext
|
||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
||||
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
// If unsuccessful, log that this happened, as it may be a bug
|
||||
else
|
||||
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
|
||||
authProvider.getClass().getName());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, create new UserContexts from available AuthenticationProviders
|
||||
else {
|
||||
|
||||
// Get UserContexts from each available AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Generate new UserContext
|
||||
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return userContexts;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user using the given credentials and optional
|
||||
* authentication token, returning the authentication token associated with
|
||||
* the user's Guacamole session, which may be newly generated. If an
|
||||
* existing token is provided, the authentication procedure will attempt to
|
||||
* update or reuse the provided token, but it is possible that a new token
|
||||
* will be returned. Note that this function CANNOT return null.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use when authenticating the user.
|
||||
*
|
||||
* @param token
|
||||
* The authentication token to use if attempting to re-authenticate an
|
||||
* existing session, or null to request a new token.
|
||||
*
|
||||
* @return
|
||||
* The authentication token associated with the newly created or
|
||||
* existing session.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the authentication or re-authentication attempt fails.
|
||||
*/
|
||||
public String authenticate(Credentials credentials, String token)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull existing session if token provided
|
||||
GuacamoleSession existingSession;
|
||||
if (token != null)
|
||||
existingSession = tokenSessionMap.get(token);
|
||||
else
|
||||
existingSession = null;
|
||||
|
||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||
List<UserContext> userContexts = getUserContexts(existingSession, authenticatedUser);
|
||||
|
||||
// Update existing session, if it exists
|
||||
String authToken;
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
}
|
||||
|
||||
return authToken;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates a specific authentication token and its corresponding
|
||||
* Guacamole session, effectively logging out the associated user. If the
|
||||
* authentication token is not valid, this function has no effect.
|
||||
*
|
||||
* @param authToken
|
||||
* The token being invalidated.
|
||||
*
|
||||
* @return
|
||||
* true if the given authentication token was valid and the
|
||||
* corresponding Guacamole session was destroyed, false if the given
|
||||
* authentication token was not valid and no action was taken.
|
||||
*/
|
||||
public boolean destroyGuacamoleSession(String authToken) {
|
||||
|
||||
// Remove corresponding GuacamoleSession if the token is valid
|
||||
GuacamoleSession session = tokenSessionMap.remove(authToken);
|
||||
if (session == null)
|
||||
return false;
|
||||
|
||||
// Invalidate the removed session
|
||||
session.invalidate();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with 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
|
||||
* A List of all UserContexts associated with the provided auth token.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the auth token does not correspond to any logged in user.
|
||||
*/
|
||||
public List<UserContext> getUserContexts(String authToken)
|
||||
throws GuacamoleException {
|
||||
return getGuacamoleSession(authToken).getUserContexts();
|
||||
}
|
||||
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
||||
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);
|
||||
|
||||
/**
|
||||
* Executor service which runs the period session eviction task.
|
||||
*/
|
||||
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
|
||||
/**
|
||||
* Keeps track of the authToken to GuacamoleSession mapping.
|
||||
*/
|
||||
private final ConcurrentMap<String, GuacamoleSession> sessionMap =
|
||||
new ConcurrentHashMap<String, GuacamoleSession>();
|
||||
|
||||
/**
|
||||
* Create a new BasicTokenGuacamoleSessionMap configured using the given
|
||||
* environment.
|
||||
*
|
||||
* @param environment
|
||||
* The environment to use when configuring the token session map.
|
||||
*/
|
||||
public BasicTokenSessionMap(Environment environment) {
|
||||
|
||||
int sessionTimeoutValue;
|
||||
|
||||
// Read session timeout from guacamole.properties
|
||||
try {
|
||||
sessionTimeoutValue = environment.getProperty(BasicGuacamoleProperties.API_SESSION_TIMEOUT, 60);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
|
||||
logger.debug("Error while reading session timeout value.", e);
|
||||
sessionTimeoutValue = 60;
|
||||
}
|
||||
|
||||
// Check for expired sessions every minute
|
||||
logger.info("Sessions will expire after {} minutes of inactivity.", sessionTimeoutValue);
|
||||
executor.scheduleAtFixedRate(new SessionEvictionTask(sessionTimeoutValue * 60000l), 1, 1, TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Task which iterates through all active sessions, evicting those sessions
|
||||
* which are beyond the session timeout.
|
||||
*/
|
||||
private class SessionEvictionTask implements Runnable {
|
||||
|
||||
/**
|
||||
* The maximum allowed age of any session, in milliseconds.
|
||||
*/
|
||||
private final long sessionTimeout;
|
||||
|
||||
/**
|
||||
* Creates a new task which automatically evicts sessions which are
|
||||
* older than the specified timeout.
|
||||
*
|
||||
* @param sessionTimeout The maximum age of any session, in
|
||||
* milliseconds.
|
||||
*/
|
||||
public SessionEvictionTask(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// Get start time of session check time
|
||||
long sessionCheckStart = System.currentTimeMillis();
|
||||
|
||||
logger.debug("Checking for expired sessions...");
|
||||
|
||||
// For each session, remove sesions which have expired
|
||||
Iterator<Map.Entry<String, GuacamoleSession>> entries = sessionMap.entrySet().iterator();
|
||||
while (entries.hasNext()) {
|
||||
|
||||
Map.Entry<String, GuacamoleSession> entry = entries.next();
|
||||
GuacamoleSession session = entry.getValue();
|
||||
|
||||
// Do not expire sessions which are active
|
||||
if (session.hasTunnels())
|
||||
continue;
|
||||
|
||||
// Get elapsed time since last access
|
||||
long age = sessionCheckStart - session.getLastAccessedTime();
|
||||
|
||||
// If session is too old, evict it and check the next one
|
||||
if (age >= sessionTimeout) {
|
||||
logger.debug("Session \"{}\" has timed out.", entry.getKey());
|
||||
entries.remove();
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Log completion and duration
|
||||
logger.debug("Session check completed in {} ms.",
|
||||
System.currentTimeMillis() - sessionCheckStart);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleSession get(String authToken) {
|
||||
|
||||
// There are no null auth tokens
|
||||
if (authToken == null)
|
||||
return null;
|
||||
|
||||
// Update the last access time and return the GuacamoleSession
|
||||
GuacamoleSession session = sessionMap.get(authToken);
|
||||
if (session != null)
|
||||
session.access();
|
||||
|
||||
return session;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String authToken, GuacamoleSession session) {
|
||||
sessionMap.put(authToken, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleSession remove(String authToken) {
|
||||
|
||||
// There are no null auth tokens
|
||||
if (authToken == null)
|
||||
return null;
|
||||
|
||||
// Attempt to retrieve only if non-null
|
||||
return sessionMap.remove(authToken);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -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.apache.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);
|
||||
}
|
||||
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.APIRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service for managing auth tokens via the Guacamole REST API.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/tokens")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class TokenRESTService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(TokenRESTService.class);
|
||||
|
||||
/**
|
||||
* Service for authenticating users and managing their Guacamole sessions.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Returns the credentials associated with the given request, using the
|
||||
* provided username and password.
|
||||
*
|
||||
* @param request
|
||||
* The request to use to derive the credentials.
|
||||
*
|
||||
* @param username
|
||||
* The username to associate with the credentials, or null if the
|
||||
* username should be derived from the request.
|
||||
*
|
||||
* @param password
|
||||
* The password to associate with the credentials, or null if the
|
||||
* password should be derived from the request.
|
||||
*
|
||||
* @return
|
||||
* A new Credentials object whose contents have been derived from the
|
||||
* given request, along with the provided username and password.
|
||||
*/
|
||||
private Credentials getCredentials(HttpServletRequest request,
|
||||
String username, String password) {
|
||||
|
||||
// If no username/password given, try Authorization header
|
||||
if (username == null && password == null) {
|
||||
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (authorization != null && authorization.startsWith("Basic ")) {
|
||||
|
||||
try {
|
||||
|
||||
// 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.debug("Invalid HTTP Basic \"Authorization\" header received.");
|
||||
|
||||
}
|
||||
|
||||
// UTF-8 support is required by the Java specification
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // end Authorization header fallback
|
||||
|
||||
// Build credentials
|
||||
Credentials credentials = new Credentials();
|
||||
credentials.setUsername(username);
|
||||
credentials.setPassword(password);
|
||||
credentials.setRequest(request);
|
||||
credentials.setSession(request.getSession(true));
|
||||
|
||||
return credentials;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user, generates an auth token, associates that auth token
|
||||
* with the user's UserContext for use by further requests. If an existing
|
||||
* token is provided, the authentication procedure will attempt to update
|
||||
* or reuse the provided token.
|
||||
*
|
||||
* @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 token
|
||||
* An optional existing auth token for the user who is to be
|
||||
* authenticated.
|
||||
*
|
||||
* @param consumedRequest
|
||||
* The HttpServletRequest associated with the login attempt. The
|
||||
* parameters of this request may not be accessible, as the request may
|
||||
* have been fully consumed by JAX-RS.
|
||||
*
|
||||
* @param parameters
|
||||
* A MultivaluedMap containing all parameters from the given HTTP
|
||||
* request. All request parameters must be made available through this
|
||||
* map, even if those parameters are no longer accessible within the
|
||||
* now-fully-consumed HTTP request.
|
||||
*
|
||||
* @return
|
||||
* An authentication response object containing the possible-new auth
|
||||
* token, as well as other related data.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents successful authentication.
|
||||
*/
|
||||
@POST
|
||||
public APIAuthenticationResult createToken(@FormParam("username") String username,
|
||||
@FormParam("password") String password,
|
||||
@FormParam("token") String token,
|
||||
@Context HttpServletRequest consumedRequest,
|
||||
MultivaluedMap<String, String> parameters)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Reconstitute the HTTP request with the map of parameters
|
||||
HttpServletRequest request = new APIRequest(consumedRequest, parameters);
|
||||
|
||||
// Build credentials from request
|
||||
Credentials credentials = getCredentials(request, username, password);
|
||||
|
||||
// Create/update session producing possibly-new token
|
||||
token = authenticationService.authenticate(credentials, token);
|
||||
|
||||
// Pull corresponding session
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(token);
|
||||
if (session == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||
|
||||
// Build list of all available auth providers
|
||||
List<UserContext> userContexts = session.getUserContexts();
|
||||
List<String> authProviderIdentifiers = new ArrayList<String>(userContexts.size());
|
||||
for (UserContext userContext : userContexts)
|
||||
authProviderIdentifiers.add(userContext.getAuthenticationProvider().getIdentifier());
|
||||
|
||||
// Return possibly-new auth token
|
||||
AuthenticatedUser authenticatedUser = session.getAuthenticatedUser();
|
||||
return new APIAuthenticationResult(
|
||||
token,
|
||||
authenticatedUser.getIdentifier(),
|
||||
authenticatedUser.getAuthenticationProvider().getIdentifier(),
|
||||
authProviderIdentifiers
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates a specific auth token, effectively logging out the associated
|
||||
* user.
|
||||
*
|
||||
* @param authToken
|
||||
* The token being invalidated.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the specified token does not exist.
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{token}")
|
||||
public void invalidateToken(@PathParam("token") String authToken)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Invalidate session, if it exists
|
||||
if (!authenticationService.destroyGuacamoleSession(authToken))
|
||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
import org.apache.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);
|
||||
|
||||
/**
|
||||
* Removes the GuacamoleSession associated with the given auth token.
|
||||
*
|
||||
* @param authToken The token to remove.
|
||||
* @return The GuacamoleSession for the given auth token, if the auth token
|
||||
* represents a currently logged in user, null otherwise.
|
||||
*/
|
||||
public GuacamoleSession remove(String authToken);
|
||||
|
||||
/**
|
||||
* Shuts down this session map, disallowing future sessions and reclaiming
|
||||
* any resources.
|
||||
*/
|
||||
public void shutdown();
|
||||
|
||||
}
|
||||
@@ -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 authentication aspect of the Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.auth;
|
||||
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.connection;
|
||||
|
||||
import java.util.Map;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
|
||||
/**
|
||||
* A simple connection to expose through the REST endpoints.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
|
||||
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;
|
||||
|
||||
/**
|
||||
* Map of all associated parameter values, indexed by parameter name.
|
||||
*/
|
||||
private Map<String, String> parameters;
|
||||
|
||||
/**
|
||||
* Map of all associated attributes by attribute identifier.
|
||||
*/
|
||||
private Map<String, String> attributes;
|
||||
|
||||
/**
|
||||
* The count of currently active connections using this connection.
|
||||
*/
|
||||
private int activeConnections;
|
||||
|
||||
/**
|
||||
* Create an empty APIConnection.
|
||||
*/
|
||||
public APIConnection() {}
|
||||
|
||||
/**
|
||||
* Create an APIConnection from a Connection record. Parameters for the
|
||||
* connection will not be included.
|
||||
*
|
||||
* @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 {
|
||||
|
||||
// Set connection information
|
||||
this.name = connection.getName();
|
||||
this.identifier = connection.getIdentifier();
|
||||
this.parentIdentifier = connection.getParentIdentifier();
|
||||
this.activeConnections = connection.getActiveConnections();
|
||||
|
||||
// Set protocol from configuration
|
||||
GuacamoleConfiguration configuration = connection.getConfiguration();
|
||||
this.protocol = configuration.getProtocol();
|
||||
|
||||
// Associate any attributes
|
||||
this.attributes = connection.getAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of currently active connections using this
|
||||
* connection.
|
||||
*
|
||||
* @return
|
||||
* The number of currently active usages of this connection.
|
||||
*/
|
||||
public int getActiveConnections() {
|
||||
return activeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of currently active connections using this connection.
|
||||
*
|
||||
* @param activeConnections
|
||||
* The number of currently active usages of this connection.
|
||||
*/
|
||||
public void setActiveUsers(int activeConnections) {
|
||||
this.activeConnections = activeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all attributes associated with this connection. Each
|
||||
* entry key is the attribute identifier, while each value is the attribute
|
||||
* value itself.
|
||||
*
|
||||
* @return
|
||||
* The attribute map for this connection.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map of all attributes associated with this connection. Each
|
||||
* entry key is the attribute identifier, while each value is the attribute
|
||||
* value itself.
|
||||
*
|
||||
* @param attributes
|
||||
* The attribute map for this connection.
|
||||
*/
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.connection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
|
||||
/**
|
||||
* A wrapper to make an APIConnection look like a Connection. Useful where a
|
||||
* org.apache.guacamole.net.auth.Connection is required.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class APIConnectionWrapper implements Connection {
|
||||
|
||||
/**
|
||||
* The wrapped APIConnection.
|
||||
*/
|
||||
private final APIConnection apiConnection;
|
||||
|
||||
/**
|
||||
* Creates a new APIConnectionWrapper which wraps the given APIConnection
|
||||
* as a Connection.
|
||||
*
|
||||
* @param apiConnection
|
||||
* The APIConnection to wrap.
|
||||
*/
|
||||
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 int getActiveConnections() {
|
||||
return apiConnection.getActiveConnections();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleConfiguration getConfiguration() {
|
||||
|
||||
// Create the GuacamoleConfiguration with current protocol
|
||||
GuacamoleConfiguration configuration = new GuacamoleConfiguration();
|
||||
configuration.setProtocol(apiConnection.getProtocol());
|
||||
|
||||
// Add parameters, if available
|
||||
Map<String, String> parameters = apiConnection.getParameters();
|
||||
if (parameters != null)
|
||||
configuration.setParameters(parameters);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(GuacamoleConfiguration config) {
|
||||
|
||||
// Set protocol and parameters
|
||||
apiConnection.setProtocol(config.getProtocol());
|
||||
apiConnection.setParameters(config.getParameters());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return apiConnection.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
apiConnection.setAttributes(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException {
|
||||
throw new UnsupportedOperationException("Operation not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ConnectionRecord> getHistory() throws GuacamoleException {
|
||||
return Collections.<ConnectionRecord>emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.connection;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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 org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleSecurityException;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.apache.guacamole.net.basic.rest.history.APIConnectionRecord;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A REST Service for handling connection CRUD operations.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@Path("/data/{dataSource}/connections")
|
||||
@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;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Retrieves an individual connection.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection to be retrieved.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{connectionID}")
|
||||
public APIConnection getConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection
|
||||
return new APIConnection(retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the parameters associated with a single connection.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection whose parameters are to be
|
||||
* retrieved.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection.
|
||||
*
|
||||
* @return
|
||||
* A map of parameter name/value pairs.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection parameters.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{connectionID}/parameters")
|
||||
public Map<String, String> getConnectionParameters(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
User self = userContext.self();
|
||||
|
||||
// Retrieve permission sets
|
||||
SystemPermissionSet systemPermissions = self.getSystemPermissions();
|
||||
ObjectPermissionSet connectionPermissions = self.getConnectionPermissions();
|
||||
|
||||
// Deny access if adminstrative or update permission is missing
|
||||
if (!systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER)
|
||||
&& !connectionPermissions.hasPermission(ObjectPermission.Type.UPDATE, connectionID))
|
||||
throw new GuacamoleSecurityException("Permission to read connection parameters denied.");
|
||||
|
||||
// Retrieve the requested connection
|
||||
Connection connection = retrievalService.retrieveConnection(userContext, connectionID);
|
||||
|
||||
// Retrieve connection configuration
|
||||
GuacamoleConfiguration config = connection.getConfiguration();
|
||||
|
||||
// Return parameter map
|
||||
return config.getParameters();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the usage history of a single connection.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection whose history is to be
|
||||
* retrieved.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection.
|
||||
*
|
||||
* @return
|
||||
* A list of connection records, describing the start and end times of
|
||||
* various usages of this connection.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection history.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{connectionID}/history")
|
||||
public List<APIConnectionRecord> getConnectionHistory(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection
|
||||
Connection connection = retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID);
|
||||
|
||||
// Retrieve the requested connection's history
|
||||
List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
|
||||
for (ConnectionRecord record : connection.getHistory())
|
||||
apiRecords.add(new APIConnectionRecord(record));
|
||||
|
||||
// Return the converted history
|
||||
return apiRecords;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an individual connection.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection to be deleted.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to delete.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while deleting the connection.
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{connectionID}")
|
||||
public void deleteConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the connection directory
|
||||
Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
|
||||
|
||||
// Delete the specified connection
|
||||
connectionDirectory.remove(connectionID);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection and returns the new connection, with identifier
|
||||
* field populated.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the connection is to be created.
|
||||
*
|
||||
* @param connection
|
||||
* The connection to create.
|
||||
*
|
||||
* @return
|
||||
* The new connection.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the connection.
|
||||
*/
|
||||
@POST
|
||||
public APIConnection createConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
APIConnection connection) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection data was provided
|
||||
if (connection == null)
|
||||
throw new GuacamoleClientException("Connection JSON must be submitted when creating connections.");
|
||||
|
||||
// Add the new connection
|
||||
Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
|
||||
connectionDirectory.add(new APIConnectionWrapper(connection));
|
||||
|
||||
// Return the new connection
|
||||
return connection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing connection. If the parent identifier of the
|
||||
* connection is changed, the connection will also be moved to the new
|
||||
* parent group.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection to be updated.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to update.
|
||||
*
|
||||
* @param connection
|
||||
* The connection data to update the specified connection with.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while updating the connection.
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{connectionID}")
|
||||
public void updateConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID,
|
||||
APIConnection connection) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection data was provided
|
||||
if (connection == null)
|
||||
throw new GuacamoleClientException("Connection JSON must be submitted when updating connections.");
|
||||
|
||||
// Get the connection directory
|
||||
Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
|
||||
|
||||
// Retrieve connection to update
|
||||
Connection existingConnection = retrievalService.retrieveConnection(userContext, connectionID);
|
||||
|
||||
// Build updated configuration
|
||||
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||
config.setProtocol(connection.getProtocol());
|
||||
config.setParameters(connection.getParameters());
|
||||
|
||||
// Update the connection
|
||||
existingConnection.setConfiguration(config);
|
||||
existingConnection.setParentIdentifier(connection.getParentIdentifier());
|
||||
existingConnection.setName(connection.getName());
|
||||
existingConnection.setAttributes(connection.getAttributes());
|
||||
connectionDirectory.update(existingConnection);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -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 connection manipulation aspect of the Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.connection;
|
||||
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.connectiongroup;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup.Type;
|
||||
import org.apache.guacamole.net.basic.rest.connection.APIConnection;
|
||||
|
||||
/**
|
||||
* A simple connection group to expose through the REST endpoints.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
|
||||
public class APIConnectionGroup {
|
||||
|
||||
/**
|
||||
* The identifier of the root connection group.
|
||||
*/
|
||||
public static final String ROOT_IDENTIFIER = "ROOT";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The count of currently active connections using this connection group.
|
||||
*/
|
||||
private int activeConnections;
|
||||
|
||||
/**
|
||||
* All child connection groups. If children are not being queried, this may
|
||||
* be omitted.
|
||||
*/
|
||||
private Collection<APIConnectionGroup> childConnectionGroups;
|
||||
|
||||
/**
|
||||
* All child connections. If children are not being queried, this may be
|
||||
* omitted.
|
||||
*/
|
||||
private Collection<APIConnection> childConnections;
|
||||
|
||||
/**
|
||||
* Map of all associated attributes by attribute identifier.
|
||||
*/
|
||||
private Map<String, String> attributes;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
// Set connection group information
|
||||
this.identifier = connectionGroup.getIdentifier();
|
||||
this.parentIdentifier = connectionGroup.getParentIdentifier();
|
||||
this.name = connectionGroup.getName();
|
||||
this.type = connectionGroup.getType();
|
||||
this.activeConnections = connectionGroup.getActiveConnections();
|
||||
|
||||
// Associate any attributes
|
||||
this.attributes = connectionGroup.getAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all child connection groups, or null if children
|
||||
* have not been queried.
|
||||
*
|
||||
* @return
|
||||
* A collection of all child connection groups, or null if children
|
||||
* have not been queried.
|
||||
*/
|
||||
public Collection<APIConnectionGroup> getChildConnectionGroups() {
|
||||
return childConnectionGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection of all child connection groups to the given
|
||||
* collection, which may be null if children have not been queried.
|
||||
*
|
||||
* @param childConnectionGroups
|
||||
* The collection containing all child connection groups of this
|
||||
* connection group, or null if children have not been queried.
|
||||
*/
|
||||
public void setChildConnectionGroups(Collection<APIConnectionGroup> childConnectionGroups) {
|
||||
this.childConnectionGroups = childConnectionGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all child connections, or null if children have
|
||||
* not been queried.
|
||||
*
|
||||
* @return
|
||||
* A collection of all child connections, or null if children have not
|
||||
* been queried.
|
||||
*/
|
||||
public Collection<APIConnection> getChildConnections() {
|
||||
return childConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection of all child connections to the given collection,
|
||||
* which may be null if children have not been queried.
|
||||
*
|
||||
* @param childConnections
|
||||
* The collection containing all child connections of this connection
|
||||
* group, or null if children have not been queried.
|
||||
*/
|
||||
public void setChildConnections(Collection<APIConnection> childConnections) {
|
||||
this.childConnections = childConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of currently active connections using this
|
||||
* connection group.
|
||||
*
|
||||
* @return
|
||||
* The number of currently active usages of this connection group.
|
||||
*/
|
||||
public int getActiveConnections() {
|
||||
return activeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of currently active connections using this connection
|
||||
* group.
|
||||
*
|
||||
* @param activeConnections
|
||||
* The number of currently active usages of this connection group.
|
||||
*/
|
||||
public void setActiveUsers(int activeConnections) {
|
||||
this.activeConnections = activeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all attributes associated with this connection group.
|
||||
* Each entry key is the attribute identifier, while each value is the
|
||||
* attribute value itself.
|
||||
*
|
||||
* @return
|
||||
* The attribute map for this connection group.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map of all attributes associated with this connection group.
|
||||
* Each entry key is the attribute identifier, while each value is the
|
||||
* attribute value itself.
|
||||
*
|
||||
* @param attributes
|
||||
* The attribute map for this connection group.
|
||||
*/
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
}
|
||||
+124
@@ -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.apache.guacamole.net.basic.rest.connectiongroup;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
|
||||
/**
|
||||
* A wrapper to make an APIConnection look like a ConnectionGroup.
|
||||
* Useful where a org.apache.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 int getActiveConnections() {
|
||||
return apiConnectionGroup.getActiveConnections();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getConnectionIdentifiers() {
|
||||
throw new UnsupportedOperationException("Operation not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getConnectionGroupIdentifiers() {
|
||||
throw new UnsupportedOperationException("Operation not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return apiConnectionGroup.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
apiConnectionGroup.setAttributes(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException {
|
||||
throw new UnsupportedOperationException("Operation not supported.");
|
||||
}
|
||||
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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.apache.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 org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.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("/data/{dataSource}/connectionGroups")
|
||||
@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;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Gets an individual connection group.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection group to be retrieved.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The ID of the connection group to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection group, without any descendants.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a problem is encountered while retrieving the connection group.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{connectionGroupID}")
|
||||
public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection group
|
||||
return new APIConnectionGroup(retrievalService.retrieveConnectionGroup(session, authProviderIdentifier, connectionGroupID));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an individual connection group and all children.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection group to be retrieved.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The ID of the connection group to retrieve.
|
||||
*
|
||||
* @param permissions
|
||||
* If specified and non-empty, limit the returned list to only those
|
||||
* connections for which the current user has any of the given
|
||||
* permissions. Otherwise, all visible connections are returned.
|
||||
* Connection groups are unaffected by this parameter.
|
||||
*
|
||||
* @return
|
||||
* The requested connection group, including all descendants.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a problem is encountered while retrieving the connection group or
|
||||
* its descendants.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{connectionGroupID}/tree")
|
||||
public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Retrieve the requested tree, filtering by the given permissions
|
||||
ConnectionGroup treeRoot = retrievalService.retrieveConnectionGroup(userContext, connectionGroupID);
|
||||
ConnectionGroupTree tree = new ConnectionGroupTree(userContext, treeRoot, permissions);
|
||||
|
||||
// Return tree as a connection group
|
||||
return tree.getRootAPIConnectionGroup();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an individual connection group.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection group to be deleted.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The identifier of the connection group to delete.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while deleting the connection group.
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{connectionGroupID}")
|
||||
public void deleteConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the connection group directory
|
||||
Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
|
||||
|
||||
// Delete the connection group
|
||||
connectionGroupDirectory.remove(connectionGroupID);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection group and returns the new connection group,
|
||||
* with identifier field populated.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the connection group is to be created.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The connection group to create.
|
||||
*
|
||||
* @return
|
||||
* The new connection group.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the connection group.
|
||||
*/
|
||||
@POST
|
||||
public APIConnectionGroup createConnectionGroup(
|
||||
@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
APIConnectionGroup connectionGroup) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection group data was provided
|
||||
if (connectionGroup == null)
|
||||
throw new GuacamoleClientException("Connection group JSON must be submitted when creating connections groups.");
|
||||
|
||||
// Add the new connection group
|
||||
Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
|
||||
connectionGroupDirectory.add(new APIConnectionGroupWrapper(connectionGroup));
|
||||
|
||||
// Return the new connection group
|
||||
return connectionGroup;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a connection group. If the parent identifier of the
|
||||
* connection group is changed, the connection group will also be moved to
|
||||
* the new parent group.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection group to be updated.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The identifier of the existing connection group to update.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The data to update the existing connection group with.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while updating the connection group.
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{connectionGroupID}")
|
||||
public void updateConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID,
|
||||
APIConnectionGroup connectionGroup)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection group data was provided
|
||||
if (connectionGroup == null)
|
||||
throw new GuacamoleClientException("Connection group JSON must be submitted when updating connection groups.");
|
||||
|
||||
// Get the connection group directory
|
||||
Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
|
||||
|
||||
// Retrieve connection group to update
|
||||
ConnectionGroup existingConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, connectionGroupID);
|
||||
|
||||
// Update the connection group
|
||||
existingConnectionGroup.setName(connectionGroup.getName());
|
||||
existingConnectionGroup.setParentIdentifier(connectionGroup.getParentIdentifier());
|
||||
existingConnectionGroup.setType(connectionGroup.getType());
|
||||
existingConnectionGroup.setAttributes(connectionGroup.getAttributes());
|
||||
connectionGroupDirectory.update(existingConnectionGroup);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.connectiongroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.basic.rest.connection.APIConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides access to the entire tree of connection groups and their
|
||||
* connections.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ConnectionGroupTree {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConnectionGroupTree.class);
|
||||
|
||||
/**
|
||||
* The context of the user obtaining this tree.
|
||||
*/
|
||||
private final UserContext userContext;
|
||||
|
||||
/**
|
||||
* The root connection group as an APIConnectionGroup.
|
||||
*/
|
||||
private final APIConnectionGroup rootAPIGroup;
|
||||
|
||||
/**
|
||||
* All connection groups that have been retrieved, stored by their
|
||||
* identifiers.
|
||||
*/
|
||||
private final Map<String, APIConnectionGroup> retrievedGroups =
|
||||
new HashMap<String, APIConnectionGroup>();
|
||||
|
||||
/**
|
||||
* Adds each of the provided connections to the current tree as children
|
||||
* of their respective parents. The parent connection groups must already
|
||||
* be added.
|
||||
*
|
||||
* @param connections
|
||||
* The connections to add to the tree.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while adding the connection to the tree.
|
||||
*/
|
||||
private void addConnections(Collection<Connection> connections)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Add each connection to the tree
|
||||
for (Connection connection : connections) {
|
||||
|
||||
// Retrieve the connection's parent group
|
||||
APIConnectionGroup parent = retrievedGroups.get(connection.getParentIdentifier());
|
||||
if (parent != null) {
|
||||
|
||||
Collection<APIConnection> children = parent.getChildConnections();
|
||||
|
||||
// Create child collection if it does not yet exist
|
||||
if (children == null) {
|
||||
children = new ArrayList<APIConnection>();
|
||||
parent.setChildConnections(children);
|
||||
}
|
||||
|
||||
// Add child
|
||||
children.add(new APIConnection(connection));
|
||||
|
||||
}
|
||||
|
||||
// Warn of internal consistency issues
|
||||
else
|
||||
logger.debug("Connection \"{}\" cannot be added to the tree: parent \"{}\" does not actually exist.",
|
||||
connection.getIdentifier(),
|
||||
connection.getParentIdentifier());
|
||||
|
||||
} // end for each connection
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds each of the provided connection groups to the current tree as
|
||||
* children of their respective parents. The parent connection groups must
|
||||
* already be added.
|
||||
*
|
||||
* @param connectionGroups
|
||||
* The connection groups to add to the tree.
|
||||
*/
|
||||
private void addConnectionGroups(Collection<ConnectionGroup> connectionGroups) {
|
||||
|
||||
// Add each connection group to the tree
|
||||
for (ConnectionGroup connectionGroup : connectionGroups) {
|
||||
|
||||
// Retrieve the connection group's parent group
|
||||
APIConnectionGroup parent = retrievedGroups.get(connectionGroup.getParentIdentifier());
|
||||
if (parent != null) {
|
||||
|
||||
Collection<APIConnectionGroup> children = parent.getChildConnectionGroups();
|
||||
|
||||
// Create child collection if it does not yet exist
|
||||
if (children == null) {
|
||||
children = new ArrayList<APIConnectionGroup>();
|
||||
parent.setChildConnectionGroups(children);
|
||||
}
|
||||
|
||||
// Add child
|
||||
APIConnectionGroup apiConnectionGroup = new APIConnectionGroup(connectionGroup);
|
||||
retrievedGroups.put(connectionGroup.getIdentifier(), apiConnectionGroup);
|
||||
children.add(apiConnectionGroup);
|
||||
|
||||
}
|
||||
|
||||
// Warn of internal consistency issues
|
||||
else
|
||||
logger.debug("Connection group \"{}\" cannot be added to the tree: parent \"{}\" does not actually exist.",
|
||||
connectionGroup.getIdentifier(),
|
||||
connectionGroup.getParentIdentifier());
|
||||
|
||||
} // end for each connection group
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all descendants of the given parent groups to their corresponding
|
||||
* parents already stored under root.
|
||||
*
|
||||
* @param parents
|
||||
* The parents whose descendants should be added to the tree.
|
||||
*
|
||||
* @param permissions
|
||||
* If specified and non-empty, limit added connections to only
|
||||
* connections for which the current user has any of the given
|
||||
* permissions. Otherwise, all visible connections are added.
|
||||
* Connection groups are unaffected by this parameter.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the descendants.
|
||||
*/
|
||||
private void addDescendants(Collection<ConnectionGroup> parents,
|
||||
List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
// If no parents, nothing to do
|
||||
if (parents.isEmpty())
|
||||
return;
|
||||
|
||||
Collection<String> childConnectionIdentifiers = new ArrayList<String>();
|
||||
Collection<String> childConnectionGroupIdentifiers = new ArrayList<String>();
|
||||
|
||||
// Build lists of identifiers for retrieval
|
||||
for (ConnectionGroup parent : parents) {
|
||||
childConnectionIdentifiers.addAll(parent.getConnectionIdentifiers());
|
||||
childConnectionGroupIdentifiers.addAll(parent.getConnectionGroupIdentifiers());
|
||||
}
|
||||
|
||||
// Filter identifiers based on permissions, if requested
|
||||
if (permissions != null && !permissions.isEmpty()) {
|
||||
ObjectPermissionSet permissionSet = userContext.self().getConnectionPermissions();
|
||||
childConnectionIdentifiers = permissionSet.getAccessibleObjects(permissions, childConnectionIdentifiers);
|
||||
}
|
||||
|
||||
// Retrieve child connections
|
||||
if (!childConnectionIdentifiers.isEmpty()) {
|
||||
Collection<Connection> childConnections = userContext.getConnectionDirectory().getAll(childConnectionIdentifiers);
|
||||
addConnections(childConnections);
|
||||
}
|
||||
|
||||
// Retrieve child connection groups
|
||||
if (!childConnectionGroupIdentifiers.isEmpty()) {
|
||||
Collection<ConnectionGroup> childConnectionGroups = userContext.getConnectionGroupDirectory().getAll(childConnectionGroupIdentifiers);
|
||||
addConnectionGroups(childConnectionGroups);
|
||||
addDescendants(childConnectionGroups, permissions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection group tree using the given connection group as
|
||||
* the tree root.
|
||||
*
|
||||
* @param userContext
|
||||
* The context of the user obtaining the connection group tree.
|
||||
*
|
||||
* @param root
|
||||
* The connection group to use as the root of this connection group
|
||||
* tree.
|
||||
*
|
||||
* @param permissions
|
||||
* If specified and non-empty, limit the contents of the tree to only
|
||||
* those connections for which the current user has any of the given
|
||||
* permissions. Otherwise, all visible connections are returned.
|
||||
* Connection groups are unaffected by this parameter.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the tree of connection groups
|
||||
* and their descendants.
|
||||
*/
|
||||
public ConnectionGroupTree(UserContext userContext, ConnectionGroup root,
|
||||
List<ObjectPermission.Type> permissions) throws GuacamoleException {
|
||||
|
||||
this.userContext = userContext;
|
||||
|
||||
// Store root of tree
|
||||
this.rootAPIGroup = new APIConnectionGroup(root);
|
||||
retrievedGroups.put(root.getIdentifier(), this.rootAPIGroup);
|
||||
|
||||
// Add all descendants
|
||||
addDescendants(Collections.singleton(root), permissions);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entire connection group tree as an APIConnectionGroup. The
|
||||
* returned APIConnectionGroup is the root group and will contain all
|
||||
* descendant connection groups and connections, arranged hierarchically.
|
||||
*
|
||||
* @return
|
||||
* The root connection group, containing the entire connection group
|
||||
* tree and all connections.
|
||||
*/
|
||||
public APIConnectionGroup getRootAPIConnectionGroup() {
|
||||
return rootAPIGroup;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -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.apache.guacamole.net.basic.rest.connectiongroup;
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.history;
|
||||
|
||||
import java.util.Date;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
|
||||
/**
|
||||
* A connection record which may be exposed through the REST endpoints.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIConnectionRecord {
|
||||
|
||||
/**
|
||||
* The identifier of the connection associated with this record.
|
||||
*/
|
||||
private final String connectionIdentifier;
|
||||
|
||||
/**
|
||||
* The identifier of the connection associated with this record.
|
||||
*/
|
||||
private final String connectionName;
|
||||
|
||||
/**
|
||||
* The date and time the connection began.
|
||||
*/
|
||||
private final Date startDate;
|
||||
|
||||
/**
|
||||
* The date and time the connection ended, or null if the connection is
|
||||
* still running or if the end time is unknown.
|
||||
*/
|
||||
private final Date endDate;
|
||||
|
||||
/**
|
||||
* The host from which the connection originated, if known.
|
||||
*/
|
||||
private final String remoteHost;
|
||||
|
||||
/**
|
||||
* The name of the user who used or is using the connection.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Whether the connection is currently active.
|
||||
*/
|
||||
private final boolean active;
|
||||
|
||||
/**
|
||||
* Creates a new APIConnectionRecord, copying the data from the given
|
||||
* record.
|
||||
*
|
||||
* @param record
|
||||
* The record to copy data from.
|
||||
*/
|
||||
public APIConnectionRecord(ConnectionRecord record) {
|
||||
this.connectionIdentifier = record.getConnectionIdentifier();
|
||||
this.connectionName = record.getConnectionName();
|
||||
this.startDate = record.getStartDate();
|
||||
this.endDate = record.getEndDate();
|
||||
this.remoteHost = record.getRemoteHost();
|
||||
this.username = record.getUsername();
|
||||
this.active = record.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the connection associated with this
|
||||
* record.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the connection associated with this record.
|
||||
*/
|
||||
public String getConnectionIdentifier() {
|
||||
return connectionIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the connection associated with this record.
|
||||
*
|
||||
* @return
|
||||
* The name of the connection associated with this record.
|
||||
*/
|
||||
public String getConnectionName() {
|
||||
return connectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time the connection began.
|
||||
*
|
||||
* @return
|
||||
* The date and time the connection began.
|
||||
*/
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time the connection ended, if applicable.
|
||||
*
|
||||
* @return
|
||||
* The date and time the connection ended, or null if the connection is
|
||||
* still running or if the end time is unknown.
|
||||
*/
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote host from which this connection originated.
|
||||
*
|
||||
* @return
|
||||
* The remote host from which this connection originated.
|
||||
*/
|
||||
public String getRemoteHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the user who used or is using the connection at the
|
||||
* times given by this connection record.
|
||||
*
|
||||
* @return
|
||||
* The name of the user who used or is using the associated connection.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the connection associated with this record is still
|
||||
* active.
|
||||
*
|
||||
* @return
|
||||
* true if the connection associated with this record is still active,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.history;
|
||||
|
||||
import org.apache.guacamole.net.auth.ConnectionRecordSet;
|
||||
import org.apache.guacamole.net.basic.rest.APIError;
|
||||
import org.apache.guacamole.net.basic.rest.APIException;
|
||||
|
||||
/**
|
||||
* A sort predicate which species the property to use when sorting connection
|
||||
* records, along with the sort order.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class APIConnectionRecordSortPredicate {
|
||||
|
||||
/**
|
||||
* The prefix which will be included before the name of a sortable property
|
||||
* to indicate that the sort order is descending, not ascending.
|
||||
*/
|
||||
public static final String DESCENDING_PREFIX = "-";
|
||||
|
||||
/**
|
||||
* All possible property name strings and their corresponding
|
||||
* ConnectionRecordSet.SortableProperty values.
|
||||
*/
|
||||
public enum SortableProperty {
|
||||
|
||||
/**
|
||||
* The date that the connection associated with the connection record
|
||||
* began (connected).
|
||||
*/
|
||||
startDate(ConnectionRecordSet.SortableProperty.START_DATE);
|
||||
|
||||
/**
|
||||
* The ConnectionRecordSet.SortableProperty that this property name
|
||||
* string represents.
|
||||
*/
|
||||
public final ConnectionRecordSet.SortableProperty recordProperty;
|
||||
|
||||
/**
|
||||
* Creates a new SortableProperty which associates the property name
|
||||
* string (identical to its own name) with the given
|
||||
* ConnectionRecordSet.SortableProperty value.
|
||||
*
|
||||
* @param recordProperty
|
||||
* The ConnectionRecordSet.SortableProperty value to associate with
|
||||
* the new SortableProperty.
|
||||
*/
|
||||
SortableProperty(ConnectionRecordSet.SortableProperty recordProperty) {
|
||||
this.recordProperty = recordProperty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The property to use when sorting ConnectionRecords.
|
||||
*/
|
||||
private ConnectionRecordSet.SortableProperty property;
|
||||
|
||||
/**
|
||||
* Whether the requested sort order is descending (true) or ascending
|
||||
* (false).
|
||||
*/
|
||||
private boolean descending;
|
||||
|
||||
/**
|
||||
* Parses the given string value, determining the requested sort property
|
||||
* and ordering. Possible values consist of any valid property name, and
|
||||
* may include an optional prefix to denote descending sort order. Each
|
||||
* possible property name is enumerated by the SortableValue enum.
|
||||
*
|
||||
* @param value
|
||||
* The sort predicate string to parse, which must consist ONLY of a
|
||||
* valid property name, possibly preceded by the DESCENDING_PREFIX.
|
||||
*
|
||||
* @throws APIException
|
||||
* If the provided sort predicate string is invalid.
|
||||
*/
|
||||
public APIConnectionRecordSortPredicate(String value)
|
||||
throws APIException {
|
||||
|
||||
// Parse whether sort order is descending
|
||||
if (value.startsWith(DESCENDING_PREFIX)) {
|
||||
descending = true;
|
||||
value = value.substring(DESCENDING_PREFIX.length());
|
||||
}
|
||||
|
||||
// Parse sorting property into ConnectionRecordSet.SortableProperty
|
||||
try {
|
||||
this.property = SortableProperty.valueOf(value).recordProperty;
|
||||
}
|
||||
|
||||
// Bail out if sort property is not valid
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new APIException(
|
||||
APIError.Type.BAD_REQUEST,
|
||||
String.format("Invalid sort property: \"%s\"", value)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SortableProperty defined by ConnectionRecordSet which
|
||||
* represents the property requested.
|
||||
*
|
||||
* @return
|
||||
* The ConnectionRecordSet.SortableProperty which refers to the same
|
||||
* property as the string originally provided when this
|
||||
* APIConnectionRecordSortPredicate was created.
|
||||
*/
|
||||
public ConnectionRecordSet.SortableProperty getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the requested sort order is descending.
|
||||
*
|
||||
* @return
|
||||
* true if the sort order is descending, false if the sort order is
|
||||
* ascending.
|
||||
*/
|
||||
public boolean isDescending() {
|
||||
return descending;
|
||||
}
|
||||
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.history;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
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 org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecordSet;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A REST Service for retrieving and managing the history records of Guacamole
|
||||
* objects.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/data/{dataSource}/history")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class HistoryRESTService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(HistoryRESTService.class);
|
||||
|
||||
/**
|
||||
* The maximum number of history records to return in any one response.
|
||||
*/
|
||||
private static final int MAXIMUM_HISTORY_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* A service for authenticating users from auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Retrieves the usage history for all connections, restricted by optional
|
||||
* filter parameters.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext containing the connection whose history is to be
|
||||
* retrieved.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The set of strings that each must occur somewhere within the
|
||||
* returned connection records, whether within the associated username,
|
||||
* the name of the associated connection, or any associated date. If
|
||||
* non-empty, any connection record not matching each of the strings
|
||||
* within the collection will be excluded from the results.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to apply while sorting the resulting connection
|
||||
* records, describing the properties involved and the sort order for
|
||||
* those properties.
|
||||
*
|
||||
* @return
|
||||
* A list of connection records, describing the start and end times of
|
||||
* various usages of this connection.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection history.
|
||||
*/
|
||||
@GET
|
||||
@Path("/connections")
|
||||
public List<APIConnectionRecord> getConnectionHistory(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@QueryParam("contains") List<String> requiredContents,
|
||||
@QueryParam("order") List<APIConnectionRecordSortPredicate> sortPredicates)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Retrieve overall connection history
|
||||
ConnectionRecordSet history = userContext.getConnectionHistory();
|
||||
|
||||
// Restrict to records which contain the specified strings
|
||||
for (String required : requiredContents) {
|
||||
if (!required.isEmpty())
|
||||
history = history.contains(required);
|
||||
}
|
||||
|
||||
// Sort according to specified ordering
|
||||
for (APIConnectionRecordSortPredicate predicate : sortPredicates)
|
||||
history = history.sort(predicate.getProperty(), predicate.isDescending());
|
||||
|
||||
// Limit to maximum result size
|
||||
history = history.limit(MAXIMUM_HISTORY_SIZE);
|
||||
|
||||
// Convert record set to collection of API connection records
|
||||
List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
|
||||
for (ConnectionRecord record : history.asCollection())
|
||||
apiRecords.add(new APIConnectionRecord(record));
|
||||
|
||||
// Return the converted history
|
||||
return apiRecords;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 retrieval or maintenance of history records using the
|
||||
* Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.history;
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.language;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.apache.guacamole.net.basic.extension.LanguageResourceService;
|
||||
|
||||
|
||||
/**
|
||||
* A REST Service for handling the listing of languages.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@Path("/languages")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class LanguageRESTService {
|
||||
|
||||
/**
|
||||
* Service for retrieving information regarding available language
|
||||
* resources.
|
||||
*/
|
||||
@Inject
|
||||
private LanguageResourceService languageResourceService;
|
||||
|
||||
/**
|
||||
* Returns a map of all available language keys to their corresponding
|
||||
* human-readable names.
|
||||
*
|
||||
* @return
|
||||
* A map of languages defined in the system, of language key to
|
||||
* display name.
|
||||
*/
|
||||
@GET
|
||||
public Map<String, String> getLanguages() {
|
||||
return languageResourceService.getLanguageNames();
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -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 language retrieval aspect of the Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.language;
|
||||
|
||||
@@ -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 basic Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest;
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.apache.guacamole.net.basic.rest.patch;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.net.basic.extension.PatchResourceService;
|
||||
import org.apache.guacamole.net.basic.resource.Resource;
|
||||
|
||||
/**
|
||||
* A REST Service for handling the listing of HTML patches.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/patches")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class PatchRESTService {
|
||||
|
||||
/**
|
||||
* Service for retrieving information regarding available HTML patch
|
||||
* resources.
|
||||
*/
|
||||
@Inject
|
||||
private PatchResourceService patchResourceService;
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the given resource as a String. The
|
||||
* resource is assumed to be encoded in UTF-8.
|
||||
*
|
||||
* @param resource
|
||||
* The resource to read as a new String.
|
||||
*
|
||||
* @return
|
||||
* A new String containing the contents of the given resource.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error prevents reading the resource.
|
||||
*/
|
||||
private String readResourceAsString(Resource resource) throws IOException {
|
||||
|
||||
StringBuilder contents = new StringBuilder();
|
||||
|
||||
// Read entire resource into StringBuilder one chunk at a time
|
||||
Reader reader = new InputStreamReader(resource.asStream(), "UTF-8");
|
||||
try {
|
||||
|
||||
char buffer[] = new char[8192];
|
||||
int length;
|
||||
|
||||
while ((length = reader.read(buffer)) != -1) {
|
||||
contents.append(buffer, 0, length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ensure resource is always closed
|
||||
finally {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
return contents.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all available HTML patches, in the order they should
|
||||
* be applied. Each patch is raw HTML containing additional meta tags
|
||||
* describing how and where the patch should be applied.
|
||||
*
|
||||
* @return
|
||||
* A list of all HTML patches defined in the system, in the order they
|
||||
* should be applied.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs preventing any HTML patch from being read.
|
||||
*/
|
||||
@GET
|
||||
public List<String> getPatches() throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Allocate a list of equal size to the total number of patches
|
||||
List<Resource> resources = patchResourceService.getPatchResources();
|
||||
List<String> patches = new ArrayList<String>(resources.size());
|
||||
|
||||
// Convert each patch resource to a string
|
||||
for (Resource resource : resources) {
|
||||
patches.add(readResourceAsString(resource));
|
||||
}
|
||||
|
||||
// Return all patches in string form
|
||||
return patches;
|
||||
|
||||
}
|
||||
|
||||
// Bail out entirely on error
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Unable to read HTML patches.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 HTML patch retrieval aspect of the Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.patch;
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.permission;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
|
||||
/**
|
||||
* The set of permissions which are granted to a specific user, organized by
|
||||
* object type and, if applicable, identifier. This object can be constructed
|
||||
* with arbitrary permissions present, or manipulated after creation through
|
||||
* the manipulation or replacement of its collections of permissions, but is
|
||||
* otherwise not intended for internal use as a data structure for permissions.
|
||||
* Its primary purpose is as a hierarchical format for exchanging granted
|
||||
* permissions with REST clients.
|
||||
*/
|
||||
public class APIPermissionSet {
|
||||
|
||||
/**
|
||||
* Map of connection ID to the set of granted permissions.
|
||||
*/
|
||||
private Map<String, Set<ObjectPermission.Type>> connectionPermissions =
|
||||
new HashMap<String, Set<ObjectPermission.Type>>();
|
||||
|
||||
/**
|
||||
* Map of connection group ID to the set of granted permissions.
|
||||
*/
|
||||
private Map<String, Set<ObjectPermission.Type>> connectionGroupPermissions =
|
||||
new HashMap<String, Set<ObjectPermission.Type>>();
|
||||
|
||||
/**
|
||||
* Map of active connection ID to the set of granted permissions.
|
||||
*/
|
||||
private Map<String, Set<ObjectPermission.Type>> activeConnectionPermissions =
|
||||
new HashMap<String, Set<ObjectPermission.Type>>();
|
||||
|
||||
/**
|
||||
* Map of user ID to the set of granted permissions.
|
||||
*/
|
||||
private Map<String, Set<ObjectPermission.Type>> userPermissions =
|
||||
new HashMap<String, Set<ObjectPermission.Type>>();
|
||||
|
||||
/**
|
||||
* Set of all granted system-level permissions.
|
||||
*/
|
||||
private Set<SystemPermission.Type> systemPermissions =
|
||||
EnumSet.noneOf(SystemPermission.Type.class);
|
||||
|
||||
/**
|
||||
* Creates a new permission set which contains no granted permissions. Any
|
||||
* permissions must be added by manipulating or replacing the applicable
|
||||
* permission collection.
|
||||
*/
|
||||
public APIPermissionSet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the system permissions from the given SystemPermissionSet to the
|
||||
* Set of system permissions provided.
|
||||
*
|
||||
* @param permissions
|
||||
* The Set to add system permissions to.
|
||||
*
|
||||
* @param permSet
|
||||
* The SystemPermissionSet containing the system permissions to add.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving system permissions from the
|
||||
* SystemPermissionSet.
|
||||
*/
|
||||
private void addSystemPermissions(Set<SystemPermission.Type> permissions,
|
||||
SystemPermissionSet permSet) throws GuacamoleException {
|
||||
|
||||
// Add all provided system permissions
|
||||
for (SystemPermission permission : permSet.getPermissions())
|
||||
permissions.add(permission.getType());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the object permissions from the given ObjectPermissionSet to the
|
||||
* Map of object permissions provided.
|
||||
*
|
||||
* @param permissions
|
||||
* The Map to add object permissions to.
|
||||
*
|
||||
* @param permSet
|
||||
* The ObjectPermissionSet containing the object permissions to add.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving object permissions from the
|
||||
* ObjectPermissionSet.
|
||||
*/
|
||||
private void addObjectPermissions(Map<String, Set<ObjectPermission.Type>> permissions,
|
||||
ObjectPermissionSet permSet) throws GuacamoleException {
|
||||
|
||||
// Add all provided object permissions
|
||||
for (ObjectPermission permission : permSet.getPermissions()) {
|
||||
|
||||
// Get associated set of permissions
|
||||
String identifier = permission.getObjectIdentifier();
|
||||
Set<ObjectPermission.Type> objectPermissions = permissions.get(identifier);
|
||||
|
||||
// Create new set if none yet exists
|
||||
if (objectPermissions == null)
|
||||
permissions.put(identifier, EnumSet.of(permission.getType()));
|
||||
|
||||
// Otherwise add to existing set
|
||||
else
|
||||
objectPermissions.add(permission.getType());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permission set containing all permissions currently
|
||||
* granted to the given user.
|
||||
*
|
||||
* @param user
|
||||
* The user whose permissions should be stored within this permission
|
||||
* set.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user's permissions.
|
||||
*/
|
||||
public APIPermissionSet(User user) throws GuacamoleException {
|
||||
|
||||
// Add all permissions from the provided user
|
||||
addSystemPermissions(systemPermissions, user.getSystemPermissions());
|
||||
addObjectPermissions(connectionPermissions, user.getConnectionPermissions());
|
||||
addObjectPermissions(connectionGroupPermissions, user.getConnectionGroupPermissions());
|
||||
addObjectPermissions(activeConnectionPermissions, user.getActiveConnectionPermissions());
|
||||
addObjectPermissions(userPermissions, user.getUserPermissions());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of connection IDs to the set of permissions granted for
|
||||
* that connection. If no permissions are granted to a particular
|
||||
* connection, its ID will not be present as a key in the map. This map is
|
||||
* mutable, and changes to this map will affect the permission set
|
||||
* directly.
|
||||
*
|
||||
* @return
|
||||
* A map of connection IDs to the set of permissions granted for that
|
||||
* connection.
|
||||
*/
|
||||
public Map<String, Set<ObjectPermission.Type>> getConnectionPermissions() {
|
||||
return connectionPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of connection group IDs to the set of permissions granted
|
||||
* for that connection group. If no permissions are granted to a particular
|
||||
* connection group, its ID will not be present as a key in the map. This
|
||||
* map is mutable, and changes to this map will affect the permission set
|
||||
* directly.
|
||||
*
|
||||
* @return
|
||||
* A map of connection group IDs to the set of permissions granted for
|
||||
* that connection group.
|
||||
*/
|
||||
public Map<String, Set<ObjectPermission.Type>> getConnectionGroupPermissions() {
|
||||
return connectionGroupPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of active connection IDs to the set of permissions granted
|
||||
* for that active connection. If no permissions are granted to a particular
|
||||
* active connection, its ID will not be present as a key in the map. This
|
||||
* map is mutable, and changes to this map will affect the permission set
|
||||
* directly.
|
||||
*
|
||||
* @return
|
||||
* A map of active connection IDs to the set of permissions granted for
|
||||
* that active connection.
|
||||
*/
|
||||
public Map<String, Set<ObjectPermission.Type>> getActiveConnectionPermissions() {
|
||||
return activeConnectionPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of user IDs to the set of permissions granted for that
|
||||
* user. If no permissions are granted to a particular user, its ID will
|
||||
* not be present as a key in the map. This map is mutable, and changes to
|
||||
* to this map will affect the permission set directly.
|
||||
*
|
||||
* @return
|
||||
* A map of user IDs to the set of permissions granted for that user.
|
||||
*/
|
||||
public Map<String, Set<ObjectPermission.Type>> getUserPermissions() {
|
||||
return userPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of granted system-level permissions. If no permissions
|
||||
* are granted at the system level, this will be an empty set. This set is
|
||||
* mutable, and changes to this set will affect the permission set
|
||||
* directly.
|
||||
*
|
||||
* @return
|
||||
* The set of granted system-level permissions.
|
||||
*/
|
||||
public Set<SystemPermission.Type> getSystemPermissions() {
|
||||
return systemPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current map of connection permissions with the given map,
|
||||
* which must map connection ID to its corresponding set of granted
|
||||
* permissions. If a connection has no permissions, its ID must not be
|
||||
* present as a key in the map.
|
||||
*
|
||||
* @param connectionPermissions
|
||||
* The map which must replace the currently-stored map of permissions.
|
||||
*/
|
||||
public void setConnectionPermissions(Map<String, Set<ObjectPermission.Type>> connectionPermissions) {
|
||||
this.connectionPermissions = connectionPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current map of connection group permissions with the given
|
||||
* map, which must map connection group ID to its corresponding set of
|
||||
* granted permissions. If a connection group has no permissions, its ID
|
||||
* must not be present as a key in the map.
|
||||
*
|
||||
* @param connectionGroupPermissions
|
||||
* The map which must replace the currently-stored map of permissions.
|
||||
*/
|
||||
public void setConnectionGroupPermissions(Map<String, Set<ObjectPermission.Type>> connectionGroupPermissions) {
|
||||
this.connectionGroupPermissions = connectionGroupPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current map of active connection permissions with the give
|
||||
* map, which must map active connection ID to its corresponding set of
|
||||
* granted permissions. If an active connection has no permissions, its ID
|
||||
* must not be present as a key in the map.
|
||||
*
|
||||
* @param activeConnectionPermissions
|
||||
* The map which must replace the currently-stored map of permissions.
|
||||
*/
|
||||
public void setActiveConnectionPermissions(Map<String, Set<ObjectPermission.Type>> activeConnectionPermissions) {
|
||||
this.activeConnectionPermissions = activeConnectionPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current map of user permissions with the given map, which
|
||||
* must map user ID to its corresponding set of granted permissions. If a
|
||||
* user has no permissions, its ID must not be present as a key in the map.
|
||||
*
|
||||
* @param userPermissions
|
||||
* The map which must replace the currently-stored map of permissions.
|
||||
*/
|
||||
public void setUserPermissions(Map<String, Set<ObjectPermission.Type>> userPermissions) {
|
||||
this.userPermissions = userPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current set of system-level permissions with the given set.
|
||||
* If no system-level permissions are granted, the empty set must be
|
||||
* specified.
|
||||
*
|
||||
* @param systemPermissions
|
||||
* The set which must replace the currently-stored set of permissions.
|
||||
*/
|
||||
public void setSystemPermissions(Set<SystemPermission.Type> systemPermissions) {
|
||||
this.systemPermissions = systemPermissions;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -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.apache.guacamole.net.basic.rest.permission;
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.schema;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
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 org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.form.Form;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.apache.guacamole.protocols.ProtocolInfo;
|
||||
|
||||
/**
|
||||
* A REST service which provides access to descriptions of the properties,
|
||||
* attributes, etc. of objects used within the Guacamole REST API.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/schema/{dataSource}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class SchemaRESTService {
|
||||
|
||||
/**
|
||||
* A service for authenticating users from auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Retrieves the possible attributes of a user object.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext dictating the available user attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* user object.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the possible attributes.
|
||||
*/
|
||||
@GET
|
||||
@Path("/users/attributes")
|
||||
public Collection<Form> getUserAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible user attributes
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getUserAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the possible attributes of a connection object.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext dictating the available connection attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* connection object.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the possible attributes.
|
||||
*/
|
||||
@GET
|
||||
@Path("/connections/attributes")
|
||||
public Collection<Form> getConnectionAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible connection attributes
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getConnectionAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the possible attributes of a connection group object.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext dictating the available connection group
|
||||
* attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* connection group object.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the possible attributes.
|
||||
*/
|
||||
@GET
|
||||
@Path("/connectionGroups/attributes")
|
||||
public Collection<Form> getConnectionGroupAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible connection group attributes
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getConnectionGroupAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a map of protocols defined in the system - protocol name to protocol.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext dictating the protocols available. Currently, the
|
||||
* UserContext actually does not dictate this, the the same set of
|
||||
* protocols will be retrieved for all users, though the identifier
|
||||
* given here will be validated.
|
||||
*
|
||||
* @return
|
||||
* A map of protocol information, where each key is the unique name
|
||||
* associated with that protocol.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the available protocols.
|
||||
*/
|
||||
@GET
|
||||
@Path("/protocols")
|
||||
public Map<String, ProtocolInfo> getProtocols(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Verify the given auth token and auth provider identifier are valid
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get and return a map of all protocols.
|
||||
Environment env = new LocalEnvironment();
|
||||
return env.getProtocols();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 self-description of the Guacamole REST API, such as
|
||||
* the attributes or parameters applicable to specific objects.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.schema;
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
import java.util.Map;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.apache.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;
|
||||
|
||||
/**
|
||||
* Map of all associated attributes by attribute identifier.
|
||||
*/
|
||||
private Map<String, String> attributes;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
// Set user information
|
||||
this.username = user.getIdentifier();
|
||||
this.password = user.getPassword();
|
||||
|
||||
// Associate any attributes
|
||||
this.attributes = user.getAttributes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all attributes associated with this user. Each entry
|
||||
* key is the attribute identifier, while each value is the attribute
|
||||
* value itself.
|
||||
*
|
||||
* @return
|
||||
* The attribute map for this user.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map of all attributes associated with this user. Each entry key
|
||||
* is the attribute identifier, while each value is the attribute value
|
||||
* itself.
|
||||
*
|
||||
* @param attributes
|
||||
* The attribute map for this user.
|
||||
*/
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
/**
|
||||
* All the information necessary for the password update operation on a user.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class APIUserPasswordUpdate {
|
||||
|
||||
/**
|
||||
* The old (current) password of this user.
|
||||
*/
|
||||
private String oldPassword;
|
||||
|
||||
/**
|
||||
* The new password of this user.
|
||||
*/
|
||||
private String newPassword;
|
||||
|
||||
/**
|
||||
* Returns the old password for this user. This password must match the
|
||||
* user's current password for the password update operation to succeed.
|
||||
*
|
||||
* @return
|
||||
* The old password for this user.
|
||||
*/
|
||||
public String getOldPassword() {
|
||||
return oldPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the old password for this user. This password must match the
|
||||
* user's current password for the password update operation to succeed.
|
||||
*
|
||||
* @param oldPassword
|
||||
* The old password for this user.
|
||||
*/
|
||||
public void setOldPassword(String oldPassword) {
|
||||
this.oldPassword = oldPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new password that will be assigned to this user.
|
||||
*
|
||||
* @return
|
||||
* The new password for this user.
|
||||
*/
|
||||
public String getNewPassword() {
|
||||
return newPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new password that will be assigned to this user.
|
||||
*
|
||||
* @param newPassword
|
||||
* The new password for this user.
|
||||
*/
|
||||
public void setNewPassword(String newPassword) {
|
||||
this.newPassword = newPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleUnsupportedException;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
|
||||
/**
|
||||
* A wrapper to make an APIUser look like a User. Useful where an
|
||||
* org.apache.guacamole.net.auth.User is required. As a simple wrapper for
|
||||
* APIUser, access to permissions is not provided. Any attempt to access or
|
||||
* manipulate permissions on an APIUserWrapper will result in an exception.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class APIUserWrapper implements User {
|
||||
|
||||
/**
|
||||
* The wrapped APIUser.
|
||||
*/
|
||||
private final APIUser apiUser;
|
||||
|
||||
/**
|
||||
* Wrap a given APIUser to expose as a User.
|
||||
* @param apiUser The APIUser to wrap.
|
||||
*/
|
||||
public APIUserWrapper(APIUser apiUser) {
|
||||
this.apiUser = apiUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return apiUser.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifier(String username) {
|
||||
apiUser.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return apiUser.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String password) {
|
||||
apiUser.setPassword(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return apiUser.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
apiUser.setAttributes(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemPermissionSet getSystemPermissions()
|
||||
throws GuacamoleException {
|
||||
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPermissionSet getConnectionPermissions()
|
||||
throws GuacamoleException {
|
||||
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPermissionSet getConnectionGroupPermissions()
|
||||
throws GuacamoleException {
|
||||
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPermissionSet getUserPermissions()
|
||||
throws GuacamoleException {
|
||||
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPermissionSet getActiveConnectionPermissions()
|
||||
throws GuacamoleException {
|
||||
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
|
||||
}
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.permission.Permission;
|
||||
import org.apache.guacamole.net.auth.permission.PermissionSet;
|
||||
|
||||
/**
|
||||
* A set of changes to be applied to a PermissionSet, describing the set of
|
||||
* permissions being added and removed.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
* @param <PermissionType>
|
||||
* The type of permissions being added and removed.
|
||||
*/
|
||||
public class PermissionSetPatch<PermissionType extends Permission> {
|
||||
|
||||
/**
|
||||
* The set of all permissions being added.
|
||||
*/
|
||||
private final Set<PermissionType> addedPermissions =
|
||||
new HashSet<PermissionType>();
|
||||
|
||||
/**
|
||||
* The set of all permissions being removed.
|
||||
*/
|
||||
private final Set<PermissionType> removedPermissions =
|
||||
new HashSet<PermissionType>();
|
||||
|
||||
/**
|
||||
* Queues the given permission to be added. The add operation will be
|
||||
* performed only when apply() is called.
|
||||
*
|
||||
* @param permission
|
||||
* The permission to add.
|
||||
*/
|
||||
public void addPermission(PermissionType permission) {
|
||||
addedPermissions.add(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given permission to be removed. The remove operation will be
|
||||
* performed only when apply() is called.
|
||||
*
|
||||
* @param permission
|
||||
* The permission to remove.
|
||||
*/
|
||||
public void removePermission(PermissionType permission) {
|
||||
removedPermissions.add(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all queued changes to the given permission set.
|
||||
*
|
||||
* @param permissionSet
|
||||
* The permission set to add and remove permissions from.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while manipulating the permissions of the given
|
||||
* permission set.
|
||||
*/
|
||||
public void apply(PermissionSet<PermissionType> permissionSet)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Add any added permissions
|
||||
if (!addedPermissions.isEmpty())
|
||||
permissionSet.addPermissions(addedPermissions);
|
||||
|
||||
// Remove any removed permissions
|
||||
if (!removedPermissions.isEmpty())
|
||||
permissionSet.removePermissions(removedPermissions);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+647
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
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.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.GuacamoleSecurityException;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.apache.guacamole.net.auth.permission.Permission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
import org.apache.guacamole.net.basic.GuacamoleSession;
|
||||
import org.apache.guacamole.net.basic.rest.APIPatch;
|
||||
import static org.apache.guacamole.net.basic.rest.APIPatch.Operation.add;
|
||||
import static org.apache.guacamole.net.basic.rest.APIPatch.Operation.remove;
|
||||
import org.apache.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.apache.guacamole.net.basic.rest.PATCH;
|
||||
import org.apache.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.apache.guacamole.net.basic.rest.permission.APIPermissionSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A REST Service for handling user CRUD operations.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/data/{dataSource}/users")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class UserRESTService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserRESTService.class);
|
||||
|
||||
/**
|
||||
* The prefix of any path within an operation of a JSON patch which
|
||||
* modifies the permissions of a user regarding a specific connection.
|
||||
*/
|
||||
private static final String CONNECTION_PERMISSION_PATCH_PATH_PREFIX = "/connectionPermissions/";
|
||||
|
||||
/**
|
||||
* The prefix of any path within an operation of a JSON patch which
|
||||
* modifies the permissions of a user regarding a specific connection group.
|
||||
*/
|
||||
private static final String CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX = "/connectionGroupPermissions/";
|
||||
|
||||
/**
|
||||
* The prefix of any path within an operation of a JSON patch which
|
||||
* modifies the permissions of a user regarding a specific active connection.
|
||||
*/
|
||||
private static final String ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX = "/activeConnectionPermissions/";
|
||||
|
||||
/**
|
||||
* The prefix of any path within an operation of a JSON patch which
|
||||
* modifies the permissions of a user regarding another, specific user.
|
||||
*/
|
||||
private static final String USER_PERMISSION_PATCH_PATH_PREFIX = "/userPermissions/";
|
||||
|
||||
/**
|
||||
* The path of any operation within a JSON patch which modifies the
|
||||
* permissions of a user regarding the entire system.
|
||||
*/
|
||||
private static final String SYSTEM_PERMISSION_PATCH_PATH = "/systemPermissions";
|
||||
|
||||
/**
|
||||
* A service for authenticating users from auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Gets a list of users in the given data source (UserContext), filtering
|
||||
* the returned list by the given permission, if specified.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext from which the users are to be retrieved.
|
||||
*
|
||||
* @param permissions
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for a user to appear in the result.
|
||||
* If null, no filtering will be performed.
|
||||
*
|
||||
* @return
|
||||
* A list of all visible users. If a permission was specified, this
|
||||
* list will contain only those users for whom the current user has
|
||||
* that permission.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error is encountered while retrieving users.
|
||||
*/
|
||||
@GET
|
||||
public List<APIUser> getUsers(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// An admin user has access to any user
|
||||
User self = userContext.self();
|
||||
SystemPermissionSet systemPermissions = self.getSystemPermissions();
|
||||
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
|
||||
// Filter users, if requested
|
||||
Collection<String> userIdentifiers = userDirectory.getIdentifiers();
|
||||
if (!isAdmin && permissions != null && !permissions.isEmpty()) {
|
||||
ObjectPermissionSet userPermissions = self.getUserPermissions();
|
||||
userIdentifiers = userPermissions.getAccessibleObjects(permissions, userIdentifiers);
|
||||
}
|
||||
|
||||
// Retrieve all users, converting to API users
|
||||
List<APIUser> apiUsers = new ArrayList<APIUser>();
|
||||
for (User user : userDirectory.getAll(userIdentifiers))
|
||||
apiUsers.add(new APIUser(user));
|
||||
|
||||
return apiUsers;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an individual user.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext from which the requested user is to be retrieved.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to retrieve.
|
||||
*
|
||||
* @return user
|
||||
* The user having the given username.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{username}")
|
||||
public APIUser getUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested user
|
||||
User user = retrievalService.retrieveUser(session, authProviderIdentifier, username);
|
||||
return new APIUser(user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user and returns the user that was created.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the requested user is to be created.
|
||||
*
|
||||
* @param user
|
||||
* The new user to create.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a problem is encountered while creating the user.
|
||||
*
|
||||
* @return
|
||||
* The newly created user.
|
||||
*/
|
||||
@POST
|
||||
public APIUser createUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier, APIUser user)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an individual existing user.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the requested user is to be updated.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to update.
|
||||
*
|
||||
* @param user
|
||||
* The data to update the user with.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while updating the user.
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{username}")
|
||||
public void updateUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username, APIUser user)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
|
||||
// Validate data and path are sane
|
||||
if (!user.getUsername().equals(username))
|
||||
throw new GuacamoleClientException("Username in path does not match username provided JSON data.");
|
||||
|
||||
// A user may not use this endpoint to modify himself
|
||||
if (userContext.self().getIdentifier().equals(user.getUsername()))
|
||||
throw new GuacamoleSecurityException("Permission denied.");
|
||||
|
||||
// Get the user
|
||||
User existingUser = retrievalService.retrieveUser(userContext, username);
|
||||
|
||||
// Do not update the user password if no password was provided
|
||||
if (user.getPassword() != null)
|
||||
existingUser.setPassword(user.getPassword());
|
||||
|
||||
// Update user attributes
|
||||
existingUser.setAttributes(user.getAttributes());
|
||||
|
||||
// Update the user
|
||||
userDirectory.update(existingUser);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the password for an individual existing user.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the requested user is to be updated.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to update.
|
||||
*
|
||||
* @param userPasswordUpdate
|
||||
* The object containing the old password for the user, as well as the
|
||||
* new password to set for that user.
|
||||
*
|
||||
* @param request
|
||||
* The HttpServletRequest associated with the password update attempt.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while updating the user's password.
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{username}/password")
|
||||
public void updatePassword(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username,
|
||||
APIUserPasswordUpdate userPasswordUpdate,
|
||||
@Context HttpServletRequest request) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Build credentials
|
||||
Credentials credentials = new Credentials();
|
||||
credentials.setUsername(username);
|
||||
credentials.setPassword(userPasswordUpdate.getOldPassword());
|
||||
credentials.setRequest(request);
|
||||
credentials.setSession(request.getSession(true));
|
||||
|
||||
// Verify that the old password was correct
|
||||
try {
|
||||
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
|
||||
if (authProvider.authenticateUser(credentials) == null)
|
||||
throw new GuacamoleSecurityException("Permission denied.");
|
||||
}
|
||||
|
||||
// Pass through any credentials exceptions as simple permission denied
|
||||
catch (GuacamoleCredentialsException e) {
|
||||
throw new GuacamoleSecurityException("Permission denied.");
|
||||
}
|
||||
|
||||
// Get the user directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
|
||||
// Get the user that we want to updates
|
||||
User user = retrievalService.retrieveUser(userContext, username);
|
||||
|
||||
// Set password to the newly provided one
|
||||
user.setPassword(userPasswordUpdate.getNewPassword());
|
||||
|
||||
// Update the user
|
||||
userDirectory.update(user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an individual existing user.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext from which the requested user is to be deleted.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to delete.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while deleting the user.
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{username}")
|
||||
public void deleteUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
|
||||
// Get the user
|
||||
User existingUser = userDirectory.get(username);
|
||||
if (existingUser == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
|
||||
|
||||
// Delete the user
|
||||
userDirectory.remove(username);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of permissions for the user with the given username.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the requested user is to be found.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to retrieve permissions for.
|
||||
*
|
||||
* @return
|
||||
* A list of all permissions granted to the specified user.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving permissions.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{username}/permissions")
|
||||
public APIPermissionSet getPermissions(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
User user;
|
||||
|
||||
// If username is own username, just use self - might not have query permissions
|
||||
if (userContext.self().getIdentifier().equals(username))
|
||||
user = userContext.self();
|
||||
|
||||
// If not self, query corresponding user from directory
|
||||
else {
|
||||
user = userContext.getUserDirectory().get(username);
|
||||
if (user == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
|
||||
}
|
||||
|
||||
return new APIPermissionSet(user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given permission set patch by queuing an add or remove
|
||||
* operation for the given permission based on the given patch operation.
|
||||
*
|
||||
* @param <PermissionType>
|
||||
* The type of permission stored within the permission set.
|
||||
*
|
||||
* @param operation
|
||||
* The patch operation to perform.
|
||||
*
|
||||
* @param permissionSetPatch
|
||||
* The permission set patch being modified.
|
||||
*
|
||||
* @param permission
|
||||
* The permission being added or removed from the set.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the requested patch operation is not supported.
|
||||
*/
|
||||
private <PermissionType extends Permission> void updatePermissionSet(
|
||||
APIPatch.Operation operation,
|
||||
PermissionSetPatch<PermissionType> permissionSetPatch,
|
||||
PermissionType permission) throws GuacamoleException {
|
||||
|
||||
// Add or remove permission based on operation
|
||||
switch (operation) {
|
||||
|
||||
// Add permission
|
||||
case add:
|
||||
permissionSetPatch.addPermission(permission);
|
||||
break;
|
||||
|
||||
// Remove permission
|
||||
case remove:
|
||||
permissionSetPatch.removePermission(permission);
|
||||
break;
|
||||
|
||||
// Unsupported patch operation
|
||||
default:
|
||||
throw new GuacamoleClientException("Unsupported patch operation: \"" + operation + "\"");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a given list of permission patches. Each patch specifies either
|
||||
* an "add" or a "remove" operation for a permission type, represented by
|
||||
* a string. Valid permission types depend on the path of each patch
|
||||
* operation, as the path dictates the permission being modified, such as
|
||||
* "/connectionPermissions/42" or "/systemPermissions".
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider associated with
|
||||
* the UserContext in which the requested user is to be found.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to modify the permissions of.
|
||||
*
|
||||
* @param patches
|
||||
* The permission patches to apply for this request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a problem is encountered while modifying permissions.
|
||||
*/
|
||||
@PATCH
|
||||
@Path("/{username}/permissions")
|
||||
public void patchPermissions(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username,
|
||||
List<APIPatch<String>> patches) throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the user
|
||||
User user = userContext.getUserDirectory().get(username);
|
||||
if (user == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
|
||||
|
||||
// Permission patches for all types of permissions
|
||||
PermissionSetPatch<ObjectPermission> connectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
|
||||
PermissionSetPatch<ObjectPermission> connectionGroupPermissionPatch = new PermissionSetPatch<ObjectPermission>();
|
||||
PermissionSetPatch<ObjectPermission> activeConnectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
|
||||
PermissionSetPatch<ObjectPermission> userPermissionPatch = new PermissionSetPatch<ObjectPermission>();
|
||||
PermissionSetPatch<SystemPermission> systemPermissionPatch = new PermissionSetPatch<SystemPermission>();
|
||||
|
||||
// Apply all patch operations individually
|
||||
for (APIPatch<String> patch : patches) {
|
||||
|
||||
String path = patch.getPath();
|
||||
|
||||
// Create connection permission if path has connection prefix
|
||||
if (path.startsWith(CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
|
||||
|
||||
// Get identifier and type from patch operation
|
||||
String identifier = path.substring(CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
|
||||
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
|
||||
|
||||
// Create and update corresponding permission
|
||||
ObjectPermission permission = new ObjectPermission(type, identifier);
|
||||
updatePermissionSet(patch.getOp(), connectionPermissionPatch, permission);
|
||||
|
||||
}
|
||||
|
||||
// Create connection group permission if path has connection group prefix
|
||||
else if (path.startsWith(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX)) {
|
||||
|
||||
// Get identifier and type from patch operation
|
||||
String identifier = path.substring(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX.length());
|
||||
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
|
||||
|
||||
// Create and update corresponding permission
|
||||
ObjectPermission permission = new ObjectPermission(type, identifier);
|
||||
updatePermissionSet(patch.getOp(), connectionGroupPermissionPatch, permission);
|
||||
|
||||
}
|
||||
|
||||
// Create active connection permission if path has active connection prefix
|
||||
else if (path.startsWith(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
|
||||
|
||||
// Get identifier and type from patch operation
|
||||
String identifier = path.substring(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
|
||||
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
|
||||
|
||||
// Create and update corresponding permission
|
||||
ObjectPermission permission = new ObjectPermission(type, identifier);
|
||||
updatePermissionSet(patch.getOp(), activeConnectionPermissionPatch, permission);
|
||||
|
||||
}
|
||||
|
||||
// Create user permission if path has user prefix
|
||||
else if (path.startsWith(USER_PERMISSION_PATCH_PATH_PREFIX)) {
|
||||
|
||||
// Get identifier and type from patch operation
|
||||
String identifier = path.substring(USER_PERMISSION_PATCH_PATH_PREFIX.length());
|
||||
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
|
||||
|
||||
// Create and update corresponding permission
|
||||
ObjectPermission permission = new ObjectPermission(type, identifier);
|
||||
updatePermissionSet(patch.getOp(), userPermissionPatch, permission);
|
||||
|
||||
}
|
||||
|
||||
// Create system permission if path is system path
|
||||
else if (path.equals(SYSTEM_PERMISSION_PATCH_PATH)) {
|
||||
|
||||
// Get identifier and type from patch operation
|
||||
SystemPermission.Type type = SystemPermission.Type.valueOf(patch.getValue());
|
||||
|
||||
// Create and update corresponding permission
|
||||
SystemPermission permission = new SystemPermission(type);
|
||||
updatePermissionSet(patch.getOp(), systemPermissionPatch, permission);
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, the path is not supported
|
||||
else
|
||||
throw new GuacamoleClientException("Unsupported patch path: \"" + path + "\"");
|
||||
|
||||
} // end for each patch operation
|
||||
|
||||
// Save the permission changes
|
||||
connectionPermissionPatch.apply(user.getConnectionPermissions());
|
||||
connectionGroupPermissionPatch.apply(user.getConnectionGroupPermissions());
|
||||
activeConnectionPermissionPatch.apply(user.getActiveConnectionPermissions());
|
||||
userPermissionPatch.apply(user.getUserPermissions());
|
||||
systemPermissionPatch.apply(user.getSystemPermissions());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 user manipulation aspect of the Guacamole REST API.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.rest.user;
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.apache.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.ServerEndpointConfig;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
|
||||
|
||||
/**
|
||||
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
|
||||
* HTTP, properly parsing connection IDs included in the connection request.
|
||||
*/
|
||||
public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
|
||||
|
||||
/**
|
||||
* Unique string which shall be used to store the TunnelRequest
|
||||
* associated with a WebSocket connection.
|
||||
*/
|
||||
private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
|
||||
|
||||
/**
|
||||
* Unique string which shall be used to store the TunnelRequestService to
|
||||
* be used for processing TunnelRequests.
|
||||
*/
|
||||
private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
|
||||
|
||||
/**
|
||||
* Configurator implementation which stores the requested GuacamoleTunnel
|
||||
* within the user properties. The GuacamoleTunnel will be later retrieved
|
||||
* during the connection process.
|
||||
*/
|
||||
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) {
|
||||
|
||||
super.modifyHandshake(config, request, response);
|
||||
|
||||
// Store tunnel request and tunnel request service for retrieval
|
||||
// upon WebSocket open
|
||||
Map<String, Object> userProperties = config.getUserProperties();
|
||||
userProperties.clear();
|
||||
userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
|
||||
userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel createTunnel(Session session,
|
||||
EndpointConfig config) throws GuacamoleException {
|
||||
|
||||
Map<String, Object> userProperties = config.getUserProperties();
|
||||
|
||||
// Get original tunnel request
|
||||
TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
|
||||
if (tunnelRequest == null)
|
||||
return null;
|
||||
|
||||
// Get tunnel request service
|
||||
TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
|
||||
if (tunnelRequestService == null)
|
||||
return null;
|
||||
|
||||
// Create and return tunnel
|
||||
return tunnelRequestService.createTunnel(tunnelRequest);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -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.apache.guacamole.net.basic.websocket;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.util.Arrays;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import org.apache.guacamole.net.basic.TunnelLoader;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the JSR-356 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("javax.websocket.Endpoint");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading JSR-356 WebSocket support...");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
|
||||
/**
|
||||
* WebSocket-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
*/
|
||||
private final Map<String, List<String>> handshakeParameters;
|
||||
|
||||
/**
|
||||
* Creates a TunnelRequest implementation which delegates parameter and
|
||||
* session retrieval to the given HandshakeRequest.
|
||||
*
|
||||
* @param request The HandshakeRequest to wrap.
|
||||
*/
|
||||
public WebSocketTunnelRequest(HandshakeRequest request) {
|
||||
this.handshakeParameters = request.getParameterMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
|
||||
// Pull list of values, if present
|
||||
List<String> values = getParameterValues(name);
|
||||
if (values == null || values.isEmpty())
|
||||
return null;
|
||||
|
||||
// Return first parameter value arbitrarily
|
||||
return values.get(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
return handshakeParameters.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.jetty8;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
|
||||
/**
|
||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||
* 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(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(request);
|
||||
}
|
||||
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.jetty8;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||
import org.eclipse.jetty.websocket.WebSocketServlet;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.net.basic.HTTPTunnelRequest;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
/**
|
||||
* The default, minimum buffer size for instructions.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Sends the given status on the given WebSocket connection and closes the
|
||||
* connection.
|
||||
*
|
||||
* @param connection The WebSocket connection to close.
|
||||
* @param guac_status The status to send.
|
||||
*/
|
||||
public static void closeConnection(Connection connection,
|
||||
GuacamoleStatus guac_status) {
|
||||
|
||||
connection.close(guac_status.getWebSocketCode(),
|
||||
Integer.toString(guac_status.getGuacamoleStatusCode()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
|
||||
|
||||
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
||||
|
||||
// Return new WebSocket which communicates through tunnel
|
||||
return new WebSocket.OnTextMessage() {
|
||||
|
||||
/**
|
||||
* The GuacamoleTunnel associated with the connected WebSocket. If
|
||||
* the WebSocket has not yet been connected, this will be null.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel = null;
|
||||
|
||||
@Override
|
||||
public void onMessage(String string) {
|
||||
|
||||
// Ignore inbound messages if there is no associated tunnel
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
|
||||
// Write message received
|
||||
try {
|
||||
writer.write(string.toCharArray());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("WebSocket tunnel write failed.", e);
|
||||
}
|
||||
|
||||
tunnel.releaseWriter();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(final Connection connection) {
|
||||
|
||||
try {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not start connection if tunnel does not exist
|
||||
if (tunnel == null) {
|
||||
closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||
GuacamoleReader reader = tunnel.acquireReader();
|
||||
char[] readMessage;
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to read
|
||||
while ((readMessage = reader.read()) != null) {
|
||||
|
||||
// Buffer message
|
||||
buffer.append(readMessage);
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
connection.sendMessage(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No more data
|
||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
// Catch any thrown guacamole exception and attempt
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("WebSocket tunnel read failed due to I/O error.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readThread.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int i, String string) {
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the JavaScript Guacamole client makes a connection
|
||||
* request. It it up to the implementor of this function to define what
|
||||
* conditions must be met for a tunnel to be configured and returned as a
|
||||
* result of this connection request (whether some sort of credentials must
|
||||
* be specified, for example).
|
||||
*
|
||||
* @param request
|
||||
* The TunnelRequest associated with the connection request received.
|
||||
* Any parameters specified along with the connection request can be
|
||||
* read from this object.
|
||||
*
|
||||
* @return
|
||||
* A newly constructed GuacamoleTunnel if successful, null otherwise.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while constructing the GuacamoleTunnel, or if the
|
||||
* conditions required for connection are not met.
|
||||
*/
|
||||
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
|
||||
throws GuacamoleException;
|
||||
|
||||
}
|
||||
|
||||
+73
@@ -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.apache.guacamole.net.basic.websocket.jetty8;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.apache.guacamole.net.basic.TunnelLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the Jetty 8 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("org.apache.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading Jetty 8 WebSocket support...");
|
||||
serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.websocket.jetty8;
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* WebSocketCreator which selects the appropriate WebSocketListener
|
||||
* implementation if the "guacamole" subprotocol is in use.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class BasicGuacamoleWebSocketCreator implements WebSocketCreator {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
private final TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Creates a new WebSocketCreator which uses the given TunnelRequestService
|
||||
* to create new GuacamoleTunnels for inbound requests.
|
||||
*
|
||||
* @param tunnelRequestService The service to use for inbound tunnel
|
||||
* requests.
|
||||
*/
|
||||
public BasicGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
|
||||
this.tunnelRequestService = tunnelRequestService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
|
||||
|
||||
// Validate and use "guacamole" subprotocol
|
||||
for (String subprotocol : request.getSubProtocols()) {
|
||||
|
||||
if ("guacamole".equals(subprotocol)) {
|
||||
response.setAcceptedSubProtocol(subprotocol);
|
||||
return new BasicGuacamoleWebSocketTunnelListener(tunnelRequestService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Invalid protocol
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* WebSocket listener implementation which properly parses connection IDs
|
||||
* included in the connection request.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class BasicGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
private final TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Creates a new WebSocketListener which uses the given TunnelRequestService
|
||||
* to create new GuacamoleTunnels for inbound requests.
|
||||
*
|
||||
* @param tunnelRequestService The service to use for inbound tunnel
|
||||
* requests.
|
||||
*/
|
||||
public BasicGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
|
||||
this.tunnelRequestService = tunnelRequestService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Singleton
|
||||
public class BasicGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
@Override
|
||||
public void configure(WebSocketServletFactory factory) {
|
||||
|
||||
// Register WebSocket implementation
|
||||
factory.setCreator(new BasicGuacamoleWebSocketCreator(tunnelRequestService));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus;
|
||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* WebSocket listener implementation which provides a Guacamole tunnel
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
|
||||
|
||||
/**
|
||||
* The default, minimum buffer size for instructions.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(BasicGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
/**
|
||||
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
|
||||
* as reads/writes to this tunnel.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel;
|
||||
|
||||
/**
|
||||
* Sends the given status on the given WebSocket connection and closes the
|
||||
* connection.
|
||||
*
|
||||
* @param session The outbound WebSocket connection to close.
|
||||
* @param guac_status The status to send.
|
||||
*/
|
||||
private void closeConnection(Session session, GuacamoleStatus guac_status) {
|
||||
|
||||
try {
|
||||
int code = guac_status.getWebSocketCode();
|
||||
String message = Integer.toString(guac_status.getGuacamoleStatusCode());
|
||||
session.close(new CloseStatus(code, message));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to close WebSocket connection.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new tunnel for the given session. How this tunnel is created
|
||||
* or retrieved is implementation-dependent.
|
||||
*
|
||||
* @param session The session associated with the active WebSocket
|
||||
* connection.
|
||||
* @return A connected tunnel, or null if no such tunnel exists.
|
||||
* @throws GuacamoleException If an error occurs while retrieving the
|
||||
* tunnel, or if access to the tunnel is denied.
|
||||
*/
|
||||
protected abstract GuacamoleTunnel createTunnel(Session session)
|
||||
throws GuacamoleException;
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(final Session session) {
|
||||
|
||||
try {
|
||||
|
||||
// Get tunnel
|
||||
tunnel = createTunnel(session);
|
||||
if (tunnel == null) {
|
||||
closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare read transfer thread
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
/**
|
||||
* Remote (client) side of this connection
|
||||
*/
|
||||
private final RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||
GuacamoleReader reader = tunnel.acquireReader();
|
||||
char[] readMessage;
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to read
|
||||
while ((readMessage = reader.read()) != null) {
|
||||
|
||||
// Buffer message
|
||||
buffer.append(readMessage);
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
remote.sendString(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No more data
|
||||
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
// Catch any thrown guacamole exception and attempt
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("I/O error prevents further reads.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readThread.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message) {
|
||||
|
||||
// Ignore inbound messages if there is no associated tunnel
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
|
||||
try {
|
||||
// Write received message
|
||||
writer.write(message.toCharArray());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("WebSocket tunnel write failed.", e);
|
||||
}
|
||||
|
||||
tunnel.releaseWriter();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] payload, int offset, int length) {
|
||||
throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable t) {
|
||||
|
||||
logger.debug("WebSocket tunnel closing due to error.", t);
|
||||
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason) {
|
||||
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.apache.guacamole.net.basic.TunnelLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the Jetty 9 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("org.apache.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading Jetty 9 WebSocket support...");
|
||||
serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
|
||||
/**
|
||||
* Jetty 9 WebSocket-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
*/
|
||||
private final Map<String, String[]> handshakeParameters;
|
||||
|
||||
/**
|
||||
* Creates a TunnelRequest implementation which delegates parameter and
|
||||
* session retrieval to the given UpgradeRequest.
|
||||
*
|
||||
* @param request The UpgradeRequest to wrap.
|
||||
*/
|
||||
public WebSocketTunnelRequest(UpgradeRequest request) {
|
||||
this.handshakeParameters = request.getParameterMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
|
||||
// Pull list of values, if present
|
||||
List<String> values = getParameterValues(name);
|
||||
if (values == null || values.isEmpty())
|
||||
return null;
|
||||
|
||||
// Return first parameter value arbitrarily
|
||||
return values.get(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
|
||||
String[] values = handshakeParameters.get(name);
|
||||
if (values == null)
|
||||
return null;
|
||||
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jetty 9 WebSocket tunnel implementation. The classes here require at least
|
||||
* Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.websocket.jetty9;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Standard WebSocket tunnel implementation. The classes here require a recent
|
||||
* servlet container that supports JSR 356.
|
||||
*/
|
||||
package org.apache.guacamole.net.basic.websocket;
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.apache.guacamole.net.basic.websocket.tomcat;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.basic.TunnelRequestService;
|
||||
import org.apache.guacamole.net.basic.TunnelRequest;
|
||||
|
||||
/**
|
||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||
* 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(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(request);
|
||||
};
|
||||
|
||||
}
|
||||
Loaded 100 of 402 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user