blob: d9dea2ce906369beea3686db08fb5a1278c60a65 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Chapter 24. Custom authentication</title><link rel="stylesheet" type="text/css" href="gug.css" /><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot" /><link rel="home" href="index.html" title="Guacamole Manual" /><link rel="up" href="developers-guide.html" title="Part II. Developer's Guide" /><link rel="prev" href="custom-protocols.html" title="Chapter 23. Adding new protocols" /><link rel="next" href="event-listeners.html" title="Chapter 25. Event listeners" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/>
</head><body>
<!-- CONTENT -->
<div id="page"><div id="content">
<div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Chapter 24. Custom authentication</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><th width="60%" align="center">Part II. Developer's Guide</th><td width="20%" align="right"> <a accesskey="n" href="event-listeners.html">Next</a></td></tr></table><hr /></div><div xml:lang="en" class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><a id="custom-auth"></a>Chapter 24. Custom authentication</h2></div></div></div><div class="toc"><p><strong>Table of Contents</strong></p><dl class="toc"><dt><span class="section"><a href="custom-auth.html#custom-auth-model">Guacamole's authentication model</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-skeleton">A Guacamole extension skeleton</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-building">Building the extension</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-config">Configuration and authentication</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-more-config">Parsing the configuration</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-installing">Installing the extension</a></span></dt></dl></div><a id="idm46420844868960" class="indexterm"></a><p>Guacamole's authentication layer is designed to be extendable such that users can
integrate Guacamole into existing authentication systems without having to resort to writing
their own web application around the Guacamole API.</p><p>The web application comes with a default authentication mechanism which uses an XML file
to associate users with connections. Extensions for Guacamole that provide LDAP-based
authentication or database-based authentication have also been developed.</p><p>To demonstrate the principles involved, we will implement a very simple authentication
extension which associates a single user/password pair with a single connection, with all
this information saved in properties inside the <code class="filename">guacamole.properties</code>
file.</p><p>In general, all other authentication extensions for Guacamole will use the principles
demonstrated here. This tutorial demonstrates the simplest way to create an authentication
extension for Guacamole - an authentication extension that does not support management of
users and connections via the web interface.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-model"></a>Guacamole's authentication model</h2></div></div></div><p>When you view any page in Guacamole, whether that be the login screen or the client
interface, the page makes an authentication attempt with the web application, sending
all available credentials. After entering your username and password, the exact same
process occurs, except the web application receives the username and password as
well.</p><p>The web application handles this authentication attempt by collecting all credentials
available and passing them to designated classes called "authentication providers".
Given the set of credentials, authentication providers return a context object that
provides restricted access to other users and connections, if any.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-skeleton"></a>A Guacamole extension skeleton</h2></div></div></div><p>For simplicity's sake, and because this is how things are done upstream in the
Guacamole project, we will use Maven to build our extension.</p><p>The bare minimum required for a Guacamole authentication extension is a
<code class="filename">pom.xml</code> file listing guacamole-ext as a dependency, a single
.java file implementing our stub of an authentication provider, and a
<code class="filename">guac-manifest.json</code> file describing the extension and pointing
to our authentication provider class.</p><p>In our stub, we won't actually do any authentication yet; we'll just universally
reject all authentication attempts by returning <code class="varname">null</code> for any
credentials given. You can verify that this is what happens by checking the server
logs.</p><div class="example"><a id="idm46420844654944"></a><p class="title"><strong>Example 24.1. Barebones <code class="filename">pom.xml</code> required for a simple authentication
extension.</strong></p><div class="example-contents"><pre class="programlisting">&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd"&gt;
&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
&lt;groupId&gt;org.apache.guacamole&lt;/groupId&gt;
&lt;artifactId&gt;guacamole-auth-tutorial&lt;/artifactId&gt;
&lt;packaging&gt;jar&lt;/packaging&gt;
&lt;version&gt;1.1.0&lt;/version&gt;
&lt;name&gt;guacamole-auth-tutorial&lt;/name&gt;
&lt;properties&gt;
&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;/properties&gt;
&lt;build&gt;
&lt;plugins&gt;
&lt;!-- Written for 1.6 --&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
&lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
&lt;version&gt;3.3&lt;/version&gt;
&lt;configuration&gt;
&lt;source&gt;1.6&lt;/source&gt;
&lt;target&gt;1.6&lt;/target&gt;
&lt;/configuration&gt;
&lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;
&lt;dependencies&gt;
&lt;!-- Guacamole Extension API --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.apache.guacamole&lt;/groupId&gt;
&lt;artifactId&gt;guacamole-ext&lt;/artifactId&gt;
&lt;version&gt;1.1.0&lt;/version&gt;
&lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;/project&gt;</pre></div></div><br class="example-break" /><p>We won't need to update this <code class="filename">pom.xml</code> throughout the rest of the
tutorial. Even after adding new files, Maven will just find them and compile as
necessary.</p><p>Naturally, we need the actual authentication extension skeleton code. While you can
put this in whatever file and package you want, for the sake of this tutorial, we will
assume you are using
<code class="classname">org.apache.guacamole.auth.TutorialAuthenticationProvider</code>.</p><div class="example"><a id="idm46420845030320"></a><p class="title"><strong>Example 24.2. A skeleton <code class="classname">TutorialAuthenticationProvider</code></strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
/**
* Authentication provider implementation intended to demonstrate basic use
* of Guacamole's extension API. The credentials and connection information for
* a single user are stored directly in guacamole.properties.
*/
public class TutorialAuthenticationProvider extends SimpleAuthenticationProvider {
@Override
public String getIdentifier() {
return "tutorial";
}
@Override
public Map&lt;String, GuacamoleConfiguration&gt;
getAuthorizedConfigurations(Credentials credentials)
throws GuacamoleException {
// Do nothing ... yet
return null;
}
}</pre></div></div><br class="example-break" /><p>To conform with Maven, this skeleton file must be placed within
<code class="filename">src/main/java/org/apache/guacamole/auth</code> as
<code class="filename">TutorialAuthenticationProvider.java</code>.</p><p>Notice how simple the authentication provider is. The
<code class="classname">SimpleAuthenticationProvider</code> base class simplifies the
<code class="classname">AuthenticationProvider</code> interface, requiring nothing more than
a unique identifier (we will use "tutorial") and a single
<code class="methodname">getAuthorizedConfigurations()</code> implementation, which must
return a <code class="classname">Map</code> of <code class="classname">GuacamoleConfiguration</code>
each associated with some arbitrary unique ID. This unique ID will be presented to the
user in the connection list after they log in.</p><p>For now, <code class="methodname">getAuthorizedConfigurations()</code> will just return
<code class="varname">null</code>. This will cause Guacamole to report an invalid login for
every attempt. Note that there is a difference in semantics between returning an empty
map and returning <code class="varname">null</code>, as the former indicates the credentials are
authorized but simply have no associated configurations, while the latter indicates the
credentials are not authorized at all.</p><p>The only remaining piece for the overall skeleton to be complete is a
<code class="filename">guac-manifest.json</code> file. <span class="emphasis"><em>This file is absolutely
required for all Guacamole extensions.</em></span> The
<code class="filename">guac-manifest.json</code> format is described in more detail in <a class="xref" href="guacamole-ext.html" title="Chapter 22. guacamole-ext">Chapter 22, <em>guacamole-ext</em></a>. It provides
for quite a few properties, but for our authentication extension we are mainly
interested in the Guacamole version sanity check (to make sure an extension built for
the API of Guacamole version X is not accidentally used against version Y) and telling
Guacamole where to find our authentication provider class.</p><p>The Guacamole extension format requires that <code class="filename">guac-manifest.json</code>
be placed in the root directory of the extension <code class="filename">.jar</code> file. To
accomplish this with Maven, we place it within the
<code class="filename">src/main/resources</code> directory. Maven will automatically pick it
up during the build and include it within the <code class="filename">.jar</code>.</p><div class="example"><a id="idm46420844610752"></a><p class="title"><strong>Example 24.3. The required <code class="filename">guac-manifest.json</code></strong></p><div class="example-contents"><pre class="programlisting">{
"guacamoleVersion" : "1.1.0",
"name" : "Tutorial Authentication Extension",
"namespace" : "guac-auth-tutorial",
"authProviders" : [
"org.apache.guacamole.auth.TutorialAuthenticationProvider"
]
}</pre></div></div><br class="example-break" /></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-building"></a>Building the extension</h2></div></div></div><p>Once all three of the above files are in place, the extension will build, and can even
be installed within Guacamole (see <a class="xref" href="custom-auth.html#custom-auth-installing" title="Installing the extension">the section called “Installing the extension”</a> at the end of this chapter), even though it is
just a skeleton at this point. It won't do anything yet other than reject all
authentication attempts, but it's good to at least try building the extension to make
sure nothing is missing and that all steps have been followed correctly so far:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> mvn package
<code class="computeroutput">[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building guacamole-auth-tutorial 1.1.0
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.345 s
[INFO] Finished at: 2015-12-16T13:39:00-08:00
[INFO] Final Memory: 14M/138M
[INFO] ------------------------------------------------------------------------</code>
<code class="prompt">$</code></pre></div><p>Assuming you see the "<code class="computeroutput">BUILD SUCCESS</code>" message when you
build the extension, there will be a new file,
<code class="filename">target/guacamole-auth-tutorial-1.1.0.jar</code>, which can be
installed within Guacamole and tested. If you changed the name or version of the project
in the <code class="filename">pom.xml</code> file, the name of this new <code class="filename">.jar</code>
file will be different, but it can still be found within
<code class="filename">target/</code>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-config"></a>Configuration and authentication</h2></div></div></div><p>Once we receive credentials, we need to validate those credentials against the
associated properties in <code class="filename">guacamole.properties</code> (our source of
authentication information for the sake of this tutorial).</p><p>We will define four properties:</p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="property">tutorial-user</span></span></dt><dd><p>The name of the only user we accept.</p></dd><dt><span class="term"><span class="property">tutorial-password</span></span></dt><dd><p>The password we require for the user specified to be
authenticated.</p></dd><dt><span class="term"><span class="property">tutorial-protocol</span></span></dt><dd><p>The protocol of the configuration this user is authorized to use,
which will be sent to guacd when the user logs in and selects their
connection.</p></dd><dt><span class="term"><span class="property">tutorial-parameters</span></span></dt><dd><p>A comma-delimited list of
<code class="code"><em class="replaceable"><code>name</code></em>=<em class="replaceable"><code>value</code></em></code>
pairs. For the sake of simplicity, we'll assume there will never be any
commas in the values.</p></dd></dl></div><p>If the username and password match what is stored in the file, we read the
configuration information, store it in a <code class="classname">GuacamoleConfiguration</code>,
and return the configuration within a set, telling Guacamole that this user is
authorized but only to access the configurations returned.</p><p>Upstream, we always place the properties of authentication providers in their own
class, and so we will also do that here in this tutorial, as it keeps things
organized.</p><div class="example"><a id="idm46420844345040"></a><p class="title"><strong>Example 24.4. <code class="filename">TutorialProperties.java</code>, a class containing property
definitions</strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth;
import org.apache.guacamole.properties.StringGuacamoleProperty;
/**
* Utility class containing all properties used by the custom authentication
* tutorial. The properties defined here must be specified within
* guacamole.properties to configure the tutorial authentication provider.
*/
public class TutorialGuacamoleProperties {
/**
* This class should not be instantiated.
*/
private TutorialGuacamoleProperties() {}
/**
* The only user to allow.
*/
public static final StringGuacamoleProperty TUTORIAL_USER =
new StringGuacamoleProperty() {
@Override
public String getName() { return "tutorial-user"; }
};
/**
* The password required for the specified user.
*/
public static final StringGuacamoleProperty TUTORIAL_PASSWORD =
new StringGuacamoleProperty() {
@Override
public String getName() { return "tutorial-password"; }
};
/**
* The protocol to use when connecting.
*/
public static final StringGuacamoleProperty TUTORIAL_PROTOCOL =
new StringGuacamoleProperty() {
@Override
public String getName() { return "tutorial-protocol"; }
};
/**
* All parameters associated with the connection, as a comma-delimited
* list of name="value"
*/
public static final StringGuacamoleProperty TUTORIAL_PARAMETERS =
new StringGuacamoleProperty() {
@Override
public String getName() { return "tutorial-parameters"; }
};
}</pre></div></div><br class="example-break" /><p>Normally, we would define a new type of <code class="classname">GuacamoleProperty</code> to
handle the parsing of the parameters required by <code class="varname">TUTORIAL_PARAMETERS</code>,
but for the sake of simplicity, parsing of this parameter will be embedded in the
authentication function later.</p><p>You will need to modify your existing <code class="filename">guacamole.properties</code> file,
adding each of the above properties to describe one of your available
connections.</p><div class="example"><a id="idm46420844339392"></a><p class="title"><strong>Example 24.5. Properties describing a user and connection, as required by this tutorial</strong></p><div class="example-contents"><pre class="programlisting"># Username and password
tutorial-user: <em class="replaceable"><code>tutorial</code></em>
tutorial-password: <em class="replaceable"><code>password</code></em>
# Connection information
tutorial-protocol: <em class="replaceable"><code>vnc</code></em>
tutorial-parameters: <em class="replaceable"><code>hostname=localhost, port=5900</code></em></pre></div></div><br class="example-break" /><p>Once these properties and their accessor class are in place, it's simple enough to
read the properties within <code class="methodname">getAuthorizedConfigurations()</code> and
authenticate the user based on their username and password.</p><div class="example"><a id="idm46420844335376"></a><p class="title"><strong>Example 24.6. Checking the credentials against the properties</strong></p><div class="example-contents"><pre class="programlisting">@Override
public Map&lt;String, GuacamoleConfiguration&gt;
getAuthorizedConfigurations(Credentials credentials)
throws GuacamoleException {
// Get the Guacamole server environment
Environment environment = new LocalEnvironment();
// Get username from guacamole.properties
String username = environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_USER
);
// If wrong username, fail
if (!username.equals(credentials.getUsername()))
return null;
// Get password from guacamole.properties
String password = environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_PASSWORD
);
// If wrong password, fail
if (!password.equals(credentials.getPassword()))
return null;
// Successful login. Return configurations (STUB)
return new HashMap&lt;String, GuacamoleConfiguration&gt;();
}</pre></div></div><br class="example-break" /><p>As is, the authentication provider will work in its current state in that the correct
username and password will authenticate the user, while an incorrect username or
password will not, but we still aren't returning an actual map of configurations. We
need to construct the configuration based on the properties in the
<code class="filename">guacamole.properties</code> file after the user has been
authenticated, and return that configuration to the web application.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-more-config"></a>Parsing the configuration</h2></div></div></div><p>The only remaining task before we have a fully-functioning authentication provider is
to actually parse the configuration from the <code class="filename">guacamole.properties</code>
file.</p><div class="example"><a id="idm46420844330320"></a><p class="title"><strong>Example 24.7. Parsing and returning a <code class="classname">GuacamoleConfiguration</code></strong></p><div class="example-contents"><pre class="programlisting">@Override
public Map&lt;String, GuacamoleConfiguration&gt;
getAuthorizedConfigurations(Credentials credentials)
throws GuacamoleException {
// Get the Guacamole server environment
Environment environment = new LocalEnvironment();
// Get username from guacamole.properties
String username = environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_USER
);
// If wrong username, fail
if (!username.equals(credentials.getUsername()))
return null;
// Get password from guacamole.properties
String password = environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_PASSWORD
);
// If wrong password, fail
if (!password.equals(credentials.getPassword()))
return null;
// Successful login. Return configurations.
Map&lt;String, GuacamoleConfiguration&gt; configs =
new HashMap&lt;String, GuacamoleConfiguration&gt;();
// Create new configuration
GuacamoleConfiguration config = new GuacamoleConfiguration();
// Set protocol specified in properties
config.setProtocol(environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_PROTOCOL
));
// Set all parameters, splitting at commas
for (String parameterValue : environment.getRequiredProperty(
TutorialGuacamoleProperties.TUTORIAL_PARAMETERS
).split(",\\s*")) {
// Find the equals sign
int equals = parameterValue.indexOf('=');
if (equals == -1)
throw new GuacamoleServerException("Required equals sign missing");
// Get name and value from parameter string
String name = parameterValue.substring(0, equals);
String value = parameterValue.substring(equals+1);
// Set parameter as specified
config.setParameter(name, value);
}
configs.put("Tutorial Connection", config);
return configs;
}</pre></div></div><br class="example-break" /><p>The extension is now complete and can be built as described earlier in <a class="xref" href="custom-auth.html#custom-auth-building" title="Building the extension">the section called “Building the extension”</a>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-installing"></a>Installing the extension</h2></div></div></div><p>Guacamole extensions are self-contained <code class="filename">.jar</code> files which are
installed by being placed within <code class="filename">GUACAMOLE_HOME/extensions</code>, and
this extension is no different. As described in <a class="xref" href="configuring-guacamole.html" title="Chapter 5. Configuring Guacamole">Chapter 5, <em>Configuring Guacamole</em></a>,
<code class="varname">GUACAMOLE_HOME</code> is a placeholder used to refer to the directory
that Guacamole uses to locate its configuration files and extensions. Typically, this
will be the <code class="filename">.guacamole</code> directory within the home directory of the
user running Tomcat.</p><p>To install your extension, ensure that the required properties have been added to your
<code class="filename">guacamole.properties</code>, copy the
<code class="filename">target/guacamole-auth-tutorial-1.1.0.jar</code> file into
<code class="filename">GUACAMOLE_HOME/extensions</code> and restart Tomcat. Guacamole will
automatically load your extension, logging an informative message that it has done
so:</p><div class="informalexample"><pre class="screen">Extension "Tutorial Authentication Extension" loaded.</pre></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="developers-guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="event-listeners.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 23. Adding new protocols </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 25. Event listeners</td></tr></table></div>
</div></div>
<!-- Google Analytics -->
<script type="text/javascript">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-75289145-1', 'auto');
ga('send', 'pageview');
</script>
</body></html>