diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java
index 9a8c67235..051f6a9cb 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java
@@ -98,6 +98,18 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
@Inject
private ConnectionRecordMapper connectionRecordMapper;
+ /**
+ * The hostname to use when connecting to guacd if no hostname is provided
+ * within guacamole.properties.
+ */
+ private static final String DEFAULT_GUACD_HOSTNAME = "localhost";
+
+ /**
+ * The port to use when connecting to guacd if no port is provided within
+ * guacamole.properties.
+ */
+ private static final int DEFAULT_GUACD_PORT = 4822;
+
/**
* All active connections through the tunnel having a given UUID.
*/
@@ -266,17 +278,17 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
throws GuacamoleException {
// Use SSL if requested
- if (environment.getProperty(Environment.GUACD_SSL, true))
- return new ManagedInetGuacamoleSocket(
- environment.getRequiredProperty(Environment.GUACD_HOSTNAME),
- environment.getRequiredProperty(Environment.GUACD_PORT),
+ if (environment.getProperty(Environment.GUACD_SSL, false))
+ return new ManagedSSLGuacamoleSocket(
+ environment.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME),
+ environment.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT),
socketClosedCallback
);
// Otherwise, just use straight TCP
return new ManagedInetGuacamoleSocket(
- environment.getRequiredProperty(Environment.GUACD_HOSTNAME),
- environment.getRequiredProperty(Environment.GUACD_PORT),
+ environment.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME),
+ environment.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT),
socketClosedCallback
);
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java
new file mode 100644
index 000000000..cf3f3804d
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java
@@ -0,0 +1,71 @@
+/*
+ * 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.glyptodon.guacamole.auth.jdbc.tunnel;
+
+import org.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.net.SSLGuacamoleSocket;
+
+/**
+ * Implementation of GuacamoleSocket which connects via SSL to a given hostname
+ * and port. If the socket is closed for any reason, a given task is run.
+ *
+ * @author Michael Jumper
+ */
+public class ManagedSSLGuacamoleSocket extends SSLGuacamoleSocket {
+
+ /**
+ * The task to run when the socket is closed.
+ */
+ private final Runnable socketClosedTask;
+
+ /**
+ * Creates a new socket which connects via SSL to a given hostname and
+ * port. If the socket is closed for any reason, the given task is run.
+ *
+ * @param hostname
+ * The hostname of the Guacamole proxy server to connect to.
+ *
+ * @param port
+ * The port of the Guacamole proxy server to connect to.
+ *
+ * @param socketClosedTask
+ * The task to run when the socket is closed. This task will NOT be
+ * run if an exception occurs during connection, and this
+ * ManagedInetGuacamoleSocket instance is ultimately not created.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while connecting to the Guacamole proxy server.
+ */
+ public ManagedSSLGuacamoleSocket(String hostname, int port,
+ Runnable socketClosedTask) throws GuacamoleException {
+ super(hostname, port);
+ this.socketClosedTask = socketClosedTask;
+ }
+
+ @Override
+ public void close() throws GuacamoleException {
+ super.close();
+ socketClosedTask.run();
+ }
+
+}
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml
index 436cb6ca3..b0d898f95 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml
@@ -51,6 +51,7 @@
unpack-dependencies
+ runtime
${project.build.directory}/classes
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json
new file mode 100644
index 000000000..3c7deb865
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json
@@ -0,0 +1,13 @@
+{
+
+ "guacamoleVersion" : "0.9.6",
+
+ "name" : "MySQL Authentication",
+ "namespace" : "guac-mysql",
+
+ "authProviders" : [
+ "net.sourceforge.guacamole.net.auth.mysql.MySQLAuthenticationProvider"
+ ]
+
+}
+
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml
index 207348690..85db10341 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml
@@ -51,6 +51,7 @@
unpack-dependencies
+ runtime
${project.build.directory}/classes
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json
new file mode 100644
index 000000000..2be870f25
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json
@@ -0,0 +1,13 @@
+{
+
+ "guacamoleVersion" : "0.9.6",
+
+ "name" : "PostgreSQL Authentication",
+ "namespace" : "guac-postgresql",
+
+ "authProviders" : [
+ "org.glyptodon.guacamole.auth.postgresql.PostgreSQLAuthenticationProvider"
+ ]
+
+}
+
diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml
index 8e67ab405..340b5f4d9 100644
--- a/extensions/guacamole-auth-ldap/pom.xml
+++ b/extensions/guacamole-auth-ldap/pom.xml
@@ -32,6 +32,26 @@
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.10
+
+
+ unpack-dependencies
+ prepare-package
+
+ unpack-dependencies
+
+
+ runtime
+ ${project.build.directory}/classes
+
+
+
+
+
maven-assembly-plugin
@@ -64,6 +84,7 @@
org.glyptodon.guacamole
guacamole-common
0.9.6
+ provided
@@ -71,6 +92,7 @@
org.glyptodon.guacamole
guacamole-ext
0.9.6
+ provided
diff --git a/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml b/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml
index 9b0b27dde..59a416b70 100644
--- a/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml
+++ b/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml
@@ -11,43 +11,29 @@
tar.gz
-
+
-
-
- doc
-
+
+
+ doc
+
-
-
- schema
- schema
-
+
+
+ schema
+ schema
+
+
+
+
+ target
+
+
+ *.jar
+
+
-
-
-
-
- lib
- runtime
- false
- true
- true
-
-
-
-
- org.glyptodon.guacamole:guacamole-common
-
-
- org.glyptodon.guacamole:guacamole-ext
-
-
-
-
-
diff --git a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json
new file mode 100644
index 000000000..e8d9a9e7b
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json
@@ -0,0 +1,12 @@
+{
+
+ "guacamoleVersion" : "0.9.6",
+
+ "name" : "LDAP Authentication",
+ "namespace" : "guac-ldap",
+
+ "authProviders" : [
+ "net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider"
+ ]
+
+}
diff --git a/extensions/guacamole-auth-noauth/pom.xml b/extensions/guacamole-auth-noauth/pom.xml
index 4596d32d6..67899f164 100644
--- a/extensions/guacamole-auth-noauth/pom.xml
+++ b/extensions/guacamole-auth-noauth/pom.xml
@@ -32,6 +32,26 @@
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.10
+
+
+ unpack-dependencies
+ prepare-package
+
+ unpack-dependencies
+
+
+ runtime
+ ${project.build.directory}/classes
+
+
+
+
+
maven-assembly-plugin
@@ -64,6 +84,7 @@
org.glyptodon.guacamole
guacamole-common
0.9.6
+ provided
@@ -71,6 +92,7 @@
org.glyptodon.guacamole
guacamole-ext
0.9.6
+ provided
diff --git a/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml b/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml
index 610749432..834d5beff 100644
--- a/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml
+++ b/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml
@@ -11,7 +11,7 @@
tar.gz
-
+
@@ -19,29 +19,15 @@
doc
+
+
+ target
+
+
+ *.jar
+
+
+
-
-
-
-
- lib
- runtime
- false
- true
- true
-
-
-
-
- org.glyptodon.guacamole:guacamole-common
-
-
- org.glyptodon.guacamole:guacamole-ext
-
-
-
-
-
diff --git a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java
index 7b0adfafb..3752f8029 100644
--- a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java
+++ b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java
@@ -54,7 +54,6 @@ import org.xml.sax.helpers.XMLReaderFactory;
*
* Example `guacamole.properties`:
*
- * auth-provider: net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider
* noauth-config: /etc/guacamole/noauth-config.xml
*
*
@@ -93,7 +92,7 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider {
private final Environment environment;
/**
- * The filename of the XML file to read the user mapping from.
+ * The XML file to read the configuration from.
*/
public static final FileGuacamoleProperty NOAUTH_CONFIG = new FileGuacamoleProperty() {
@@ -104,6 +103,12 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider {
};
+ /**
+ * The default filename to use for the configuration, if not defined within
+ * guacamole.properties.
+ */
+ public static final String DEFAULT_NOAUTH_CONFIG = "noauth-config.xml";
+
/**
* Creates a new NoAuthenticationProvider that does not perform any
* authentication at all. All attempts to access the Guacamole system are
@@ -125,7 +130,14 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider {
* property.
*/
private File getConfigurationFile() throws GuacamoleException {
- return environment.getRequiredProperty(NOAUTH_CONFIG);
+
+ // Get config file, defaulting to GUACAMOLE_HOME/noauth-config.xml
+ File configFile = environment.getProperty(NOAUTH_CONFIG);
+ if (configFile == null)
+ configFile = new File(environment.getGuacamoleHome(), DEFAULT_NOAUTH_CONFIG);
+
+ return configFile;
+
}
public synchronized void init() throws GuacamoleException {
diff --git a/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json
new file mode 100644
index 000000000..03fd29916
--- /dev/null
+++ b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json
@@ -0,0 +1,12 @@
+{
+
+ "guacamoleVersion" : "0.9.6",
+
+ "name" : "Disabled Authentication",
+ "namespace" : "guac-noauth",
+
+ "authProviders" : [
+ "net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider"
+ ]
+
+}
diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java
index 5421bceec..35200c0d1 100644
--- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java
+++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java
@@ -95,28 +95,28 @@ public class LocalEnvironment implements Environment {
properties = new Properties();
try {
- InputStream stream;
+ InputStream stream = null;
// If not a directory, load from classpath
- if (!guacHome.isDirectory()) {
-
- // Read from classpath
+ if (!guacHome.isDirectory())
stream = LocalEnvironment.class.getResourceAsStream("/guacamole.properties");
- if (stream == null)
- throw new GuacamoleServerException(
- "guacamole.properties not loaded from " + guacHome
- + " (not a directory), and guacamole.properties could"
- + " not be found as a resource in the classpath.");
-
- }
// Otherwise, try to load from file
- else
- stream = new FileInputStream(new File(guacHome, "guacamole.properties"));
+ else {
+ File propertiesFile = new File(guacHome, "guacamole.properties");
+ if (propertiesFile.exists())
+ stream = new FileInputStream(propertiesFile);
+ }
- // Load properties, always close stream
- try { properties.load(stream); }
- finally { stream.close(); }
+ // Load properties from stream, if any, always closing stream when done
+ if (stream != null) {
+ try { properties.load(stream); }
+ finally { stream.close(); }
+ }
+
+ // Notify if we're proceeding without guacamole.properties
+ else
+ logger.info("No guacamole.properties file found within GUACAMOLE_HOME or the classpath. Using defaults.");
}
catch (IOException e) {
diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java
index ee3973359..e5f2f0d1e 100644
--- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java
+++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java
@@ -45,6 +45,18 @@ import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
*/
public class SimpleConnection extends AbstractConnection {
+ /**
+ * The hostname to use when connecting to guacd if no hostname is provided
+ * within guacamole.properties.
+ */
+ private static final String DEFAULT_GUACD_HOSTNAME = "localhost";
+
+ /**
+ * The port to use when connecting to guacd if no port is provided within
+ * guacamole.properties.
+ */
+ private static final int DEFAULT_GUACD_PORT = 4822;
+
/**
* Backing configuration, containing all sensitive information.
*/
@@ -92,8 +104,8 @@ public class SimpleConnection extends AbstractConnection {
Environment env = new LocalEnvironment();
// Get guacd connection parameters
- String hostname = env.getProperty(Environment.GUACD_HOSTNAME);
- int port = env.getProperty(Environment.GUACD_PORT);
+ String hostname = env.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME);
+ int port = env.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT);
GuacamoleSocket socket;
diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java
index 6694f5eea..fd7ffda9f 100644
--- a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java
+++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java
@@ -76,7 +76,7 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide
private final Environment environment;
/**
- * The filename of the XML file to read the user user_mapping from.
+ * The XML file to read the user mapping from.
*/
public static final FileGuacamoleProperty BASIC_USER_MAPPING = new FileGuacamoleProperty() {
@@ -85,6 +85,12 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide
};
+ /**
+ * The default filename to use for the user mapping, if not defined within
+ * guacamole.properties.
+ */
+ public static final String DEFAULT_USER_MAPPING = "user-mapping.xml";
+
/**
* Creates a new BasicFileAuthenticationProvider that authenticates users
* against simple, monolithic XML file.
@@ -110,9 +116,10 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide
*/
private UserMapping getUserMapping() throws GuacamoleException {
- // Get user user_mapping file
- File user_mapping_file =
- environment.getRequiredProperty(BASIC_USER_MAPPING);
+ // Get user mapping file, defaulting to GUACAMOLE_HOME/user-mapping.xml
+ File user_mapping_file = environment.getProperty(BASIC_USER_MAPPING);
+ if (user_mapping_file == null)
+ user_mapping_file = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING);
// If user_mapping not yet read, or user_mapping has been modified, reread
if (user_mapping == null ||
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java
index 82f3983de..60eeffdbb 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java
@@ -30,6 +30,7 @@ import javax.servlet.ServletContextEvent;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment;
+import org.glyptodon.guacamole.net.basic.extension.ExtensionModule;
import org.glyptodon.guacamole.net.basic.log.LogModule;
import org.glyptodon.guacamole.net.basic.rest.RESTAuthModule;
import org.glyptodon.guacamole.net.basic.rest.RESTServletModule;
@@ -83,7 +84,8 @@ public class BasicServletContextListener extends GuiceServletContextListener {
return Guice.createInjector(Stage.PRODUCTION,
new EnvironmentModule(environment),
new LogModule(environment),
- new RESTAuthModule(environment, sessionMap),
+ new ExtensionModule(environment),
+ new RESTAuthModule(sessionMap),
new RESTServletModule(),
new TunnelModule()
);
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java
index 1dcb37048..8c3f40c94 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java
@@ -39,10 +39,13 @@ import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
/**
* A ClassLoader implementation which finds classes within a configurable
- * directory. This directory is set within guacamole.properties.
+ * 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 {
/**
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java
index ed2223436..799907b39 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java
@@ -60,11 +60,6 @@ public class GuacamoleSession {
*/
private UserContext userContext;
- /**
- * Collection of all event listeners configured in guacamole.properties.
- */
- private final Collection listeners = new ArrayList();
-
/**
* The current clipboard state.
*/
@@ -98,52 +93,9 @@ public class GuacamoleSession {
*/
public GuacamoleSession(Environment environment, Credentials credentials,
UserContext userContext) throws GuacamoleException {
-
this.lastAccessedTime = System.currentTimeMillis();
this.credentials = credentials;
this.userContext = userContext;
-
- // Load listeners from guacamole.properties
- try {
-
- // Get all listener classes from properties
- Collection listenerClasses =
- environment.getProperty(BasicGuacamoleProperties.EVENT_LISTENERS);
-
- // Add an instance of each class to the list
- if (listenerClasses != null) {
- for (Class> listenerClass : listenerClasses) {
-
- // Instantiate listener
- Object listener = listenerClass.getConstructor().newInstance();
-
- // Add listener to collection of listeners
- listeners.add(listener);
-
- }
- }
-
- }
- catch (InstantiationException e) {
- throw new GuacamoleException("Listener class is abstract.", e);
- }
- catch (IllegalAccessException e) {
- throw new GuacamoleException("No access to listener constructor.", e);
- }
- catch (IllegalArgumentException e) {
- // This should not happen, given there ARE no arguments
- throw new GuacamoleException("Illegal arguments to listener constructor.", e);
- }
- catch (InvocationTargetException e) {
- throw new GuacamoleException("Error while instantiating listener.", e);
- }
- catch (NoSuchMethodException e) {
- throw new GuacamoleException("Listener has no default constructor.", e);
- }
- catch (SecurityException e) {
- throw new GuacamoleException("Security restrictions prevent instantiation of listener.", e);
- }
-
}
/**
@@ -198,19 +150,6 @@ public class GuacamoleSession {
return clipboardState;
}
- /**
- * Returns a collection which iterates over instances of all listeners
- * defined in guacamole.properties. For each listener defined in
- * guacamole.properties, a new instance is created and stored in this
- * collection.
- *
- * @return A collection which iterates over instances of all listeners
- * defined in guacamole.properties.
- */
- public Collection getListeners() {
- return Collections.unmodifiableCollection(listeners);
- }
-
/**
* Returns whether this session has any associated active tunnels.
*
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java
index 4df334947..9ee464628 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java
@@ -24,7 +24,6 @@ package org.glyptodon.guacamole.net.basic;
import com.google.inject.servlet.ServletModule;
import java.lang.reflect.InvocationTargetException;
-import org.glyptodon.guacamole.GuacamoleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,7 +54,7 @@ public class TunnelModule extends ServletModule {
try {
// Attempt to find WebSocket module
- Class> module = (Class>) GuacamoleClassLoader.getInstance().findClass(classname);
+ Class> module = Class.forName(classname);
// Create loader
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
@@ -85,12 +84,6 @@ public class TunnelModule extends ServletModule {
logger.debug("Error instantiating WebSocket module.", e);
}
- // Log all GuacamoleExceptions
- catch (GuacamoleException e) {
- logger.error("Unable to load/detect WebSocket support: {}", e.getMessage());
- logger.debug("Error loading/detecting WebSocket support.", e);
- }
-
// Load attempt failed
return false;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java
index 3f312160a..cee5cecc1 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java
@@ -38,10 +38,6 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
-import org.glyptodon.guacamole.net.event.TunnelCloseEvent;
-import org.glyptodon.guacamole.net.event.TunnelConnectEvent;
-import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener;
-import org.glyptodon.guacamole.net.event.listener.TunnelConnectListener;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,84 +72,6 @@ public class TunnelRequestService {
@Inject
private AuthenticationService authenticationService;
- /**
- * Notifies all listeners in the given session that a tunnel has been
- * connected.
- *
- * @param session The session associated with the listeners to be notified.
- * @param tunnel The tunnel being connected.
- * @return true if all listeners are allowing the tunnel to connect,
- * or if there are no listeners, and false if any listener is
- * canceling the connection. Note that once one listener cancels,
- * no other listeners will run.
- * @throws GuacamoleException If any listener throws an error while being
- * notified. Note that if any listener throws an
- * error, the connect is canceled, and no other
- * listeners will run.
- */
- private boolean notifyConnect(GuacamoleSession session, GuacamoleTunnel tunnel)
- throws GuacamoleException {
-
- // Build event for auth success
- TunnelConnectEvent event = new TunnelConnectEvent(
- session.getUserContext(),
- session.getCredentials(),
- tunnel);
-
- // Notify all listeners
- for (Object listener : session.getListeners()) {
- if (listener instanceof TunnelConnectListener) {
-
- // Cancel immediately if hook returns false
- if (!((TunnelConnectListener) listener).tunnelConnected(event))
- return false;
-
- }
- }
-
- return true;
-
- }
-
- /**
- * Notifies all listeners in the given session that a tunnel has been
- * closed.
- *
- * @param session The session associated with the listeners to be notified.
- * @param tunnel The tunnel being closed.
- * @return true if all listeners are allowing the tunnel to close,
- * or if there are no listeners, and false if any listener is
- * canceling the close. Note that once one listener cancels,
- * no other listeners will run.
- * @throws GuacamoleException If any listener throws an error while being
- * notified. Note that if any listener throws an
- * error, the close is canceled, and no other
- * listeners will run.
- */
- private boolean notifyClose(GuacamoleSession session, GuacamoleTunnel tunnel)
- throws GuacamoleException {
-
- // Build event for auth success
- TunnelCloseEvent event = new TunnelCloseEvent(
- session.getUserContext(),
- session.getCredentials(),
- tunnel);
-
- // Notify all listeners
- for (Object listener : session.getListeners()) {
- if (listener instanceof TunnelCloseListener) {
-
- // Cancel immediately if hook returns false
- if (!((TunnelCloseListener) listener).tunnelClosed(event))
- return false;
-
- }
- }
-
- return true;
-
- }
-
/**
* Reads and returns the client information provided within the given
* request.
@@ -335,10 +253,6 @@ public class TunnelRequestService {
@Override
public void close() throws GuacamoleException {
- // Signal listeners
- if (!notifyClose(session, this))
- throw new GuacamoleException("Tunnel close canceled by listener.");
-
session.removeTunnel(getUUID().toString());
// Close if no exception due to listener
@@ -348,12 +262,6 @@ public class TunnelRequestService {
};
- // Notify listeners about connection
- if (!notifyConnect(session, monitoredTunnel)) {
- logger.info("Successful connection canceled by hook.");
- return null;
- }
-
// Associate tunnel with session
session.addTunnel(monitoredTunnel);
return monitoredTunnel;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java
new file mode 100644
index 000000000..4a4a8f271
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java
@@ -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.glyptodon.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.glyptodon.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() {
+
+ @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 jarURLs = new ArrayList();
+ 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());
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java
new file mode 100644
index 000000000..2f3aa17c4
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java
@@ -0,0 +1,361 @@
+/*
+ * 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.glyptodon.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.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.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.GuacamoleServerException;
+import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
+import org.glyptodon.guacamole.net.basic.resource.ClassPathResource;
+import org.glyptodon.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;
+
+ /**
+ * The collection of all JavaScript resources defined within the extension.
+ */
+ private final Collection javaScriptResources;
+
+ /**
+ * The collection of all CSS resources defined within the extension.
+ */
+ private final Collection cssResources;
+
+ /**
+ * The collection of all AuthenticationProvider classes defined within the
+ * extension.
+ */
+ private final Collection> authenticationProviderClasses;
+
+ /**
+ * Returns a new collection of resources corresponding to the collection of
+ * paths provided. Each resource will be associated with the given
+ * mimetype.
+ *
+ * @param mimetype
+ * The mimetype to associate with each resource.
+ *
+ * @param paths
+ * The paths corresponding to the resources desired.
+ *
+ * @return
+ * A new, unmodifiable collection of resources corresponding to the
+ * collection of paths provided.
+ */
+ private Collection getClassPathResources(String mimetype, Collection paths) {
+
+ // If no paths are provided, just return an empty list
+ if (paths == null)
+ return Collections.emptyList();
+
+ // Add classpath resource for each path provided
+ Collection resources = new ArrayList(paths.size());
+ for (String path : paths)
+ resources.add(new ClassPathResource(classLoader, mimetype, path));
+
+ // Callers should not rely on modifying the result
+ return Collections.unmodifiableCollection(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 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) 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> getAuthenticationProviderClasses(Collection names)
+ throws GuacamoleException {
+
+ // If no classnames are provided, just return an empty list
+ if (names == null)
+ return Collections.>emptyList();
+
+ // Define all auth provider classes
+ Collection> classes = new ArrayList>(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);
+
+ }
+
+ // Always close zip file, if possible
+ finally {
+ extension.close();
+ }
+
+ try {
+
+ // Create isolated classloader for this extension
+ classLoader = AccessController.doPrivileged(new PrivilegedExceptionAction() {
+
+ @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());
+
+ // Define authentication providers
+ authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
+
+ }
+
+ /**
+ * 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 all declared JavaScript resources associated with this
+ * extension. JavaScript resources are declared within the extension
+ * manifest.
+ *
+ * @return
+ * All declared JavaScript resources associated with this extension.
+ */
+ public Collection getJavaScriptResources() {
+ return javaScriptResources;
+ }
+
+ /**
+ * Returns all declared CSS resources associated with this extension. CSS
+ * resources are declared within the extension manifest.
+ *
+ * @return
+ * All declared CSS resources associated with this extension.
+ */
+ public Collection getCSSResources() {
+ return cssResources;
+ }
+
+ /**
+ * 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> getAuthenticationProviderClasses() {
+ return authenticationProviderClasses;
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java
new file mode 100644
index 000000000..093af3b0d
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.glyptodon.guacamole.net.basic.extension;
+
+import java.util.Collection;
+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 javaScriptPaths;
+
+ /**
+ * The paths of all CSS resources within the .jar of the extension
+ * associated with this manifest.
+ */
+ private Collection cssPaths;
+
+ /**
+ * The names of all authentication provider classes within this extension,
+ * if any.
+ */
+ private Collection authProviders;
+
+ /**
+ * Returns the version of the Guacamole web application for which the
+ * extension was built, such as "0.9.6".
+ *
+ * @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.6".
+ *
+ * @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 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 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 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 cssPaths) {
+ this.cssPaths = cssPaths;
+ }
+
+ /**
+ * 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 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 authProviders) {
+ this.authProviders = authProviders;
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java
new file mode 100644
index 000000000..e2bae9bdf
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java
@@ -0,0 +1,300 @@
+/*
+ * 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.glyptodon.guacamole.net.basic.extension;
+
+import com.google.inject.Singleton;
+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 net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider;
+import org.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.GuacamoleServerException;
+import org.glyptodon.guacamole.environment.Environment;
+import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
+import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
+import org.glyptodon.guacamole.net.basic.resource.Resource;
+import org.glyptodon.guacamole.net.basic.resource.ResourceServlet;
+import org.glyptodon.guacamole.net.basic.resource.SequenceResource;
+import org.glyptodon.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 ALLOWED_GUACAMOLE_VERSIONS =
+ Collections.unmodifiableList(Arrays.asList(
+ "0.9.6"
+ ));
+
+ /**
+ * 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;
+
+ /**
+ * The currently-bound authentication provider, if any. At the moment, we
+ * only support one authentication provider loaded at any one time.
+ */
+ private Class extends AuthenticationProvider> boundAuthenticationProvider = null;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 getAuthProviderProperty() {
+
+ // Get and bind auth provider instance, if defined via property
+ try {
+
+ // Use "auth-provider" property if present, but warn about deprecation
+ Class 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.
+ *
+ * @param authenticationProvider
+ * The AuthenticationProvider class to bind.
+ */
+ private void bindAuthenticationProvider(Class extends AuthenticationProvider> authenticationProvider) {
+
+ // Choose auth provider for binding if not already chosen
+ if (boundAuthenticationProvider == null)
+ boundAuthenticationProvider = authenticationProvider;
+
+ // If an auth provider is already chosen, skip and warn
+ else {
+ logger.debug("Ignoring AuthenticationProvider \"{}\".", authenticationProvider);
+ logger.warn("Only one authentication extension may be used at a time. Please "
+ + "make sure that only one authentication extension is present "
+ + "within the GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " "
+ + "directory, and that you are not also specifying the deprecated "
+ + "\"auth-provider\" property within guacamole.properties.");
+ return;
+ }
+
+ // Bind authentication provider
+ logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider);
+ bind(AuthenticationProvider.class).to(authenticationProvider).in(Singleton.class);
+
+ }
+
+ /**
+ * 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);
+ }
+
+ @Override
+ protected void configureServlets() {
+
+ // Load authentication provider from guacamole.properties for sake of backwards compatibility
+ Class authProviderProperty = getAuthProviderProperty();
+ if (authProviderProperty != null)
+ bindAuthenticationProvider(authProviderProperty);
+
+ // 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);
+ }
+
+ });
+
+ // Init JavaScript resources with base guacamole.min.js
+ Collection javaScriptResources = new ArrayList();
+ javaScriptResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.js"));
+
+ // Init CSS resources with base guacamole.min.css
+ Collection cssResources = new ArrayList();
+ cssResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.css"));
+
+ // 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());
+ cssResources.addAll(extension.getCSSResources());
+
+ // Attempt to load all authentication providers
+ Collection> authenticationProviders = extension.getAuthenticationProviderClasses();
+ for (Class authenticationProvider : authenticationProviders)
+ bindAuthenticationProvider(authenticationProvider);
+
+ // 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);
+ }
+
+ }
+
+ // Default to basic auth if nothing else chosen/provided
+ if (boundAuthenticationProvider == null) {
+ logger.info("Using default, \"basic\", XML-driven authentication.");
+ 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)));
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java
new file mode 100644
index 000000000..198e4c4b7
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java
@@ -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.glyptodon.guacamole.net.basic.extension;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java
index dd633ed29..c2dd97d04 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Glyptodon LLC
+ * 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
@@ -22,22 +22,24 @@
package org.glyptodon.guacamole.net.basic.properties;
-import java.lang.reflect.InvocationTargetException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.properties.GuacamoleProperty;
/**
* A GuacamoleProperty whose value is the name of a class to use to
- * authenticate users. This class must implement AuthenticationProvider.
+ * 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
*/
-public abstract class AuthenticationProviderProperty implements GuacamoleProperty {
+@Deprecated
+public abstract class AuthenticationProviderProperty implements GuacamoleProperty> {
@Override
- public AuthenticationProvider parseValue(String authProviderClassName) throws GuacamoleException {
+ @SuppressWarnings("unchecked") // Explicitly checked within by isAssignableFrom()
+ public Class parseValue(String authProviderClassName) throws GuacamoleException {
// If no property provided, return null.
if (authProviderClassName == null)
@@ -46,35 +48,21 @@ public abstract class AuthenticationProviderProperty implements GuacamolePropert
// Get auth provider instance
try {
- Object obj = GuacamoleClassLoader.getInstance().loadClass(authProviderClassName)
- .getConstructor().newInstance();
+ // Get authentication provider class
+ Class> authProviderClass = org.glyptodon.guacamole.net.basic.GuacamoleClassLoader.getInstance().loadClass(authProviderClassName);
- if (!(obj instanceof AuthenticationProvider))
+ // 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 (AuthenticationProvider) obj;
+ // Return located class
+ return (Class) authProviderClass;
}
catch (ClassNotFoundException e) {
throw new GuacamoleException("Authentication provider class not found", e);
}
- catch (NoSuchMethodException e) {
- throw new GuacamoleException("Default constructor for authentication provider not present", e);
- }
- catch (SecurityException e) {
- throw new GuacamoleException("Creation of authentication provider disallowed; check your security settings", e);
- }
- catch (InstantiationException e) {
- throw new GuacamoleException("Unable to instantiate authentication provider", e);
- }
- catch (IllegalAccessException e) {
- throw new GuacamoleException("Unable to access default constructor of authentication provider", e);
- }
- catch (InvocationTargetException e) {
- throw new GuacamoleException("Internal error in constructor of authentication provider", e.getTargetException());
- }
}
}
-
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java
index f2de45e3b..a1b2e1336 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java
@@ -22,7 +22,6 @@
package org.glyptodon.guacamole.net.basic.properties;
-import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty;
import org.glyptodon.guacamole.properties.FileGuacamoleProperty;
import org.glyptodon.guacamole.properties.IntegerGuacamoleProperty;
@@ -40,8 +39,10 @@ public class BasicGuacamoleProperties {
/**
* The authentication provider to user when retrieving the authorized
- * configurations of a user.
+ * 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
@@ -50,8 +51,11 @@ public class BasicGuacamoleProperties {
};
/**
- * The directory to search for authentication provider classes.
+ * 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
@@ -59,16 +63,6 @@ public class BasicGuacamoleProperties {
};
- /**
- * The comma-separated list of all classes to use as event listeners.
- */
- public static final EventListenersProperty EVENT_LISTENERS = new EventListenersProperty() {
-
- @Override
- public String getName() { return "event-listeners"; }
-
- };
-
/**
* The session timeout for the API, in minutes.
*/
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java
deleted file mode 100644
index 5b7e9a4b5..000000000
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 Glyptodon LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package org.glyptodon.guacamole.net.basic.properties;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
-import org.glyptodon.guacamole.properties.GuacamoleProperty;
-
-/**
- * A GuacamoleProperty whose value is a comma-separated list of class names,
- * where each class will be used as a listener for events.
- *
- * @author Michael Jumper
- */
-public abstract class EventListenersProperty implements GuacamoleProperty> {
-
- @Override
- public Collection parseValue(String classNameList) throws GuacamoleException {
-
- // If no property provided, return null.
- if (classNameList == null)
- return null;
-
- // Parse list
- String[] classNames = classNameList.split(",[\\s]*");
-
- // Fill list of classes
- Collection listeners = new ArrayList();
- try {
-
- // Load all classes in list
- for (String className : classNames) {
- Class clazz = GuacamoleClassLoader.getInstance().loadClass(className);
- listeners.add(clazz);
- }
-
- }
- catch (ClassNotFoundException e) {
- throw new GuacamoleException("Listener class not found.", e);
- }
- catch (SecurityException e) {
- throw new GuacamoleException("Security settings prevent loading of listener class.", e);
- }
-
- return listeners;
-
- }
-
-}
-
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java
index d79746ad3..ddc5fff78 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java
@@ -23,10 +23,6 @@
package org.glyptodon.guacamole.net.basic.rest;
import com.google.inject.AbstractModule;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.environment.Environment;
-import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
-import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthTokenGenerator;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator;
@@ -47,11 +43,6 @@ public class RESTAuthModule extends AbstractModule {
*/
private final Logger logger = LoggerFactory.getLogger(RESTAuthModule.class);
- /**
- * The Guacamole server environment.
- */
- private final Environment environment;
-
/**
* Singleton instance of TokenSessionMap.
*/
@@ -61,16 +52,11 @@ public class RESTAuthModule extends AbstractModule {
* Creates a module which handles binding of authentication-related
* objects, including the singleton TokenSessionMap.
*
- * @param environment
- * The environment to use when configuring authentication.
- *
* @param tokenSessionMap
* An instance of TokenSessionMap to inject as a singleton wherever
* needed.
*/
- public RESTAuthModule(Environment environment,
- TokenSessionMap tokenSessionMap) {
- this.environment = environment;
+ public RESTAuthModule(TokenSessionMap tokenSessionMap) {
this.tokenSessionMap = tokenSessionMap;
}
@@ -84,17 +70,6 @@ public class RESTAuthModule extends AbstractModule {
bind(AuthenticationService.class);
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
- // Get and bind auth provider instance
- try {
- AuthenticationProvider authProvider = environment.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
- bind(AuthenticationProvider.class).toInstance(authProvider);
- }
- catch (GuacamoleException e) {
- logger.error("Unable to read authentication provider from guacamole.properties: {}", e.getMessage());
- logger.debug("Error reading authentication provider from guacamole.properties.", e);
- throw new RuntimeException(e);
- }
-
}
}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java
index 1a42bc2fb..69a62fabe 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java
@@ -28,8 +28,6 @@ import java.util.Arrays;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.net.basic.TunnelLoader;
import org.glyptodon.guacamole.net.basic.TunnelRequestService;
import org.slf4j.Logger;
@@ -53,7 +51,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
try {
// Attempt to find WebSocket servlet
- GuacamoleClassLoader.getInstance().findClass("javax.websocket.Endpoint");
+ Class.forName("javax.websocket.Endpoint");
// Support found
return true;
@@ -65,12 +63,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
- // Log all GuacamoleExceptions
- catch (GuacamoleException e) {
- logger.error("Unable to load/detect WebSocket support: {}", e.getMessage());
- logger.debug("Error loading/detecting WebSocket support.", e);
- }
-
// Support not found
return false;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java
index 8bf0a4a42..9e94da9b2 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java
@@ -23,8 +23,6 @@
package org.glyptodon.guacamole.net.basic.websocket.jetty8;
import com.google.inject.servlet.ServletModule;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.net.basic.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
try {
// Attempt to find WebSocket servlet
- GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet");
+ Class.forName("org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet");
// Support found
return true;
@@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
- // Log all GuacamoleExceptions
- catch (GuacamoleException e) {
- logger.error("Unable to load/detect WebSocket support: {}", e.getMessage());
- logger.debug("Error loading/detecting WebSocket support.", e);
- }
-
// Support not found
return false;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java
index 6a67eae49..de27e0213 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java
@@ -23,8 +23,6 @@
package org.glyptodon.guacamole.net.basic.websocket.jetty9;
import com.google.inject.servlet.ServletModule;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.net.basic.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
try {
// Attempt to find WebSocket servlet
- GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet");
+ Class.forName("org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet");
// Support found
return true;
@@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
- // Log all GuacamoleExceptions
- catch (GuacamoleException e) {
- logger.error("Unable to load/detect WebSocket support: {}", e.getMessage());
- logger.debug("Error loading/detecting WebSocket support.", e);
- }
-
// Support not found
return false;
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java
index 4c59b78c5..432818601 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java
@@ -23,8 +23,6 @@
package org.glyptodon.guacamole.net.basic.websocket.tomcat;
import com.google.inject.servlet.ServletModule;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader;
import org.glyptodon.guacamole.net.basic.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
try {
// Attempt to find WebSocket servlet
- GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet");
+ Class.forName("org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet");
// Support found
return true;
@@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
- // Log all GuacamoleExceptions
- catch (GuacamoleException e) {
- logger.error("Unable to load/detect WebSocket support: {}", e.getMessage());
- logger.debug("Error loading/detecting WebSocket support.", e);
- }
-
// Support not found
return false;
diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html
index eb6d28dad..3303ec1a8 100644
--- a/guacamole/src/main/webapp/index.html
+++ b/guacamole/src/main/webapp/index.html
@@ -8,7 +8,7 @@
-
+
-
+