GUAC-587: Load authentication providers from extensions.

This commit is contained in:
Michael Jumper
2015-05-10 22:57:58 -07:00
parent 8ae9a2e28c
commit 685694aff4
4 changed files with 194 additions and 38 deletions

View File

@@ -40,6 +40,7 @@ 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;
@@ -74,6 +75,125 @@ public class Extension {
*/
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.
@@ -160,6 +280,13 @@ public class Extension {
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());
}
/**
@@ -184,37 +311,6 @@ public class Extension {
return manifest.getNamespace();
}
/**
* 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);
}
/**
* Returns all declared JavaScript resources associated with this
* extension. JavaScript resources are declared within the extension
@@ -224,7 +320,7 @@ public class Extension {
* All declared JavaScript resources associated with this extension.
*/
public Collection<Resource> getJavaScriptResources() {
return getClassPathResources("text/javascript", manifest.getJavaScriptPaths());
return javaScriptResources;
}
/**
@@ -235,7 +331,19 @@ public class Extension {
* All declared CSS resources associated with this extension.
*/
public Collection<Resource> getCSSResources() {
return getClassPathResources("text/css", manifest.getCSSPaths());
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;
}
}

View File

@@ -59,6 +59,12 @@ public class ExtensionManifest {
*/
private Collection<String> cssPaths;
/**
* The names of all authentication provider classes within this extension,
* if any.
*/
private Collection<String> authProviders;
/**
* Returns the name of the extension associated with this manifest. The
* name is human-readable, for display purposes only, and is defined within
@@ -169,4 +175,32 @@ public class ExtensionManifest {
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;
}
}

View File

@@ -22,6 +22,7 @@
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;
@@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
import org.glyptodon.guacamole.net.basic.resource.Resource;
import org.glyptodon.guacamole.net.basic.resource.ResourceServlet;
import org.glyptodon.guacamole.net.basic.resource.SequenceResource;
@@ -118,6 +120,13 @@ public class ExtensionModule extends ServletModule {
javaScriptResources.addAll(extension.getJavaScriptResources());
cssResources.addAll(extension.getCSSResources());
// Load all authentication providers as singletons
Collection<Class<AuthenticationProvider>> authenticationProviders = extension.getAuthenticationProviderClasses();
for (Class<AuthenticationProvider> authenticationProvider : authenticationProviders) {
logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider);
bind(AuthenticationProvider.class).to(authenticationProvider).in(Singleton.class);
}
// Log successful loading of extension by name
logger.info("Extension \"{}\" loaded.", extension.getName());

View File

@@ -84,15 +84,20 @@ public class RESTAuthModule extends AbstractModule {
bind(AuthenticationService.class);
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
// Get and bind auth provider instance
// Get and bind auth provider instance, if defined via property
try {
AuthenticationProvider authProvider = environment.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
bind(AuthenticationProvider.class).toInstance(authProvider);
// Use "auth-provider" property if present, but warn about deprecation
AuthenticationProvider authProvider = environment.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
if (authProvider != null) {
logger.warn("The \"auth-provider\" is now deprecated. Please use the \"extensions\" directory within GUACAMOLE_HOME instead.");
bind(AuthenticationProvider.class).toInstance(authProvider);
}
}
catch (GuacamoleException e) {
logger.error("Unable to read authentication provider from guacamole.properties: {}", e.getMessage());
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);
throw new RuntimeException(e);
}
}