mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Merge pull request #178 from glyptodon/extension-format
GUAC-587: Adopt extension format including manifest.
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -51,6 +51,7 @@
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "0.9.6",
|
||||
|
||||
"name" : "MySQL Authentication",
|
||||
"namespace" : "guac-mysql",
|
||||
|
||||
"authProviders" : [
|
||||
"net.sourceforge.guacamole.net.auth.mysql.MySQLAuthenticationProvider"
|
||||
]
|
||||
|
||||
}
|
||||
|
@@ -51,6 +51,7 @@
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "0.9.6",
|
||||
|
||||
"name" : "PostgreSQL Authentication",
|
||||
"namespace" : "guac-postgresql",
|
||||
|
||||
"authProviders" : [
|
||||
"org.glyptodon.guacamole.auth.postgresql.PostgreSQLAuthenticationProvider"
|
||||
]
|
||||
|
||||
}
|
||||
|
@@ -32,6 +32,26 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Copy dependencies prior to packaging -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.10</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-dependencies</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Assembly plugin - for easy distribution -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
@@ -64,6 +84,7 @@
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<artifactId>guacamole-common</artifactId>
|
||||
<version>0.9.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Guacamole Extension API -->
|
||||
@@ -71,6 +92,7 @@
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<artifactId>guacamole-ext</artifactId>
|
||||
<version>0.9.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JLDAP -->
|
||||
|
@@ -11,43 +11,29 @@
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
<!-- Include docs and schema -->
|
||||
<!-- Include docs, schema, and extension .jar -->
|
||||
<fileSets>
|
||||
|
||||
<!-- Include docs -->
|
||||
<fileSet>
|
||||
<directory>doc</directory>
|
||||
</fileSet>
|
||||
<!-- Include docs -->
|
||||
<fileSet>
|
||||
<directory>doc</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include schema -->
|
||||
<fileSet>
|
||||
<outputDirectory>schema</outputDirectory>
|
||||
<directory>schema</directory>
|
||||
</fileSet>
|
||||
<!-- Include schema -->
|
||||
<fileSet>
|
||||
<outputDirectory>schema</outputDirectory>
|
||||
<directory>schema</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include extension .jar -->
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
</fileSets>
|
||||
|
||||
<!-- Include self and all dependencies except guacamole-common
|
||||
and guacamole-ext -->
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<scope>runtime</scope>
|
||||
<unpack>false</unpack>
|
||||
<useProjectArtifact>true</useProjectArtifact>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
|
||||
<excludes>
|
||||
|
||||
<!-- Do not include guacamole-common -->
|
||||
<exclude>org.glyptodon.guacamole:guacamole-common</exclude>
|
||||
|
||||
<!-- Do not include guacamole-ext -->
|
||||
<exclude>org.glyptodon.guacamole:guacamole-ext</exclude>
|
||||
|
||||
</excludes>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
|
||||
</assembly>
|
||||
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "0.9.6",
|
||||
|
||||
"name" : "LDAP Authentication",
|
||||
"namespace" : "guac-ldap",
|
||||
|
||||
"authProviders" : [
|
||||
"net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider"
|
||||
]
|
||||
|
||||
}
|
@@ -32,6 +32,26 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Copy dependencies prior to packaging -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.10</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-dependencies</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Assembly plugin - for easy distribution -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
@@ -64,6 +84,7 @@
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<artifactId>guacamole-common</artifactId>
|
||||
<version>0.9.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Guacamole Extension API -->
|
||||
@@ -71,6 +92,7 @@
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<artifactId>guacamole-ext</artifactId>
|
||||
<version>0.9.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
<!-- Include docs and schema -->
|
||||
<!-- Include docs and extension .jar -->
|
||||
<fileSets>
|
||||
|
||||
<!-- Include docs -->
|
||||
@@ -19,29 +19,15 @@
|
||||
<directory>doc</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include extension .jar -->
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
</fileSets>
|
||||
|
||||
<!-- Include self and all dependencies except guacamole-common
|
||||
and guacamole-ext -->
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<scope>runtime</scope>
|
||||
<unpack>false</unpack>
|
||||
<useProjectArtifact>true</useProjectArtifact>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
|
||||
<excludes>
|
||||
|
||||
<!-- Do not include guacamole-common -->
|
||||
<exclude>org.glyptodon.guacamole:guacamole-common</exclude>
|
||||
|
||||
<!-- Do not include guacamole-ext -->
|
||||
<exclude>org.glyptodon.guacamole:guacamole-ext</exclude>
|
||||
|
||||
</excludes>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
|
||||
</assembly>
|
||||
|
@@ -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 {
|
||||
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "0.9.6",
|
||||
|
||||
"name" : "Disabled Authentication",
|
||||
"namespace" : "guac-noauth",
|
||||
|
||||
"authProviders" : [
|
||||
"net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider"
|
||||
]
|
||||
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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 ||
|
||||
|
@@ -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()
|
||||
);
|
||||
|
@@ -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 {
|
||||
|
||||
/**
|
||||
|
@@ -60,11 +60,6 @@ public class GuacamoleSession {
|
||||
*/
|
||||
private UserContext userContext;
|
||||
|
||||
/**
|
||||
* Collection of all event listeners configured in guacamole.properties.
|
||||
*/
|
||||
private final Collection<Object> listeners = new ArrayList<Object>();
|
||||
|
||||
/**
|
||||
* 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<Class> 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<Object> getListeners() {
|
||||
return Collections.unmodifiableCollection(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this session has any associated active tunnels.
|
||||
*
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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<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,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<Resource> javaScriptResources;
|
||||
|
||||
/**
|
||||
* The collection of all CSS resources defined within the extension.
|
||||
*/
|
||||
private final Collection<Resource> cssResources;
|
||||
|
||||
/**
|
||||
* The collection of all AuthenticationProvider classes defined within the
|
||||
* extension.
|
||||
*/
|
||||
private final Collection<Class<AuthenticationProvider>> 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<Resource> getClassPathResources(String mimetype, Collection<String> paths) {
|
||||
|
||||
// If no paths are provided, just return an empty list
|
||||
if (paths == null)
|
||||
return Collections.<Resource>emptyList();
|
||||
|
||||
// Add classpath resource for each path provided
|
||||
Collection<Resource> resources = new ArrayList<Resource>(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<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);
|
||||
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
// 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<Resource> 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<Resource> 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<Class<AuthenticationProvider>> getAuthenticationProviderClasses() {
|
||||
return authenticationProviderClasses;
|
||||
}
|
||||
|
||||
}
|
@@ -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<String> javaScriptPaths;
|
||||
|
||||
/**
|
||||
* The paths of all CSS resources within the .jar of the extension
|
||||
* associated with this manifest.
|
||||
*/
|
||||
private Collection<String> cssPaths;
|
||||
|
||||
/**
|
||||
* The names of all authentication provider classes within this extension,
|
||||
* if any.
|
||||
*/
|
||||
private Collection<String> 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<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 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<String> 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<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.
|
||||
*
|
||||
* @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<AuthenticationProvider> 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<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 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<Class<AuthenticationProvider>> authenticationProviders = extension.getAuthenticationProviderClasses();
|
||||
for (Class<AuthenticationProvider> 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)));
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
@@ -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<AuthenticationProvider> {
|
||||
@Deprecated
|
||||
public abstract class AuthenticationProviderProperty implements GuacamoleProperty<Class<AuthenticationProvider>> {
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider parseValue(String authProviderClassName) throws GuacamoleException {
|
||||
@SuppressWarnings("unchecked") // Explicitly checked within by isAssignableFrom()
|
||||
public Class<AuthenticationProvider> 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<AuthenticationProvider>) 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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<Collection<Class>> {
|
||||
|
||||
@Override
|
||||
public Collection<Class> 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<Class> listeners = new ArrayList<Class>();
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
|
||||
<link rel="icon" type="image/png" sizes="144x144" href="images/guacamole-logo-144.png"/>
|
||||
<link rel="apple-touch-icon" type="image/png" href="images/guacamole-logo-144.png"/>
|
||||
<link rel="stylesheet" type="text/css" href="guacamole.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="app.css">
|
||||
<title ng-bind="page.title | translate"></title>
|
||||
</head>
|
||||
<!--
|
||||
@@ -52,6 +52,6 @@
|
||||
<!-- Login screen for logged-out users -->
|
||||
<guac-login ng-show="expectedCredentials" form="expectedCredentials"></guac-login>
|
||||
|
||||
<script type="text/javascript" src="guacamole.min.js"></script>
|
||||
<script type="text/javascript" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user