diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java
index 20861bae98..a9000939f2 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java
@@ -26,7 +26,6 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
diff --git a/extensions/guacamole-template-ext/.gitignore b/extensions/guacamole-template-ext/.gitignore
new file mode 100644
index 0000000000..1de9633aed
--- /dev/null
+++ b/extensions/guacamole-template-ext/.gitignore
@@ -0,0 +1,3 @@
+src/main/resources/generated/
+target/
+*~
diff --git a/extensions/guacamole-template-ext/.ratignore b/extensions/guacamole-template-ext/.ratignore
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/extensions/guacamole-template-ext/pom.xml b/extensions/guacamole-template-ext/pom.xml
new file mode 100644
index 0000000000..5c56228435
--- /dev/null
+++ b/extensions/guacamole-template-ext/pom.xml
@@ -0,0 +1,148 @@
+
+
+
+
+ 4.0.0
+ org.apache.guacamole
+ guacamole-template-ext
+ jar
+ 1.5.2
+ guacamole-template-ext
+ http://guacamole.apache.org/
+
+
+ org.apache.guacamole
+ extensions
+ 1.5.2
+ ../
+
+
+
+
+
+
+
+ com.keithbranton.mojo
+ angular-maven-plugin
+ 0.3.4
+
+
+ generate-resources
+
+ html2js
+
+
+
+
+ ${basedir}/src/main/resources
+ **/*.html
+ ${basedir}/src/main/resources/generated/templates-main/templates.js
+ app/ext/template-ext
+
+
+
+
+
+ com.github.buckelieg
+ minify-maven-plugin
+
+
+ default-cli
+
+ UTF-8
+
+ ${basedir}/src/main/resources
+ ${project.build.directory}/classes
+
+ /
+ /
+ templateExt.css
+
+
+ license.txt
+
+
+
+ **/*.css
+
+
+ /
+ /
+ templateExt.js
+
+
+ license.txt
+
+
+
+ **/*.js
+
+
+
+
+ **/*.test.js
+
+ CLOSURE
+
+
+
+ OFF
+ OFF
+
+
+
+
+ minify
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.guacamole
+ guacamole-ext
+ 1.5.2
+ provided
+
+
+
+
+ com.google.inject
+ guice
+
+
+
+
+ com.google.guava
+ guava
+
+
+
+
+
diff --git a/extensions/guacamole-template-ext/src/main/assembly/dist.xml b/extensions/guacamole-template-ext/src/main/assembly/dist.xml
new file mode 100644
index 0000000000..6ee3cd8c87
--- /dev/null
+++ b/extensions/guacamole-template-ext/src/main/assembly/dist.xml
@@ -0,0 +1,53 @@
+
+
+
+
+ dist
+ ${project.artifactId}-${project.version}
+
+
+
+ tar.gz
+
+
+
+
+
+
+
+
+ target/licenses
+
+
+
+
+ target
+
+
+ *.jar
+
+
+
+
+
+
diff --git a/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/TemplateAuthenticationProvider.java b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/TemplateAuthenticationProvider.java
new file mode 100644
index 0000000000..8a036c2300
--- /dev/null
+++ b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/TemplateAuthenticationProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.templates;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.templates.user.TemplateUserContext;
+
+/**
+ * An implementation of an Authentication Provider which allows users to
+ * associate a connection with another connection as a template, pulling
+ * the values of parameters specified in the selected "template connection"
+ * into the connection.
+ */
+public class TemplateAuthenticationProvider extends AbstractAuthenticationProvider {
+
+ @Override
+ public String getIdentifier() {
+ return "template-ext";
+ }
+
+ @Override
+ public UserContext decorate(UserContext context, AuthenticatedUser authenticatedUser, Credentials credentials) throws GuacamoleException {
+ if (context instanceof TemplateUserContext)
+ return context;
+
+ return new TemplateUserContext(context);
+ }
+
+}
diff --git a/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/connection/TemplatedConnection.java b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/connection/TemplatedConnection.java
new file mode 100644
index 0000000000..362ace641a
--- /dev/null
+++ b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/connection/TemplatedConnection.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.templates.connection;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.form.Form;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.DelegatingConnection;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.apache.guacamole.templates.form.ConnectionChooserField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Connection implementation which wraps another Connection object, but also
+ * adds the functionality to link the Connection to another object as a
+ * "template" connection, inheriting parameters and attributes from the
+ * template.
+ */
+public class TemplatedConnection extends DelegatingConnection {
+
+ /**
+ * The Logger for this class.
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(TemplatedConnection.class);
+
+ /**
+ * The attribute name that provides the connection identifier which should
+ * e the template for this connection, and the parameters and attributes
+ * of which this connection will inherit.
+ */
+ public static final String TEMPLATE_ID_ATTRIBUTE_NAME = "template-connection-id";
+
+ /**
+ * An array of all of the attributes implemented specifically by this
+ * connection type.
+ */
+ public static final List TEMPLATED_CONNECTION_ATTRIBUTES = Arrays.asList(TEMPLATE_ID_ATTRIBUTE_NAME);
+
+ /**
+ * The form that provides all of the fields that need to be visible for this
+ * connection implementation in order to provide the template functionality.
+ */
+ public static final Form TEMPLATED_CONNECTION_FORM = new Form(
+ "templated-connection-form",
+ Arrays.asList(new ConnectionChooserField(TEMPLATE_ID_ATTRIBUTE_NAME))
+ );
+
+ /**
+ * The directory in which this connection exists.
+ */
+ private final Directory connectionDirectory;
+
+
+ /**
+ * Create a new TemplatedConnection, wrapping the given Connection object
+ * and delegating functionality to that underlying object, after providing
+ * the template capability.
+ *
+ * @param connection
+ * The original connection to wrap.
+ *
+ * @param directory
+ * The Connection Directory in which this connection exists, provided
+ * for the purpose of retrieving the template connection.
+ */
+ public TemplatedConnection(Connection connection, Directory directory) {
+ super(connection);
+ this.connectionDirectory = directory;
+ }
+
+ /**
+ * Retrieve the original connection that this object wraps.
+ *
+ * @return
+ * The original connection that this object wraps.
+ */
+ public Connection getWrappedConnection() {
+ return getDelegateConnection();
+ }
+
+ @Override
+ public Map getAttributes() {
+
+ // Make a mutable copy of the connection attributes
+ Map attributes = new HashMap<>(super.getAttributes());
+
+ // Loop through and add the ones that aren't present so that they are
+ // available on the web page.
+ for (String attribute : TEMPLATED_CONNECTION_ATTRIBUTES) {
+ String value = attributes.get(attribute);
+ if (value == null || value.isEmpty())
+ attributes.put(attribute, null);
+ }
+
+ // Return the new Map of attributes.
+ return attributes;
+ }
+
+ @Override
+ public void setAttributes(Map attributes) {
+
+ // Make a mutable copy of connection attributes
+ attributes = new HashMap<>(attributes);
+
+ // Loop through extension-specific attributes, only sending ones
+ // that are non-null and non-empty to the underlying storage mechanism.
+ for (String attribute : TEMPLATED_CONNECTION_ATTRIBUTES) {
+ String value = attributes.get(attribute);
+ if (value != null && value.isEmpty())
+ attributes.put(attribute, null);
+ }
+
+ // Set the complete attributes
+ super.setAttributes(attributes);
+
+ }
+
+ @Override
+ public GuacamoleConfiguration getConfiguration() {
+
+ // Retrieve the decorated connection's configuration.
+ GuacamoleConfiguration config = super.getConfiguration();
+
+ // Get the template connection identifier - if its empty, just return this config.
+ String templateId = getTemplateConnectionId();
+ if (templateId == null || templateId.isEmpty())
+ return config;
+
+ try {
+ // Get the template connection from its identifier.
+ Connection templateConnection = connectionDirectory.get(templateId);
+
+ // Get the template connection configuration.
+ GuacamoleConfiguration mergedConfig = new GuacamoleConfiguration(templateConnection.getConfiguration());
+
+ // Loop through values, overriding the template values as required.
+ for (String parameter: config.getParameterNames()) {
+ String value = config.getParameter(parameter);
+ if (value != null && !value.isEmpty()) {
+ mergedConfig.setParameter(parameter, value);
+ }
+ }
+
+ // Return the merged configuration
+ return mergedConfig;
+
+ } catch (GuacamoleException e) {
+ LOGGER.warn("Could not retrieve template connection: {}", templateId);
+ LOGGER.debug("Exception retrieving template connection.", e);
+ return null;
+ }
+
+ }
+
+ /**
+ * Return the identifier of the connection that has been set as this
+ * connections template.
+ *
+ * @return
+ * The identifier of the template connection as set in the
+ * connection attributes.
+ */
+ private String getTemplateConnectionId() {
+
+ Map attributes = getAttributes();
+ String templateId = attributes.get(TEMPLATE_ID_ATTRIBUTE_NAME);
+ if (templateId != null && !templateId.isEmpty())
+ return templateId;
+ return null;
+
+ }
+
+
+
+}
diff --git a/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/form/ConnectionChooserField.java b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/form/ConnectionChooserField.java
new file mode 100644
index 0000000000..eda0329844
--- /dev/null
+++ b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/form/ConnectionChooserField.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.templates.form;
+
+import org.apache.guacamole.form.Field;
+
+/**
+ * A field that allows users to choose from possible existing connections to
+ * which they have access.
+ */
+public class ConnectionChooserField extends Field {
+
+ /**
+ * The field type, used to load the correct field within the AngularJS
+ * application.
+ */
+ public static String FIELD_TYPE = "GUAC_CONNECTION_CHOOSER";
+
+ /**
+ * Create a new ConnectionChooserField with the specified name.
+ *
+ * @param name
+ * The name of the field.
+ */
+ public ConnectionChooserField(String name) {
+ super(name, FIELD_TYPE);
+ }
+
+}
diff --git a/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/user/TemplateUserContext.java b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/user/TemplateUserContext.java
new file mode 100644
index 0000000000..9d80b6e3bc
--- /dev/null
+++ b/extensions/guacamole-template-ext/src/main/java/org/apache/guacamole/templates/user/TemplateUserContext.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.templates.user;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.form.Form;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.DecoratingDirectory;
+import org.apache.guacamole.net.auth.DelegatingUserContext;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.templates.connection.TemplatedConnection;
+
+/**
+ * A UserContext which decorates another context, adding the capability for
+ * connections within this context to be linked to other connections in order
+ * to inherit settings from those connections.
+ */
+public class TemplateUserContext extends DelegatingUserContext {
+
+ /**
+ * Create a new instances of the TemplateUserContext, wrapping the given
+ * UserContext and delegating functionality to that context.
+ *
+ * @param context
+ * The UserContext to wrap.
+ */
+ public TemplateUserContext(UserContext context) {
+ super(context);
+ }
+
+ @Override
+ public Directory getConnectionDirectory() throws GuacamoleException {
+ return new DecoratingDirectory(super.getConnectionDirectory()) {
+
+ @Override
+ protected Connection decorate(Connection object) {
+ if (object instanceof TemplatedConnection)
+ return object;
+ return new TemplatedConnection(object, this);
+ }
+
+ @Override
+ protected Connection undecorate(Connection object) {
+ if (object instanceof TemplatedConnection)
+ return ((TemplatedConnection) object).getWrappedConnection();
+ return object;
+ }
+
+ };
+ }
+
+ @Override
+ public Collection