You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/03/29 06:20:23 UTC

[14/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/Authorization.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/Authorization.java b/guacamole/src/main/java/org/apache/guacamole/auth/Authorization.java
new file mode 100644
index 0000000..9ff1c79
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/Authorization.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+
+/**
+ * Mapping of username/password pair to configuration set. In addition to basic
+ * storage of the username, password, and configurations, this class also
+ * provides password validation functions.
+ *
+ * @author Mike Jumper
+ */
+public class Authorization {
+
+    /**
+     * All supported password encodings.
+     */
+    public static enum Encoding {
+
+        /**
+         * Plain-text password (not hashed at all).
+         */
+        PLAIN_TEXT,
+
+        /**
+         * Password hashed with MD5.
+         */
+        MD5
+
+    }
+
+    /**
+     * The username being authorized.
+     */
+    private String username;
+
+    /**
+     * The password corresponding to the username being authorized, which may
+     * be hashed.
+     */
+    private String password;
+
+    /**
+     * The encoding used when the password was hashed.
+     */
+    private Encoding encoding = Encoding.PLAIN_TEXT;
+
+    /**
+     * Map of all authorized configurations, indexed by configuration name.
+     */
+    private Map<String, GuacamoleConfiguration> configs = new
+            TreeMap<String, GuacamoleConfiguration>();
+
+    /**
+     * Lookup table of hex bytes characters by value.
+     */
+    private static final char HEX_CHARS[] = {
+        '0', '1', '2', '3', '4', '5', '6', '7',
+        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /**
+     * Produces a String containing the bytes provided in hexadecimal notation.
+     *
+     * @param bytes The bytes to convert into hex.
+     * @return A String containing the hex representation of the given bytes.
+     */
+    private static String getHexString(byte[] bytes) {
+
+        // If null byte array given, return null
+        if (bytes == null)
+            return null;
+
+        // Create string builder for holding the hex representation,
+        // pre-calculating the exact length
+        StringBuilder hex = new StringBuilder(2 * bytes.length);
+
+        // Convert each byte into a pair of hex digits
+        for (byte b : bytes) {
+            hex.append(HEX_CHARS[(b & 0xF0) >> 4])
+               .append(HEX_CHARS[ b & 0x0F      ]);
+        }
+
+        // Return the string produced
+        return hex.toString();
+
+    }
+
+    /**
+     * Returns the username associated with this authorization.
+     *
+     * @return The username associated with this authorization.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Sets the username associated with this authorization.
+     *
+     * @param username The username to associate with this authorization.
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     * Returns the password associated with this authorization, which may be
+     * encoded or hashed.
+     *
+     * @return The password associated with this authorization.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the password associated with this authorization, which must be
+     * encoded using the encoding specified with setEncoding(). By default,
+     * passwords are plain text.
+     *
+     * @param password Sets the password associated with this authorization.
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * Returns the encoding used to hash the password, if any.
+     *
+     * @return The encoding used to hash the password.
+     */
+    public Encoding getEncoding() {
+        return encoding;
+    }
+
+    /**
+     * Sets the encoding which will be used to hash the password or when
+     * comparing a given password for validation.
+     *
+     * @param encoding The encoding to use for password hashing.
+     */
+    public void setEncoding(Encoding encoding) {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Returns whether a given username/password pair is authorized based on
+     * the stored username and password. The password given must be plain text.
+     * It will be hashed as necessary to perform the validation.
+     *
+     * @param username The username to validate.
+     * @param password The password to validate.
+     * @return true if the username/password pair given is authorized, false
+     *         otherwise.
+     */
+    public boolean validate(String username, String password) {
+
+        // If username matches
+        if (username != null && password != null
+                && username.equals(this.username)) {
+
+            switch (encoding) {
+
+                // If plain text, just compare
+                case PLAIN_TEXT:
+
+                    // Compare plaintext
+                    return password.equals(this.password);
+
+                // If hased with MD5, hash password and compare
+                case MD5:
+
+                    // Compare hashed password
+                    try {
+                        MessageDigest digest = MessageDigest.getInstance("MD5");
+                        String hashedPassword = getHexString(digest.digest(password.getBytes("UTF-8")));
+                        return hashedPassword.equals(this.password.toUpperCase());
+                    }
+                    catch (UnsupportedEncodingException e) {
+                        throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
+                    }
+                    catch (NoSuchAlgorithmException e) {
+                        throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e);
+                    }
+
+            }
+
+        } // end validation check
+
+        return false;
+
+    }
+
+    /**
+     * Returns the GuacamoleConfiguration having the given name and associated
+     * with the username/password pair stored within this authorization.
+     *
+     * @param name The name of the GuacamoleConfiguration to return.
+     * @return The GuacamoleConfiguration having the given name, or null if no
+     *         such GuacamoleConfiguration exists.
+     */
+    public GuacamoleConfiguration getConfiguration(String name) {
+        return configs.get(name);
+    }
+
+    /**
+     * Adds the given GuacamoleConfiguration to the set of stored configurations
+     * under the given name.
+     *
+     * @param name The name to associate this GuacamoleConfiguration with.
+     * @param config The GuacamoleConfiguration to store.
+     */
+    public void addConfiguration(String name, GuacamoleConfiguration config) {
+        configs.put(name, config);
+    }
+
+    /**
+     * Returns a Map of all stored GuacamoleConfigurations associated with the
+     * username/password pair stored within this authorization, indexed by
+     * configuration name.
+     *
+     * @return A Map of all stored GuacamoleConfigurations.
+     */
+    public Map<String, GuacamoleConfiguration> getConfigurations() {
+        return configs;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/UserMapping.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/UserMapping.java b/guacamole/src/main/java/org/apache/guacamole/auth/UserMapping.java
new file mode 100644
index 0000000..7f86b1e
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/UserMapping.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Mapping of all usernames to corresponding authorizations.
+ *
+ * @author Mike Jumper
+ */
+public class UserMapping {
+
+    /**
+     * All authorizations, indexed by username.
+     */
+    private Map<String, Authorization> authorizations =
+            new HashMap<String, Authorization>();
+
+    /**
+     * Adds the given authorization to the user mapping.
+     *
+     * @param authorization The authorization to add to the user mapping.
+     */
+    public void addAuthorization(Authorization authorization) {
+        authorizations.put(authorization.getUsername(), authorization);
+    }
+
+    /**
+     * Returns the authorization corresponding to the user having the given
+     * username, if any.
+     *
+     * @param username The username to find the authorization for.
+     * @return The authorization corresponding to the user having the given
+     *         username, or null if no such authorization exists.
+     */
+    public Authorization getAuthorization(String username) {
+        return authorizations.get(username);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/AuthorizeTagHandler.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/AuthorizeTagHandler.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/AuthorizeTagHandler.java
new file mode 100644
index 0000000..bdd9c9a
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/AuthorizeTagHandler.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import org.apache.guacamole.auth.Authorization;
+import org.apache.guacamole.auth.UserMapping;
+import org.apache.guacamole.xml.TagHandler;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * TagHandler for the "authorize" element.
+ *
+ * @author Mike Jumper
+ */
+public class AuthorizeTagHandler implements TagHandler {
+
+    /**
+     * The Authorization corresponding to the "authorize" tag being handled
+     * by this tag handler. The data of this Authorization will be populated
+     * as the tag is parsed.
+     */
+    private Authorization authorization = new Authorization();
+
+    /**
+     * The default GuacamoleConfiguration to use if "param" or "protocol"
+     * tags occur outside a "connection" tag.
+     */
+    private GuacamoleConfiguration default_config = null;
+
+    /**
+     * The UserMapping this authorization belongs to.
+     */
+    private UserMapping parent;
+
+    /**
+     * Creates a new AuthorizeTagHandler that parses an Authorization owned
+     * by the given UserMapping.
+     *
+     * @param parent The UserMapping that owns the Authorization this handler
+     *               will parse.
+     */
+    public AuthorizeTagHandler(UserMapping parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public void init(Attributes attributes) throws SAXException {
+
+        // Init username and password
+        authorization.setUsername(attributes.getValue("username"));
+        authorization.setPassword(attributes.getValue("password"));
+
+        // Get encoding
+        String encoding = attributes.getValue("encoding");
+        if (encoding != null) {
+
+            // If "md5", use MD5 encoding
+            if (encoding.equals("md5"))
+                authorization.setEncoding(Authorization.Encoding.MD5);
+
+            // If "plain", use plain text
+            else if (encoding.equals("plain"))
+                authorization.setEncoding(Authorization.Encoding.PLAIN_TEXT);
+
+            // Otherwise, bad encoding
+            else
+                throw new SAXException(
+                        "Invalid encoding: '" + encoding + "'");
+
+        }
+
+        parent.addAuthorization(this.asAuthorization());
+
+    }
+
+    @Override
+    public TagHandler childElement(String localName) throws SAXException {
+
+        // "connection" tag
+        if (localName.equals("connection"))
+            return new ConnectionTagHandler(authorization);
+
+        // "param" tag
+        if (localName.equals("param")) {
+
+            // Create default config if it doesn't exist
+            if (default_config == null) {
+                default_config = new GuacamoleConfiguration();
+                authorization.addConfiguration("DEFAULT", default_config);
+            }
+
+            return new ParamTagHandler(default_config);
+        }
+
+        // "protocol" tag
+        if (localName.equals("protocol")) {
+
+            // Create default config if it doesn't exist
+            if (default_config == null) {
+                default_config = new GuacamoleConfiguration();
+                authorization.addConfiguration("DEFAULT", default_config);
+            }
+
+            return new ProtocolTagHandler(default_config);
+        }
+
+        return null;
+
+    }
+
+    @Override
+    public void complete(String textContent) throws SAXException {
+        // Do nothing
+    }
+
+    /**
+     * Returns an Authorization backed by the data of this authorize tag
+     * handler. This Authorization is guaranteed to at least have the username,
+     * password, and encoding available. Any associated configurations will be
+     * added dynamically as the authorize tag is parsed.
+     *
+     * @return An Authorization backed by the data of this authorize tag
+     *         handler.
+     */
+    public Authorization asAuthorization() {
+        return authorization;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/BasicFileAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/BasicFileAuthenticationProvider.java
new file mode 100644
index 0000000..be3fd1a
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/BasicFileAuthenticationProvider.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.environment.LocalEnvironment;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider;
+import org.apache.guacamole.auth.Authorization;
+import org.apache.guacamole.auth.UserMapping;
+import org.apache.guacamole.xml.DocumentHandler;
+import org.apache.guacamole.properties.FileGuacamoleProperty;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Authenticates users against a static list of username/password pairs.
+ * Each username/password may be associated with multiple configurations.
+ * This list is stored in an XML file which is reread if modified.
+ *
+ * @author Michael Jumper, Michal Kotas
+ */
+public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvider {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(BasicFileAuthenticationProvider.class);
+
+    /**
+     * The time the user mapping file was last modified. If the file has never
+     * been read, and thus no modification time exists, this will be
+     * Long.MIN_VALUE.
+     */
+    private long lastModified = Long.MIN_VALUE;
+
+    /**
+     * The parsed UserMapping read when the user mapping file was last parsed.
+     */
+    private UserMapping cachedUserMapping;
+
+    /**
+     * Guacamole server environment.
+     */
+    private final Environment environment;
+
+    /**
+     * The XML file to read the user mapping from.
+     */
+    public static final FileGuacamoleProperty BASIC_USER_MAPPING = new FileGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "basic-user-mapping"; }
+
+    };
+
+    /**
+     * 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.
+     *
+     * @throws GuacamoleException
+     *     If a required property is missing, or an error occurs while parsing
+     *     a property.
+     */
+    public BasicFileAuthenticationProvider() throws GuacamoleException {
+        environment = new LocalEnvironment();
+    }
+
+    @Override
+    public String getIdentifier() {
+        return "default";
+    }
+
+    /**
+     * Returns a UserMapping containing all authorization data given within
+     * the XML file specified by the "basic-user-mapping" property in
+     * guacamole.properties. If the XML file has been modified or has not yet
+     * been read, this function may reread the file.
+     *
+     * @return
+     *     A UserMapping containing all authorization data within the user
+     *     mapping XML file, or null if the file cannot be found/parsed.
+     */
+    private UserMapping getUserMapping() {
+
+        // Get user mapping file, defaulting to GUACAMOLE_HOME/user-mapping.xml
+        File userMappingFile;
+        try {
+            userMappingFile = environment.getProperty(BASIC_USER_MAPPING);
+            if (userMappingFile == null)
+                userMappingFile = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING);
+        }
+
+        // Abort if property cannot be parsed
+        catch (GuacamoleException e) {
+            logger.warn("Unable to read user mapping filename from properties: {}", e.getMessage());
+            logger.debug("Error parsing user mapping property.", e);
+            return null;
+        }
+
+        // Abort if user mapping does not exist
+        if (!userMappingFile.exists()) {
+            logger.debug("User mapping file \"{}\" does not exist and will not be read.", userMappingFile);
+            return null;
+        }
+
+        // Refresh user mapping if file has changed
+        if (lastModified < userMappingFile.lastModified()) {
+
+            logger.debug("Reading user mapping file: \"{}\"", userMappingFile);
+
+            // Parse document
+            try {
+
+                // Get handler for root element
+                UserMappingTagHandler userMappingHandler =
+                        new UserMappingTagHandler();
+
+                // Set up document handler
+                DocumentHandler contentHandler = new DocumentHandler(
+                        "user-mapping", userMappingHandler);
+
+                // Set up XML parser
+                XMLReader parser = XMLReaderFactory.createXMLReader();
+                parser.setContentHandler(contentHandler);
+
+                // Read and parse file
+                InputStream input = new BufferedInputStream(new FileInputStream(userMappingFile));
+                parser.parse(new InputSource(input));
+                input.close();
+
+                // Store mod time and user mapping
+                lastModified = userMappingFile.lastModified();
+                cachedUserMapping = userMappingHandler.asUserMapping();
+
+            }
+
+            // If the file is unreadable, return no mapping
+            catch (IOException e) {
+                logger.warn("Unable to read user mapping file \"{}\": {}", userMappingFile, e.getMessage());
+                logger.debug("Error reading user mapping file.", e);
+                return null;
+            }
+
+            // If the file cannot be parsed, return no mapping
+            catch (SAXException e) {
+                logger.warn("User mapping file \"{}\" is not valid: {}", userMappingFile, e.getMessage());
+                logger.debug("Error parsing user mapping file.", e);
+                return null;
+            }
+
+        }
+
+        // Return (possibly cached) user mapping
+        return cachedUserMapping;
+
+    }
+
+    @Override
+    public Map<String, GuacamoleConfiguration>
+            getAuthorizedConfigurations(Credentials credentials)
+            throws GuacamoleException {
+
+        // Abort authorization if no user mapping exists
+        UserMapping userMapping = getUserMapping();
+        if (userMapping == null)
+            return null;
+
+        // Validate and return info for given user and pass
+        Authorization auth = userMapping.getAuthorization(credentials.getUsername());
+        if (auth != null && auth.validate(credentials.getUsername(), credentials.getPassword()))
+            return auth.getConfigurations();
+
+        // Unauthorized
+        return null;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/ConnectionTagHandler.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/ConnectionTagHandler.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ConnectionTagHandler.java
new file mode 100644
index 0000000..f9acabb
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ConnectionTagHandler.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import org.apache.guacamole.auth.Authorization;
+import org.apache.guacamole.xml.TagHandler;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * TagHandler for the "connection" element.
+ *
+ * @author Mike Jumper
+ */
+public class ConnectionTagHandler implements TagHandler {
+
+    /**
+     * The GuacamoleConfiguration backing this tag handler.
+     */
+    private GuacamoleConfiguration config = new GuacamoleConfiguration();
+
+    /**
+     * The name associated with the connection being parsed.
+     */
+    private String name;
+
+    /**
+     * The Authorization this connection belongs to.
+     */
+    private Authorization parent;
+
+    /**
+     * Creates a new ConnectionTagHandler that parses a Connection owned by
+     * the given Authorization.
+     *
+     * @param parent The Authorization that will own this Connection once
+     *               parsed.
+     */
+    public ConnectionTagHandler(Authorization parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public void init(Attributes attributes) throws SAXException {
+        name = attributes.getValue("name");
+        parent.addConfiguration(name, this.asGuacamoleConfiguration());
+    }
+
+    @Override
+    public TagHandler childElement(String localName) throws SAXException {
+
+        if (localName.equals("param"))
+            return new ParamTagHandler(config);
+
+        if (localName.equals("protocol"))
+            return new ProtocolTagHandler(config);
+
+        return null;
+
+    }
+
+    @Override
+    public void complete(String textContent) throws SAXException {
+        // Do nothing
+    }
+
+    /**
+     * Returns a GuacamoleConfiguration whose contents are populated from data
+     * within this connection element and child elements. This
+     * GuacamoleConfiguration will continue to be modified as the user mapping
+     * is parsed.
+     *
+     * @return A GuacamoleConfiguration whose contents are populated from data
+     *         within this connection element.
+     */
+    public GuacamoleConfiguration asGuacamoleConfiguration() {
+        return config;
+    }
+
+    /**
+     * Returns the name associated with this connection.
+     *
+     * @return The name associated with this connection.
+     */
+    public String getName() {
+        return name;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/ParamTagHandler.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/ParamTagHandler.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ParamTagHandler.java
new file mode 100644
index 0000000..5d3d804
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ParamTagHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import org.apache.guacamole.xml.TagHandler;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * TagHandler for the "param" element.
+ *
+ * @author Mike Jumper
+ */
+public class ParamTagHandler implements TagHandler {
+
+    /**
+     * The GuacamoleConfiguration which will be populated with data from
+     * the tag handled by this tag handler.
+     */
+    private GuacamoleConfiguration config;
+
+    /**
+     * The name of the parameter.
+     */
+    private String name;
+
+    /**
+     * Creates a new handler for an "param" tag having the given
+     * attributes.
+     *
+     * @param config The GuacamoleConfiguration to update with the data parsed
+     *               from the "protocol" tag.
+     */
+    public ParamTagHandler(GuacamoleConfiguration config) {
+        this.config = config;
+    }
+
+    @Override
+    public void init(Attributes attributes) throws SAXException {
+        this.name = attributes.getValue("name");
+    }
+
+    @Override
+    public TagHandler childElement(String localName) throws SAXException {
+        throw new SAXException("The 'param' tag can contain no elements.");
+    }
+
+    @Override
+    public void complete(String textContent) throws SAXException {
+        config.setParameter(name, textContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/ProtocolTagHandler.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/ProtocolTagHandler.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ProtocolTagHandler.java
new file mode 100644
index 0000000..3fc5f12
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/ProtocolTagHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import org.apache.guacamole.xml.TagHandler;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * TagHandler for the "protocol" element.
+ *
+ * @author Mike Jumper
+ */
+public class ProtocolTagHandler implements TagHandler {
+
+    /**
+     * The GuacamoleConfiguration which will be populated with data from
+     * the tag handled by this tag handler.
+     */
+    private GuacamoleConfiguration config;
+
+    /**
+     * Creates a new handler for a "protocol" tag having the given
+     * attributes.
+     *
+     * @param config The GuacamoleConfiguration to update with the data parsed
+     *               from the "protocol" tag.
+     * @throws SAXException If the attributes given are not valid.
+     */
+    public ProtocolTagHandler(GuacamoleConfiguration config) throws SAXException {
+        this.config = config;
+    }
+
+    @Override
+    public void init(Attributes attributes) throws SAXException {
+        // Do nothing
+    }
+
+    @Override
+    public TagHandler childElement(String localName) throws SAXException {
+        throw new SAXException("The 'protocol' tag can contain no elements.");
+    }
+
+    @Override
+    public void complete(String textContent) throws SAXException {
+        config.setProtocol(textContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/UserMappingTagHandler.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/UserMappingTagHandler.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/UserMappingTagHandler.java
new file mode 100644
index 0000000..f0bb6fa
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/UserMappingTagHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.auth.basic;
+
+import org.apache.guacamole.auth.UserMapping;
+import org.apache.guacamole.xml.TagHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * TagHandler for the "user-mapping" element.
+ *
+ * @author Mike Jumper
+ */
+public class UserMappingTagHandler implements TagHandler {
+
+    /**
+     * The UserMapping which will contain all data parsed by this tag handler.
+     */
+    private UserMapping user_mapping = new UserMapping();
+
+    @Override
+    public void init(Attributes attributes) throws SAXException {
+        // Do nothing
+    }
+
+    @Override
+    public TagHandler childElement(String localName) throws SAXException {
+
+        // Start parsing of authorize tags, add to list of all authorizations
+        if (localName.equals("authorize"))
+            return new AuthorizeTagHandler(user_mapping);
+
+        return null;
+
+    }
+
+    @Override
+    public void complete(String textContent) throws SAXException {
+        // Do nothing
+    }
+
+    /**
+     * Returns a user mapping containing all authorizations and configurations
+     * parsed so far. This user mapping will be backed by the data being parsed,
+     * thus any additional authorizations or configurations will be available
+     * in the object returned by this function even after this function has
+     * returned, once the data corresponding to those authorizations or
+     * configurations has been parsed.
+     *
+     * @return A user mapping containing all authorizations and configurations
+     *         parsed so far.
+     */
+    public UserMapping asUserMapping() {
+        return user_mapping;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/basic/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/basic/package-info.java b/guacamole/src/main/java/org/apache/guacamole/auth/basic/package-info.java
new file mode 100644
index 0000000..d5f8fb7
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/basic/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Classes related to parsing the user-mapping.xml file.
+ */
+package org.apache.guacamole.auth.basic;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/auth/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/package-info.java b/guacamole/src/main/java/org/apache/guacamole/auth/package-info.java
new file mode 100644
index 0000000..d1ac69e
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/auth/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Classes which drive the default, basic authentication of the Guacamole
+ * web application.
+ */
+package org.apache.guacamole.auth;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
new file mode 100644
index 0000000..b244255
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.extension;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.UUID;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides a safe wrapper around an AuthenticationProvider subclass, such that
+ * authentication attempts can cleanly fail, and errors can be properly logged,
+ * even if the AuthenticationProvider cannot be instantiated.
+ *
+ * @author Michael Jumper
+ */
+public class AuthenticationProviderFacade implements AuthenticationProvider {
+
+    /**
+     * Logger for this class.
+     */
+    private Logger logger = LoggerFactory.getLogger(AuthenticationProviderFacade.class);
+
+    /**
+     * The underlying authentication provider, or null if the authentication
+     * provider could not be instantiated.
+     */
+    private final AuthenticationProvider authProvider;
+
+    /**
+     * The identifier to provide for the underlying authentication provider if
+     * the authentication provider could not be loaded.
+     */
+    private final String facadeIdentifier = UUID.randomUUID().toString();
+
+    /**
+     * Creates a new AuthenticationProviderFacade which delegates all function
+     * calls to an instance of the given AuthenticationProvider subclass. If
+     * an instance of the given class cannot be created, creation of this
+     * facade will still succeed, but its use will result in errors being
+     * logged, and all authentication attempts will fail.
+     *
+     * @param authProviderClass
+     *     The AuthenticationProvider subclass to instantiate.
+     */
+    public AuthenticationProviderFacade(Class<? extends AuthenticationProvider> authProviderClass) {
+
+        AuthenticationProvider instance = null;
+        
+        try {
+            // Attempt to instantiate the authentication provider
+            instance = authProviderClass.getConstructor().newInstance();
+        }
+        catch (NoSuchMethodException e) {
+            logger.error("The authentication extension in use is not properly defined. "
+                       + "Please contact the developers of the extension or, if you "
+                       + "are the developer, turn on debug-level logging.");
+            logger.debug("AuthenticationProvider is missing a default constructor.", e);
+        }
+        catch (SecurityException e) {
+            logger.error("The Java security mananager is preventing authentication extensions "
+                       + "from being loaded. Please check the configuration of Java or your "
+                       + "servlet container.");
+            logger.debug("Creation of AuthenticationProvider disallowed by security manager.", e);
+        }
+        catch (InstantiationException e) {
+            logger.error("The authentication extension in use is not properly defined. "
+                       + "Please contact the developers of the extension or, if you "
+                       + "are the developer, turn on debug-level logging.");
+            logger.debug("AuthenticationProvider cannot be instantiated.", e);
+        }
+        catch (IllegalAccessException e) {
+            logger.error("The authentication extension in use is not properly defined. "
+                       + "Please contact the developers of the extension or, if you "
+                       + "are the developer, turn on debug-level logging.");
+            logger.debug("Default constructor of AuthenticationProvider is not public.", e);
+        }
+        catch (IllegalArgumentException e) {
+            logger.error("The authentication extension in use is not properly defined. "
+                       + "Please contact the developers of the extension or, if you "
+                       + "are the developer, turn on debug-level logging.");
+            logger.debug("Default constructor of AuthenticationProvider cannot accept zero arguments.", e);
+        } 
+        catch (InvocationTargetException e) {
+
+            // Obtain causing error - create relatively-informative stub error if cause is unknown
+            Throwable cause = e.getCause();
+            if (cause == null)
+                cause = new GuacamoleException("Error encountered during initialization.");
+            
+            logger.error("Authentication extension failed to start: {}", cause.getMessage());
+            logger.debug("AuthenticationProvider instantiation failed.", e);
+
+        }
+       
+        // Associate instance, if any
+        authProvider = instance;
+
+    }
+
+    @Override
+    public String getIdentifier() {
+
+        // Ignore auth attempts if no auth provider could be loaded
+        if (authProvider == null) {
+            logger.warn("The authentication system could not be loaded. Please check for errors earlier in the logs.");
+            return facadeIdentifier;
+        }
+
+        // Delegate to underlying auth provider
+        return authProvider.getIdentifier();
+
+    }
+
+    @Override
+    public AuthenticatedUser authenticateUser(Credentials credentials)
+            throws GuacamoleException {
+
+        // Ignore auth attempts if no auth provider could be loaded
+        if (authProvider == null) {
+            logger.warn("Authentication attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
+            throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD);
+        }
+
+        // Delegate to underlying auth provider
+        return authProvider.authenticateUser(credentials);
+
+    }
+
+    @Override
+    public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
+            Credentials credentials) throws GuacamoleException {
+
+        // Ignore auth attempts if no auth provider could be loaded
+        if (authProvider == null) {
+            logger.warn("Reauthentication attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
+            throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD);
+        }
+
+        // Delegate to underlying auth provider
+        return authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
+
+    }
+
+    @Override
+    public UserContext getUserContext(AuthenticatedUser authenticatedUser)
+            throws GuacamoleException {
+
+        // Ignore auth attempts if no auth provider could be loaded
+        if (authProvider == null) {
+            logger.warn("User data retrieval attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
+            return null;
+        }
+
+        // Delegate to underlying auth provider
+        return authProvider.getUserContext(authenticatedUser);
+        
+    }
+
+    @Override
+    public UserContext updateUserContext(UserContext context,
+            AuthenticatedUser authenticatedUser)
+            throws GuacamoleException {
+
+        // Ignore auth attempts if no auth provider could be loaded
+        if (authProvider == null) {
+            logger.warn("User data refresh attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs.");
+            return null;
+        }
+
+        // Delegate to underlying auth provider
+        return authProvider.updateUserContext(context, authenticatedUser);
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java
new file mode 100644
index 0000000..1d1ac54
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.extension;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.apache.guacamole.GuacamoleException;
+
+/**
+ * A ClassLoader implementation which finds classes within .jar files within a
+ * given directory.
+ *
+ * @author Michael Jumper
+ */
+public class DirectoryClassLoader extends URLClassLoader {
+
+    /**
+     * Returns an instance of DirectoryClassLoader configured to load .jar
+     * files from the given directory. Calling this function multiple times
+     * will not affect previously-returned instances of DirectoryClassLoader.
+     *
+     * @param dir
+     *     The directory from which .jar files should be read.
+     *
+     * @return
+     *     A DirectoryClassLoader instance which loads classes from the .jar
+     *     files in the given directory.
+     *
+     * @throws GuacamoleException
+     *     If the given file is not a directory, or the contents of the given
+     *     directory cannot be read.
+     */
+    public static DirectoryClassLoader getInstance(final File dir)
+            throws GuacamoleException {
+
+        try {
+            // Attempt to create singleton classloader which loads classes from
+            // all .jar's in the lib directory defined in guacamole.properties
+            return AccessController.doPrivileged(new PrivilegedExceptionAction<DirectoryClassLoader>() {
+
+                @Override
+                public DirectoryClassLoader run() throws GuacamoleException {
+                    return new DirectoryClassLoader(dir);
+                }
+
+            });
+        }
+
+        catch (PrivilegedActionException e) {
+            throw (GuacamoleException) e.getException();
+        }
+
+    }
+
+    /**
+     * Returns all .jar files within the given directory as an array of URLs.
+     *
+     * @param dir
+     *     The directory to retrieve all .jar files from.
+     *
+     * @return
+     *     An array of the URLs of all .jar files within the given directory.
+     *
+     * @throws GuacamoleException
+     *     If the given file is not a directory, or the contents of the given
+     *     directory cannot be read.
+     */
+    private static URL[] getJarURLs(File dir) throws GuacamoleException {
+
+        // Validate directory is indeed a directory
+        if (!dir.isDirectory())
+            throw new GuacamoleException(dir + " is not a directory.");
+
+        // Get list of URLs for all .jar's in the lib directory
+        Collection<URL> jarURLs = new ArrayList<URL>();
+        File[] files = dir.listFiles(new FilenameFilter() {
+
+            @Override
+            public boolean accept(File dir, String name) {
+
+                // If it ends with .jar, accept the file
+                return name.endsWith(".jar");
+
+            }
+
+        });
+
+        // Verify directory was successfully read
+        if (files == null)
+            throw new GuacamoleException("Unable to read contents of directory " + dir);
+
+        // Add the URL for each .jar to the jar URL list
+        for (File file : files) {
+
+            try {
+                jarURLs.add(file.toURI().toURL());
+            }
+            catch (MalformedURLException e) {
+                throw new GuacamoleException(e);
+            }
+
+        }
+
+        // Set delegate classloader to new URLClassLoader which loads from the .jars found above.
+        URL[] urls = new URL[jarURLs.size()];
+        return jarURLs.toArray(urls);
+
+    }
+
+    /**
+     * Creates a new DirectoryClassLoader configured to load .jar files from
+     * the given directory.
+     *
+     * @param dir
+     *     The directory from which .jar files should be read.
+     *
+     * @throws GuacamoleException
+     *     If the given file is not a directory, or the contents of the given
+     *     directory cannot be read.
+     */
+
+    private DirectoryClassLoader(File dir) throws GuacamoleException {
+        super(getJarURLs(dir), DirectoryClassLoader.class.getClassLoader());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java
new file mode 100644
index 0000000..7ac0563
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.extension;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.resource.ClassPathResource;
+import org.apache.guacamole.resource.Resource;
+
+/**
+ * A Guacamole extension, which may provide custom authentication, static
+ * files, theming/branding, etc.
+ *
+ * @author Michael Jumper
+ */
+public class Extension {
+
+    /**
+     * The Jackson parser for parsing the language JSON files.
+     */
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    /**
+     * The name of the manifest file that describes the contents of a
+     * Guacamole extension.
+     */
+    private static final String MANIFEST_NAME = "guac-manifest.json";
+
+    /**
+     * The parsed manifest file of this extension, describing the location of
+     * resources within the extension.
+     */
+    private final ExtensionManifest manifest;
+
+    /**
+     * The classloader to use when reading resources from this extension,
+     * including classes and static files.
+     */
+    private final ClassLoader classLoader;
+
+    /**
+     * Map of all JavaScript resources defined within the extension, where each
+     * key is the path to that resource within the extension.
+     */
+    private final Map<String, Resource> javaScriptResources;
+
+    /**
+     * Map of all CSS resources defined within the extension, where each key is
+     * the path to that resource within the extension.
+     */
+    private final Map<String, Resource> cssResources;
+
+    /**
+     * Map of all HTML patch resources defined within the extension, where each
+     * key is the path to that resource within the extension.
+     */
+    private final Map<String, Resource> htmlResources;
+
+    /**
+     * Map of all translation resources defined within the extension, where
+     * each key is the path to that resource within the extension.
+     */
+    private final Map<String, Resource> translationResources;
+
+    /**
+     * Map of all resources defined within the extension which are not already
+     * associated as JavaScript, CSS, or translation resources, where each key
+     * is the path to that resource within the extension.
+     */
+    private final Map<String, Resource> staticResources;
+
+    /**
+     * The collection of all AuthenticationProvider classes defined within the
+     * extension.
+     */
+    private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses;
+
+    /**
+     * The resource for the small favicon for the extension. If provided, this
+     * will replace the default Guacamole icon.
+     */
+    private final Resource smallIcon;
+
+    /**
+     * The resource foe the large favicon for the extension. If provided, this 
+     * will replace the default Guacamole icon.
+     */
+    private final Resource largeIcon;
+
+    /**
+     * Returns a new map of all resources corresponding to the collection of
+     * paths provided. Each resource will be associated with the given
+     * mimetype, and stored in the map using its path as the key.
+     *
+     * @param mimetype
+     *     The mimetype to associate with each resource.
+     *
+     * @param paths
+     *     The paths corresponding to the resources desired.
+     *
+     * @return
+     *     A new, unmodifiable map of resources corresponding to the
+     *     collection of paths provided, where the key of each entry in the
+     *     map is the path for the resource stored in that entry.
+     */
+    private Map<String, Resource> getClassPathResources(String mimetype, Collection<String> paths) {
+
+        // If no paths are provided, just return an empty map 
+        if (paths == null)
+            return Collections.<String, Resource>emptyMap();
+
+        // Add classpath resource for each path provided
+        Map<String, Resource> resources = new HashMap<String, Resource>(paths.size());
+        for (String path : paths)
+            resources.put(path, new ClassPathResource(classLoader, mimetype, path));
+
+        // Callers should not rely on modifying the result
+        return Collections.unmodifiableMap(resources);
+
+    }
+
+    /**
+     * Returns a new map of all resources corresponding to the map of resource
+     * paths provided. Each resource will be associated with the mimetype 
+     * stored in the given map using its path as the key.
+     *
+     * @param resourceTypes 
+     *     A map of all paths to their corresponding mimetypes.
+     *
+     * @return
+     *     A new, unmodifiable map of resources corresponding to the
+     *     collection of paths provided, where the key of each entry in the
+     *     map is the path for the resource stored in that entry.
+     */
+    private Map<String, Resource> getClassPathResources(Map<String, String> resourceTypes) {
+
+        // If no paths are provided, just return an empty map 
+        if (resourceTypes == null)
+            return Collections.<String, Resource>emptyMap();
+
+        // Add classpath resource for each path/mimetype pair provided
+        Map<String, Resource> resources = new HashMap<String, Resource>(resourceTypes.size());
+        for (Map.Entry<String, String> resource : resourceTypes.entrySet()) {
+
+            // Get path and mimetype from entry
+            String path = resource.getKey();
+            String mimetype = resource.getValue();
+
+            // Store as path/resource pair
+            resources.put(path, new ClassPathResource(classLoader, mimetype, path));
+
+        }
+
+        // Callers should not rely on modifying the result
+        return Collections.unmodifiableMap(resources);
+
+    }
+
+    /**
+     * Retrieve the AuthenticationProvider subclass having the given name. If
+     * the class having the given name does not exist or isn't actually a
+     * subclass of AuthenticationProvider, an exception will be thrown.
+     *
+     * @param name
+     *     The name of the AuthenticationProvider class to retrieve.
+     *
+     * @return
+     *     The subclass of AuthenticationProvider having the given name.
+     *
+     * @throws GuacamoleException
+     *     If no such class exists, or if the class with the given name is not
+     *     a subclass of AuthenticationProvider.
+     */
+    @SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom()
+    private Class<AuthenticationProvider> getAuthenticationProviderClass(String name)
+            throws GuacamoleException {
+
+        try {
+
+            // Get authentication provider class
+            Class<?> authenticationProviderClass = classLoader.loadClass(name);
+
+            // Verify the located class is actually a subclass of AuthenticationProvider
+            if (!AuthenticationProvider.class.isAssignableFrom(authenticationProviderClass))
+                throw new GuacamoleServerException("Authentication providers MUST extend the AuthenticationProvider class.");
+
+            // Return located class
+            return (Class<AuthenticationProvider>) authenticationProviderClass;
+
+        }
+        catch (ClassNotFoundException e) {
+            throw new GuacamoleException("Authentication provider class not found.", e);
+        }
+
+    }
+
+    /**
+     * Returns a new collection of all AuthenticationProvider subclasses having
+     * the given names. If any class does not exist or isn't actually a
+     * subclass of AuthenticationProvider, an exception will be thrown, and
+     * no further AuthenticationProvider classes will be loaded.
+     *
+     * @param names
+     *     The names of the AuthenticationProvider classes to retrieve.
+     *
+     * @return
+     *     A new collection of all AuthenticationProvider subclasses having the
+     *     given names.
+     *
+     * @throws GuacamoleException
+     *     If any given class does not exist, or if any given class is not a
+     *     subclass of AuthenticationProvider.
+     */
+    private Collection<Class<AuthenticationProvider>> getAuthenticationProviderClasses(Collection<String> names)
+            throws GuacamoleException {
+
+        // If no classnames are provided, just return an empty list
+        if (names == null)
+            return Collections.<Class<AuthenticationProvider>>emptyList();
+
+        // Define all auth provider classes
+        Collection<Class<AuthenticationProvider>> classes = new ArrayList<Class<AuthenticationProvider>>(names.size());
+        for (String name : names)
+            classes.add(getAuthenticationProviderClass(name));
+
+        // Callers should not rely on modifying the result
+        return Collections.unmodifiableCollection(classes);
+
+    }
+
+    /**
+     * Loads the given file as an extension, which must be a .jar containing
+     * a guac-manifest.json file describing its contents.
+     *
+     * @param parent
+     *     The classloader to use as the parent for the isolated classloader of
+     *     this extension.
+     *
+     * @param file
+     *     The file to load as an extension.
+     *
+     * @throws GuacamoleException
+     *     If the provided file is not a .jar file, does not contain the
+     *     guac-manifest.json, or if guac-manifest.json is invalid and cannot
+     *     be parsed.
+     */
+    public Extension(final ClassLoader parent, final File file) throws GuacamoleException {
+
+        try {
+
+            // Open extension
+            ZipFile extension = new ZipFile(file);
+
+            try {
+
+                // Retrieve extension manifest
+                ZipEntry manifestEntry = extension.getEntry(MANIFEST_NAME);
+                if (manifestEntry == null)
+                    throw new GuacamoleServerException("Extension " + file.getName() + " is missing " + MANIFEST_NAME);
+
+                // Parse manifest
+                manifest = mapper.readValue(extension.getInputStream(manifestEntry), ExtensionManifest.class);
+                if (manifest == null)
+                    throw new GuacamoleServerException("Contents of " + MANIFEST_NAME + " must be a valid JSON object.");
+
+            }
+
+            // Always close zip file, if possible
+            finally {
+                extension.close();
+            }
+
+            try {
+
+                // Create isolated classloader for this extension
+                classLoader = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>() {
+
+                    @Override
+                    public ClassLoader run() throws GuacamoleException {
+
+                        try {
+
+                            // Classloader must contain only the extension itself
+                            return new URLClassLoader(new URL[]{file.toURI().toURL()}, parent);
+
+                        }
+                        catch (MalformedURLException e) {
+                            throw new GuacamoleException(e);
+                        }
+
+                    }
+
+                });
+
+            }
+
+            // Rethrow any GuacamoleException
+            catch (PrivilegedActionException e) {
+                throw (GuacamoleException) e.getException();
+            }
+
+        }
+
+        // Abort load if not a valid zip file
+        catch (ZipException e) {
+            throw new GuacamoleServerException("Extension is not a valid zip file: " + file.getName(), e);
+        }
+
+        // Abort if manifest cannot be parsed (invalid JSON)
+        catch (JsonParseException e) {
+            throw new GuacamoleServerException(MANIFEST_NAME + " is not valid JSON: " + file.getName(), e);
+        }
+
+        // Abort if zip file cannot be read at all due to I/O errors
+        catch (IOException e) {
+            throw new GuacamoleServerException("Unable to read extension: " + file.getName(), e);
+        }
+
+        // Define static resources
+        cssResources = getClassPathResources("text/css", manifest.getCSSPaths());
+        javaScriptResources = getClassPathResources("text/javascript", manifest.getJavaScriptPaths());
+        htmlResources = getClassPathResources("text/html", manifest.getHTMLPaths());
+        translationResources = getClassPathResources("application/json", manifest.getTranslationPaths());
+        staticResources = getClassPathResources(manifest.getResourceTypes());
+
+        // Define authentication providers
+        authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
+
+        // Get small icon resource if provided
+        if (manifest.getSmallIcon() != null)
+            smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon());
+        else
+            smallIcon = null;
+
+        // Get large icon resource if provided
+        if (manifest.getLargeIcon() != null)
+            largeIcon = new ClassPathResource(classLoader, "image/png", manifest.getLargeIcon());
+        else
+            largeIcon = null;
+    }
+
+    /**
+     * Returns the version of the Guacamole web application for which this
+     * extension was built.
+     *
+     * @return
+     *     The version of the Guacamole web application for which this
+     *     extension was built.
+     */
+    public String getGuacamoleVersion() {
+        return manifest.getGuacamoleVersion();
+    }
+
+    /**
+     * Returns the name of this extension, as declared in the extension's
+     * manifest.
+     *
+     * @return
+     *     The name of this extension.
+     */
+    public String getName() {
+        return manifest.getName();
+    }
+
+    /**
+     * Returns the namespace of this extension, as declared in the extension's
+     * manifest.
+     *
+     * @return
+     *     The namespace of this extension.
+     */
+    public String getNamespace() {
+        return manifest.getNamespace();
+    }
+
+    /**
+     * Returns a map of all declared JavaScript resources associated with this
+     * extension, where the key of each entry in the map is the path to that
+     * resource within the extension .jar. JavaScript resources are declared
+     * within the extension manifest.
+     *
+     * @return
+     *     All declared JavaScript resources associated with this extension.
+     */
+    public Map<String, Resource> getJavaScriptResources() {
+        return javaScriptResources;
+    }
+
+    /**
+     * Returns a map of all declared CSS resources associated with this
+     * extension, where the key of each entry in the map is the path to that
+     * resource within the extension .jar. CSS resources are declared within
+     * the extension manifest.
+     *
+     * @return
+     *     All declared CSS resources associated with this extension.
+     */
+    public Map<String, Resource> getCSSResources() {
+        return cssResources;
+    }
+
+    /**
+     * Returns a map of all declared HTML patch resources associated with this
+     * extension, where the key of each entry in the map is the path to that
+     * resource within the extension .jar. HTML patch resources are declared
+     * within the extension manifest.
+     *
+     * @return
+     *     All declared HTML patch resources associated with this extension.
+     */
+    public Map<String, Resource> getHTMLResources() {
+        return htmlResources;
+    }
+
+    /**
+     * Returns a map of all declared translation resources associated with this
+     * extension, where the key of each entry in the map is the path to that
+     * resource within the extension .jar. Translation resources are declared
+     * within the extension manifest.
+     *
+     * @return
+     *     All declared translation resources associated with this extension.
+     */
+    public Map<String, Resource> getTranslationResources() {
+        return translationResources;
+    }
+
+    /**
+     * Returns a map of all declared resources associated with this extension,
+     * where these resources are not already associated as JavaScript, CSS, or
+     * translation resources. The key of each entry in the map is the path to
+     * that resource within the extension .jar. Static resources are declared
+     * within the extension manifest.
+     *
+     * @return
+     *     All declared static resources associated with this extension.
+     */
+    public Map<String, Resource> getStaticResources() {
+        return staticResources;
+    }
+
+    /**
+     * Returns all declared authentication providers classes associated with
+     * this extension. Authentication providers are declared within the
+     * extension manifest.
+     *
+     * @return
+     *     All declared authentication provider classes with this extension.
+     */
+    public Collection<Class<AuthenticationProvider>> getAuthenticationProviderClasses() {
+        return authenticationProviderClasses;
+    }
+
+    /**
+     * Returns the resource for the small favicon for the extension. If
+     * provided, this will replace the default Guacamole icon.
+     * 
+     * @return 
+     *     The resource for the small favicon.
+     */
+    public Resource getSmallIcon() {
+        return smallIcon;
+    }
+
+    /**
+     * Returns the resource for the large favicon for the extension. If
+     * provided, this will replace the default Guacamole icon.
+     * 
+     * @return 
+     *     The resource for the large favicon.
+     */
+    public Resource getLargeIcon() {
+        return largeIcon;
+    }
+
+}