diff --git a/.dockerignore b/.dockerignore index cea180242..629ad03ea 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ .git -**/.gitignore **/*~ **/target/ diff --git a/Dockerfile b/Dockerfile index bd8131f28..ebc3cb0c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,29 +21,36 @@ # Dockerfile for guacamole-client # -# Start from Tomcat image -FROM tomcat:8.0.20-jre8 +# Use args for Tomcat image label to allow image builder to choose alternatives +# such as `--build-arg TOMCAT_JRE=jre8-alpine` +# +ARG TOMCAT_VERSION=8.0.20 +ARG TOMCAT_JRE=jre8 -# Environment variables +# Use official maven image for the build +FROM maven:3-jdk-8 AS builder + +# Build environment variables ENV \ - BUILD_DIR=/tmp/guacamole-docker-BUILD \ - BUILD_DEPENDENCIES=" \ - maven \ - openjdk-8-jdk-headless" + BUILD_DIR=/tmp/guacamole-docker-BUILD # Add configuration scripts -COPY guacamole-docker/bin /opt/guacamole/bin/ +COPY guacamole-docker/bin/ /opt/guacamole/bin/ # Copy source to container for sake of build COPY . "$BUILD_DIR" -# Build latest guacamole-client and authentication -RUN apt-get update && \ - apt-get install -y --no-install-recommends $BUILD_DEPENDENCIES && \ - /opt/guacamole/bin/build-guacamole.sh "$BUILD_DIR" /opt/guacamole && \ - rm -Rf "$BUILD_DIR" && \ - rm -Rf /var/lib/apt/lists/* && \ - apt-get purge -y --auto-remove $BUILD_DEPENDENCIES +# Run the build itself +RUN /opt/guacamole/bin/build-guacamole.sh "$BUILD_DIR" /opt/guacamole + +# For the runtime image, we start with the official Tomcat distribution +FROM tomcat:${TOMCAT_VERSION}-${TOMCAT_JRE} + +# This is where the build artifacts go in the runtime image +WORKDIR /opt/guacamole + +# Copy artifacts from builder image into this image +COPY --from=builder /opt/guacamole/ . # Start Guacamole under Tomcat, listening on 0.0.0.0:8080 EXPOSE 8080 diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java index 241f17aed..28122f9b8 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java @@ -107,4 +107,9 @@ public class CASAuthenticationProvider implements AuthenticationProvider { } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/DuoAuthenticationProvider.java b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/DuoAuthenticationProvider.java index f9989965d..1c84046cc 100644 --- a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/DuoAuthenticationProvider.java +++ b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/DuoAuthenticationProvider.java @@ -102,4 +102,9 @@ public class DuoAuthenticationProvider implements AuthenticationProvider { return context; } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderAuthenticationProvider.java b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderAuthenticationProvider.java index 1721f16ce..b3385b1db 100644 --- a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderAuthenticationProvider.java +++ b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderAuthenticationProvider.java @@ -107,4 +107,9 @@ public class HTTPHeaderAuthenticationProvider implements AuthenticationProvider } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/InjectedAuthenticationProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/InjectedAuthenticationProvider.java index d06d6edad..e73b3dfce 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/InjectedAuthenticationProvider.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/InjectedAuthenticationProvider.java @@ -104,4 +104,9 @@ public abstract class InjectedAuthenticationProvider implements AuthenticationPr authenticatedUser, credentials); } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSet.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSet.java index f89a45028..7b3d629c4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSet.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSet.java @@ -27,15 +27,17 @@ import java.util.List; import java.util.Set; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.jdbc.base.RestrictedObject; +import org.apache.guacamole.net.auth.ActivityRecordSet; +import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty; import org.apache.guacamole.net.auth.ConnectionRecord; /** - * A JDBC implementation of ConnectionRecordSet. Calls to asCollection() will - * query connection history records from the database. Which records are - * returned will be determined by the values passed in earlier. + * A JDBC implementation of ActivityRecordSet for ConnectionRecords. Calls to + * asCollection() will query connection history records from the database. Which + * records are returned will be determined by the values passed in earlier. */ public class ConnectionRecordSet extends RestrictedObject - implements org.apache.guacamole.net.auth.ConnectionRecordSet { + implements ActivityRecordSet { /** * Service for managing connection objects. diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java index 17f43806d..69eee780b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java @@ -19,7 +19,7 @@ package org.apache.guacamole.auth.jdbc.connection; -import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.ActivityRecordSet; /** * A sort predicate which species the property to use when sorting connection @@ -30,7 +30,7 @@ public class ConnectionRecordSortPredicate { /** * The property to use when sorting ConnectionRecords. */ - private final ConnectionRecordSet.SortableProperty property; + private final ActivityRecordSet.SortableProperty property; /** * Whether the sort order is descending (true) or ascending (false). @@ -47,7 +47,7 @@ public class ConnectionRecordSortPredicate { * @param descending * Whether the sort order is descending (true) or ascending (false). */ - public ConnectionRecordSortPredicate(ConnectionRecordSet.SortableProperty property, + public ConnectionRecordSortPredicate(ActivityRecordSet.SortableProperty property, boolean descending) { this.property = property; this.descending = descending; @@ -59,7 +59,7 @@ public class ConnectionRecordSortPredicate { * @return * The property that should be used when sorting ConnectionRecords. */ - public ConnectionRecordSet.SortableProperty getProperty() { + public ActivityRecordSet.SortableProperty getProperty() { return property; } 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 365c40dc5..c596b2771 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 @@ -24,6 +24,7 @@ import com.google.inject.Provider; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -232,6 +233,11 @@ public class ModeledConnection extends ModeledChildDirectoryObject getHistory() throws GuacamoleException { return connectionService.retrieveHistory(getCurrentUser(), this); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java index 1c955dbd8..5483d0267 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.sharing.connection; import com.google.inject.Inject; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -146,6 +147,11 @@ public class SharedConnection implements Connection { // Do nothing - changing attributes not supported } + @Override + public Date getLastActive() { + return null; + } + @Override public List getHistory() throws GuacamoleException { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java index 57120ea1c..8e7931d86 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java @@ -20,9 +20,12 @@ package org.apache.guacamole.auth.jdbc.sharing.user; import java.util.Collections; +import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.jdbc.sharing.permission.SharedObjectPermissionSet; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; @@ -88,6 +91,22 @@ public class SharedUser implements User { // Do nothing - no attributes supported } + @Override + public Date getLastActive() { + + // History is not recorded for shared users + return null; + + } + + @Override + public List getHistory() throws GuacamoleException { + + // History is not recorded for shared users + return Collections.emptyList(); + + } + @Override public String getPassword() { return null; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUserContext.java index cad1bab7f..4bc54b562 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUserContext.java @@ -28,16 +28,18 @@ import org.apache.guacamole.auth.jdbc.sharing.connectiongroup.SharedRootConnecti import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.ActivityRecord; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; -import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.ConnectionRecord; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.SharingProfile; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.auth.simple.SimpleActivityRecordSet; import org.apache.guacamole.net.auth.simple.SimpleConnectionGroupDirectory; -import org.apache.guacamole.net.auth.simple.SimpleConnectionRecordSet; import org.apache.guacamole.net.auth.simple.SimpleDirectory; /** @@ -175,8 +177,14 @@ public class SharedUserContext implements UserContext { } @Override - public ConnectionRecordSet getConnectionHistory() { - return new SimpleConnectionRecordSet(); + public ActivityRecordSet getConnectionHistory() { + return new SimpleActivityRecordSet(); + } + + @Override + public ActivityRecordSet getUserHistory() + throws GuacamoleException { + return new SimpleActivityRecordSet(); } @Override @@ -204,4 +212,9 @@ public class SharedUserContext implements UserContext { return Collections.
emptyList(); } + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 59ed13c1c..fe3a45b2f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -235,7 +235,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS // Build token filter containing credential tokens TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user.getCredentials()); + StandardTokens.addStandardTokens(tokenFilter, user); // Filter the configuration tokenFilter.filterValues(config.getParameters()); @@ -281,7 +281,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS // Build token filter containing credential tokens TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user.getCredentials()); + StandardTokens.addStandardTokens(tokenFilter, user); // Filter the configuration tokenFilter.filterValues(config.getParameters()); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java index 36a0be878..fc43e36b1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java @@ -29,6 +29,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.TimeZone; import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObject; @@ -49,6 +50,7 @@ import org.apache.guacamole.form.Form; import org.apache.guacamole.form.TextField; import org.apache.guacamole.form.TimeField; import org.apache.guacamole.form.TimeZoneField; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.permission.SystemPermission; @@ -792,4 +794,14 @@ public class ModeledUser extends ModeledDirectoryObject implements Us return getModel().isExpired(); } + @Override + public Date getLastActive() { + return null; + } + + @Override + public List getHistory() throws GuacamoleException { + return Collections.emptyList(); + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java index d43c3c1c3..1b238abf6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java @@ -36,12 +36,15 @@ import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileDirectory; import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.ActivityRecord; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.SharingProfile; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.simple.SimpleActivityRecordSet; /** * UserContext implementation which is driven by an arbitrary, underlying @@ -161,6 +164,12 @@ public class ModeledUserContext extends RestrictedObject return connectionRecordSet; } + @Override + public ActivityRecordSet getUserHistory() + throws GuacamoleException { + return new SimpleActivityRecordSet(); + } + @Override public ConnectionGroup getRootConnectionGroup() throws GuacamoleException { @@ -191,4 +200,9 @@ public class ModeledUserContext extends RestrictedObject return ModeledSharingProfile.ATTRIBUTES; } + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java index a3848f427..24118af45 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java @@ -136,4 +136,9 @@ public abstract class RemoteAuthenticatedUser implements AuthenticatedUser { return authenticationProvider; } + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json index c781c158c..6e03340b3 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json @@ -63,6 +63,14 @@ "NAME" : "Shared Connections (PostgreSQL)" }, + "DATA_SOURCE_SQLSERVER" : { + "NAME" : "SQL Server" + }, + + "DATA_SOURCe_SQLSERVER_SHARED" : { + "NAME" : "Shared Connections (SQL Server)" + }, + "HOME" : { "INFO_SHARED_BY" : "Shared by {USERNAME}" }, diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/pom.xml index 7b51fa2b7..05f5572d4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/pom.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/pom.xml @@ -109,6 +109,13 @@ 0.9.13-incubating + + + org.apache.guacamole + guacamole-auth-jdbc-sqlserver + 0.9.13-incubating + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/project-assembly.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/project-assembly.xml index 523b3a05e..58c886ceb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/project-assembly.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/project-assembly.xml @@ -49,6 +49,14 @@ + + + sqlserver + + org.apache.guacamole:guacamole-auth-jdbc-sqlserver + + + @@ -72,6 +80,12 @@ ../guacamole-auth-jdbc-postgresql/schema + + + sqlserver/schema + ../guacamole-auth-jdbc-sqlserver/schema + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index c2c12c138..287ca02fa 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -129,7 +129,7 @@ - + - + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index 2b3e193ca..4545332b5 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -127,7 +127,7 @@ - + - + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-jdbc-sqlserver + jar + guacamole-auth-jdbc-sqlserver + http://guacamole.incubator.apache.org/ + + + UTF-8 + + + + org.apache.guacamole + guacamole-auth-jdbc + 0.9.13-incubating + ../../ + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.6 + 1.6 + + -Xlint:all + -Werror + + true + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack-dependencies + + + runtime + ${project.build.directory}/classes + + + + + + + + org.apache.rat + apache-rat-plugin + 0.12 + + + + **/*.json + + + + + + + validate + validate + + check + + + + + + + + + + + + + + org.apache.guacamole + guacamole-ext + provided + + + + + org.apache.guacamole + guacamole-auth-jdbc-base + 0.9.13-incubating + + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/001-create-schema.sql new file mode 100644 index 000000000..c64f6f971 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/001-create-schema.sql @@ -0,0 +1,467 @@ +/* + * 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. + */ + +/** + * Turn on ANSI_NULLS for the entire DB to make it ISO-compliant. + */ +ALTER DATABASE CURRENT SET ANSI_NULLS ON; +GO; + +/** + * Turn on QUOTED_IDENTIFIER for the entire DB. + */ +ALTER DATABASE CURRENT SET QUOTED_IDENTIFIER ON; + +/** + * List for permission data type. + */ +CREATE RULE [guacamole_permission_list] + AS + @list IN ('READ','UPDATE','DELETE','ADMINISTER'); +GO; + +/** + * List for system permission data type. + */ +CREATE RULE [guacamole_system_permission_list] + AS + @list IN ('CREATE_CONNECTION', + 'CREATE_CONNECTION_GROUP', + 'CREATE_SHARING_PROFILE', + 'CREATE_USER', + 'ADMINISTER'); +GO; + +/** + * The permission data type. + */ +CREATE TYPE [guacamole_permission] FROM [nvarchar](10) NOT NULL; +EXEC sp_bindrule 'guacamole_permission_list','guacamole_permission'; + +/** + * The system permission data type. + */ +CREATE TYPE [guacamole_system_permission] FROM [nvarchar](32) NOT NULL; +EXEC sp_bindrule 'guacamole_system_permission_list','guacamole_system_permission'; +GO; + +/** + * The connection_group table stores organizational and balancing groups. + */ +CREATE TABLE [guacamole_connection_group]( + [connection_group_id] [int] IDENTITY(1,1) NOT NULL, + [parent_id] [int] NULL, + [connection_group_name] [nvarchar](128) NOT NULL, + [type] [nvarchar](32) NOT NULL, + [max_connections] [int] NULL, + [max_connections_per_user] [int] NULL, + [enable_session_affinity] [bit] NOT NULL, + + CONSTRAINT [PK_guacmaole_connection_group] PRIMARY KEY CLUSTERED + ([connection_group_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for connection_group table. + */ +ALTER TABLE [guacamole_connection_group] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_group_connection_group_id] FOREIGN KEY([parent_id]) + REFERENCES [guacamole_connection_group] ([connection_group_id]); +ALTER TABLE [guacamole_connection_group] + CHECK CONSTRAINT [FK_guacamole_connection_group_connection_group_id]; +ALTER TABLE [guacamole_connection_group] + WITH CHECK ADD CONSTRAINT [CK_guacamole_connection_group_type] + CHECK (([type]='BALANCING' OR [type]='ORGANIZATIONAL')); +ALTER TABLE [guacamole_connection_group] + CHECK CONSTRAINT [CK_guacamole_connection_group_type]; + +/** + * Default values for connection_group table. + */ +ALTER TABLE [guacamole_connection_group] + ADD CONSTRAINT [DF_guacamole_connection_group_type] DEFAULT (N'ORGANIZATIONAL') FOR [type]; +ALTER TABLE [guacamole_connection_group] + ADD CONSTRAINT [DF_guacamole_connection_group_enable_session_affinity] DEFAULT ((0)) FOR [enable_session_affinity]; +GO; + +/** + * The connection table, for storing connections and attributes. + */ +CREATE TABLE [guacamole_connection]( + [connection_id] [int] IDENTITY(1,1) NOT NULL, + [connection_name] [nvarchar](128) NOT NULL, + [parent_id] [int] NULL, + [protocol] [nvarchar](32) NOT NULL, + [proxy_port] [int] NULL, + [proxy_hostname] [nvarchar](512) NULL, + [proxy_encryption_method] [nvarchar](4) NULL, + [max_connections] [int] NULL, + [max_connections_per_user] [int] NULL, + [connection_weight] [int] NULL, + [failover_only] [bit] NOT NULL, + + CONSTRAINT [PK_guacamole_connection] PRIMARY KEY CLUSTERED + ([connection_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +ALTER TABLE [guacamole_connection] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_connection_group] FOREIGN KEY([parent_id]) + REFERENCES [guacamole_connection_group] ([connection_group_id]); +ALTER TABLE [guacamole_connection] + CHECK CONSTRAINT [FK_guacamole_connection_connection_group]; +ALTER TABLE [guacamole_connection] + WITH CHECK ADD CONSTRAINT [CK_proxy_encryption_method] + CHECK (([proxy_encryption_method]='SSL' OR [proxy_encryption_method]='NONE')); +ALTER TABLE [guacamole_connection] + CHECK CONSTRAINT [CK_proxy_encryption_method]; +ALTER TABLE [guacamole_connection] + ADD CONSTRAINT [DF_guacamole_connection_failover_only] DEFAULT ((0)) FOR [failover_only]; +GO; + +/** + * The user table stores user accounts, passwords, and properties. + */ +CREATE TABLE [guacamole_user]( + [user_id] [int] IDENTITY(1,1) NOT NULL, + [username] [nvarchar](128) NOT NULL, + [password_hash] [binary](32) NOT NULL, + [password_salt] [binary](32) NULL, + [password_date] [datetime] NOT NULL, + [disabled] [bit] NOT NULL, + [expired] [bit] NOT NULL, + [access_window_start] [time](7) NULL, + [access_window_end] [time](7) NULL, + [valid_from] [date] NULL, + [valid_until] [date] NULL, + [timezone] [nvarchar](64) NULL, + [full_name] [nvarchar](256) NULL, + [email_address] [nvarchar](256) NULL, + [organization] [nvarchar](256) NULL, + [organizational_role] [nvarchar](256) NULL, + + CONSTRAINT [PK_guacamole_user] PRIMARY KEY CLUSTERED + ([user_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Defaults for user table + */ +ALTER TABLE [guacamole_user] + ADD CONSTRAINT [DF_guacamole_user_disabled] DEFAULT ((0)) FOR [disabled]; +ALTER TABLE [guacamole_user] + ADD CONSTRAINT [DF_guacamole_user_expired] DEFAULT ((0)) FOR [expired]; +GO; + +/** + * The sharing_profile table stores profiles that allow + * connections to be shared amongst multiple users. + */ +CREATE TABLE [guacamole_sharing_profile]( + [sharing_profile_id] [int] IDENTITY(1,1) NOT NULL, + [sharing_profile_name] [nvarchar](128) NOT NULL, + [primary_connection_id] [int] NOT NULL, + + CONSTRAINT [PK_guacamole_sharing_profile] PRIMARY KEY CLUSTERED + ([sharing_profile_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for sharing_profile table. + */ +ALTER TABLE [guacamole_sharing_profile] + WITH CHECK ADD CONSTRAINT [FK_guacamole_sharing_profile_connection] FOREIGN KEY([primary_connection_id]) + REFERENCES [guacamole_connection] ([connection_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_sharing_profile] + CHECK CONSTRAINT [FK_guacamole_sharing_profile_connection]; +GO; + +/** + * The connection_parameter table stores parameters for + * connection objects. + */ +CREATE TABLE [guacamole_connection_parameter]( + [connection_id] [int] NOT NULL, + [parameter_name] [nvarchar](128) NOT NULL, + [parameter_value] [nvarchar](4000) NOT NULL, + + CONSTRAINT [PK_guacamole_connection_parameter] PRIMARY KEY CLUSTERED + ([connection_id] ASC, [parameter_name] ASC) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]; + +/** + * Foreign keys for the connection_parameter table. + */ +ALTER TABLE [guacamole_connection_parameter] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_parameter_connection] FOREIGN KEY([connection_id]) + REFERENCES [guacamole_connection] ([connection_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_connection_parameter] + CHECK CONSTRAINT [FK_guacamole_connection_parameter_connection]; +GO; + +/** + * The sharing_profile_parameter table stores parameters + * for sharing_profile objects. + */ +CREATE TABLE [guacamole_sharing_profile_parameter]( + [sharing_profile_id] [int] NOT NULL, + [parameter_name] [nvarchar](128) NOT NULL, + [parameter_value] [nvarchar](4000) NOT NULL, + + CONSTRAINT [PK_guacamole_sharing_profile_parameter] PRIMARY KEY CLUSTERED + ([sharing_profile_id] ASC, [parameter_name] ASC) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]; + +/** + * Foreign keys for the sharing_profile_parameter + * table. + */ +ALTER TABLE [guacamole_sharing_profile_parameter] + WITH CHECK ADD CONSTRAINT [FK_guacamole_sharing_profile_parameter_sharing_profile] FOREIGN KEY([sharing_profile_id]) + REFERENCES [guacamole_sharing_profile] ([sharing_profile_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_sharing_profile_parameter] + CHECK CONSTRAINT [FK_guacamole_sharing_profile_parameter_sharing_profile]; +GO; + +/** + * The connection_permission table stores permission + * mappings for connection objects. + */ +CREATE TABLE [guacamole_connection_permission]( + [user_id] [int] NOT NULL, + [connection_id] [int] NOT NULL, + [permission] [guacamole_permission] NOT NULL, + + CONSTRAINT [PK_guacamole_connection_permission] PRIMARY KEY CLUSTERED + ([user_id] ASC, [connection_id] ASC, [permission] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for the connection_permission table. + */ +ALTER TABLE [guacamole_connection_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_permission_connection1] FOREIGN KEY([connection_id]) + REFERENCES [guacamole_connection] ([connection_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_connection_permission] + CHECK CONSTRAINT [FK_guacamole_connection_permission_connection1]; +ALTER TABLE [guacamole_connection_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_permission_user1] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_connection_permission] + CHECK CONSTRAINT [FK_guacamole_connection_permission_user1]; +GO; + +/** + * The connection_group_permission table stores permission mappings for + * connection_group objects. + */ +CREATE TABLE [guacamole_connection_group_permission]( + [user_id] [int] NOT NULL, + [connection_group_id] [int] NOT NULL, + [permission] [guacamole_permission] NOT NULL, + + CONSTRAINT [PK_guacamole_connection_group_permission] PRIMARY KEY CLUSTERED + ([user_id] ASC, [connection_group_id] ASC, [permission] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for the connection_group_permission table. + */ +ALTER TABLE [guacamole_connection_group_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_group_permission_connection_group] FOREIGN KEY([connection_group_id]) + REFERENCES [guacamole_connection_group] ([connection_group_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_connection_group_permission] + CHECK CONSTRAINT [FK_guacamole_connection_group_permission_connection_group]; +ALTER TABLE [guacamole_connection_group_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_group_permission_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_connection_group_permission] + CHECK CONSTRAINT [FK_guacamole_connection_group_permission_user]; +GO; + +/** + * The sharing_profile_permission table stores permission + * mappings for sharing_profile objects. + */ +CREATE TABLE [guacamole_sharing_profile_permission]( + [user_id] [int] NOT NULL, + [sharing_profile_id] [int] NOT NULL, + [permission] [guacamole_permission] NOT NULL, + + CONSTRAINT [PK_guacamole_sharing_profile_permission] PRIMARY KEY CLUSTERED + ([user_id] ASC, [sharing_profile_id] ASC, [permission] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for the sharing_profile_permission table. + */ +ALTER TABLE [guacamole_sharing_profile_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_sharing_profile_permission_sharing_profile] FOREIGN KEY([sharing_profile_id]) + REFERENCES [guacamole_sharing_profile] ([sharing_profile_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_sharing_profile_permission] + CHECK CONSTRAINT [FK_guacamole_sharing_profile_permission_sharing_profile]; +ALTER TABLE [guacamole_sharing_profile_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_sharing_profile_permission_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_sharing_profile_permission] + CHECK CONSTRAINT [FK_guacamole_sharing_profile_permission_user]; +GO; + +/** + * The system_permission table stores permission mappings + * for system-level operations. + */ +CREATE TABLE [guacamole_system_permission]( + [user_id] [int] NOT NULL, + [permission] [guacamole_system_permission] NOT NULL, + + CONSTRAINT [PK_guacamole_system_permission] PRIMARY KEY CLUSTERED + ([user_id] ASC, [permission] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for system_permission table. + */ +ALTER TABLE [guacamole_system_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_system_permission_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_system_permission] + CHECK CONSTRAINT [FK_guacamole_system_permission_user]; +GO; + +/** + * The user_permission table stores permission mappings + * for users to other users. + */ +CREATE TABLE [guacamole_user_permission]( + [user_id] [int] NOT NULL, + [affected_user_id] [int] NOT NULL, + [permission] [guacamole_permission] NOT NULL, + + CONSTRAINT [PK_guacamole_user_permission] PRIMARY KEY CLUSTERED + ([user_id] ASC, [affected_user_id] ASC, [permission] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for user_permission table. + */ +ALTER TABLE [guacamole_user_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_user_permission_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_user_permission] + CHECK CONSTRAINT [FK_guacamole_user_permission_user]; +ALTER TABLE [guacamole_user_permission] + WITH CHECK ADD CONSTRAINT [FK_guacamole_user_permission_user1] FOREIGN KEY([affected_user_id]) + REFERENCES [guacamole_user] ([user_id]); +ALTER TABLE [guacamole_user_permission] + CHECK CONSTRAINT [FK_guacamole_user_permission_user1]; +GO; + +/** + * The connection_history table stores records for historical + * connections. + */ +CREATE TABLE [guacamole_connection_history]( + [history_id] [int] IDENTITY(1,1) NOT NULL, + [user_id] [int] NULL, + [username] [nvarchar](128) NOT NULL, + [remote_host] [nvarchar](256) NULL, + [connection_id] [int] NULL, + [connection_name] [nvarchar](128) NOT NULL, + [sharing_profile_id] [int] NULL, + [sharing_profile_name] [nvarchar](128) NULL, + [start_date] [datetime] NOT NULL, + [end_date] [datetime] NULL, + + CONSTRAINT [PK_guacamole_connection_history] PRIMARY KEY CLUSTERED + ([history_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for connection_history table + */ +ALTER TABLE [guacamole_connection_history] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_history_connection] FOREIGN KEY([connection_id]) + REFERENCES [guacamole_connection] ([connection_id]) + ON UPDATE CASCADE + ON DELETE SET NULL; +ALTER TABLE [guacamole_connection_history] + CHECK CONSTRAINT [FK_guacamole_connection_history_connection]; +ALTER TABLE [guacamole_connection_history] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_history_sharing_profile] FOREIGN KEY([sharing_profile_id]) + REFERENCES [guacamole_sharing_profile] ([sharing_profile_id]); +ALTER TABLE [guacamole_connection_history] + CHECK CONSTRAINT [FK_guacamole_connection_history_sharing_profile]; +ALTER TABLE [guacamole_connection_history] + WITH CHECK ADD CONSTRAINT [FK_guacamole_connection_history_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE SET NULL; +ALTER TABLE [guacamole_connection_history] + CHECK CONSTRAINT [FK_guacamole_connection_history_user]; +GO; + +/** + * The user_password_history table stores password history + * for users, allowing for enforcing rules associated with + * reuse of passwords. + */ +CREATE TABLE [guacamole_user_password_history]( + [password_history_id] [int] IDENTITY(1,1) NOT NULL, + [user_id] [int] NOT NULL, + [password_hash] [binary](32) NOT NULL, + [password_salt] [binary](32) NULL, + [password_date] [datetime] NOT NULL, + + CONSTRAINT [PK_guacamole_user_password_history] PRIMARY KEY CLUSTERED + ([password_history_id] ASC) ON [PRIMARY] +) ON [PRIMARY]; + +/** + * Foreign keys for user_password_history table + */ +ALTER TABLE [guacamole_user_password_history] + WITH CHECK ADD CONSTRAINT [FK_guacamole_user_password_history_user] FOREIGN KEY([user_id]) + REFERENCES [guacamole_user] ([user_id]) + ON UPDATE CASCADE + ON DELETE CASCADE; +ALTER TABLE [guacamole_user_password_history] + CHECK CONSTRAINT [FK_guacamole_user_password_history_user]; +GO; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/002-create-admin-user.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/002-create-admin-user.sql new file mode 100644 index 000000000..15944a28a --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/schema/002-create-admin-user.sql @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** + * Create the default admin user account and set up full privileges. + */ +INSERT INTO [guacamole_user] (username, password_hash, password_salt, password_date) +VALUES ('guacadmin', + 0xCA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960, + 0xFE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264, + getdate()); + +INSERT INTO [guacamole_user_permission] +SELECT [guacamole_user].[user_id], [affected].[user_id], permission +FROM ( + SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'READ' AS permission + UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'UPDATE' AS permission + UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'ADMINISTER' AS permission) + permissions + JOIN [guacamole_user] ON permissions.username = [guacamole_user].[username] + JOIN [guacamole_user] affected ON permissions.affected_username = affected.username; + +INSERT INTO [guacamole_system_permission] +SELECT user_id, permission +FROM ( + SELECT 'guacadmin' AS username, 'CREATE_CONNECTION' AS permission + UNION SELECT 'guacadmin' AS username, 'CREATE_CONNECTION_GROUP' AS permission + UNION SELECT 'guacadmin' AS username, 'CREATE_SHARING_PROFILE' AS permission + UNION SELECT 'guacadmin' AS username, 'CREATE_USER' AS permission + UNION SELECT 'guacadmin' AS username, 'ADMINISTER' AS permission) + permissions + JOIN [guacamole_user] ON permissions.username = [guacamole_user].[username]; +GO; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProvider.java new file mode 100644 index 000000000..ef5d61d90 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProvider.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.auth.sqlserver; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.InjectedAuthenticationProvider; +import org.apache.guacamole.auth.jdbc.JDBCAuthenticationProviderService; + +/** + * Provides a SQLServer-based implementation of the AuthenticationProvider + * functionality. + */ +public class SQLServerAuthenticationProvider extends InjectedAuthenticationProvider { + + /** + * Creates a new SQLServerAuthenticationProvider that reads and writes + * authentication data to a SQLServer database defined by properties in + * guacamole.properties. + * + * @throws GuacamoleException + * If a required property is missing, or an error occurs while parsing + * a property. + */ + public SQLServerAuthenticationProvider() throws GuacamoleException { + super(new SQLServerInjectorProvider(), JDBCAuthenticationProviderService.class); + } + + @Override + public String getIdentifier() { + return "sqlserver"; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProviderModule.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProviderModule.java new file mode 100644 index 000000000..22cb47461 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerAuthenticationProviderModule.java @@ -0,0 +1,121 @@ +/* + * 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.auth.sqlserver; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.name.Names; +import java.lang.UnsupportedOperationException; +import java.util.Properties; +import org.apache.guacamole.GuacamoleException; +import org.mybatis.guice.datasource.helper.JdbcHelper; + +/** + * Guice module which configures SQLServer-specific injections. + */ +public class SQLServerAuthenticationProviderModule implements Module { + + /** + * MyBatis-specific configuration properties. + */ + private final Properties myBatisProperties = new Properties(); + + /** + * SQLServer-specific driver configuration properties. + */ + private final Properties driverProperties = new Properties(); + + /** + * Which SQL Server driver should be used. + */ + private SQLServerDriver sqlServerDriver; + + /** + * Creates a new SQLServer authentication provider module that configures + * driver and MyBatis properties using the given environment. + * + * @param environment + * The environment to use when configuring MyBatis and the underlying + * JDBC driver. + * + * @throws GuacamoleException + * If a required property is missing, or an error occurs while parsing + * a property. + */ + public SQLServerAuthenticationProviderModule(SQLServerEnvironment environment) + throws GuacamoleException { + + // Set the SQLServer-specific properties for MyBatis. + myBatisProperties.setProperty("mybatis.environment.id", "guacamole"); + myBatisProperties.setProperty("JDBC.host", environment.getSQLServerHostname()); + myBatisProperties.setProperty("JDBC.port", String.valueOf(environment.getSQLServerPort())); + myBatisProperties.setProperty("JDBC.schema", environment.getSQLServerDatabase()); + myBatisProperties.setProperty("JDBC.username", environment.getSQLServerUsername()); + myBatisProperties.setProperty("JDBC.password", environment.getSQLServerPassword()); + myBatisProperties.setProperty("JDBC.autoCommit", "false"); + myBatisProperties.setProperty("mybatis.pooled.pingEnabled", "true"); + myBatisProperties.setProperty("mybatis.pooled.pingQuery", "SELECT 1"); + + // Use UTF-8 in database + driverProperties.setProperty("characterEncoding", "UTF-8"); + + // Capture which driver to use for the connection. + this.sqlServerDriver = environment.getSQLServerDriver(); + + } + + @Override + public void configure(Binder binder) { + + // Bind SQLServer-specific properties with the configured driver. + switch(sqlServerDriver) { + case JTDS: + JdbcHelper.SQL_Server_jTDS.configure(binder); + break; + + case DATA_DIRECT: + JdbcHelper.SQL_Server_DataDirect.configure(binder); + break; + + case MICROSOFT_LEGACY: + JdbcHelper.SQL_Server_MS_Driver.configure(binder); + break; + + case MICROSOFT_2005: + JdbcHelper.SQL_Server_2005_MS_Driver.configure(binder); + break; + + default: + throw new UnsupportedOperationException( + "A driver has been specified that is not supported by this module." + ); + } + + // Bind MyBatis properties + Names.bindProperties(binder, myBatisProperties); + + // Bind JDBC driver properties + binder.bind(Properties.class) + .annotatedWith(Names.named("JDBC.driverProperties")) + .toInstance(driverProperties); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java new file mode 100644 index 000000000..ec01d0668 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.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.auth.sqlserver; + +/** + * The possible SQL Server drivers to use when using a TDS-compatible database. + */ +public enum SQLServerDriver { + + /** + * The open source jTDS driver. + */ + JTDS, + + /** + * The Progress DataDirect driver. + */ + DATA_DIRECT, + + /** + * The Microsoft Legacy SQL Server driver. + */ + MICROSOFT_LEGACY, + + /** + * The Microsoft 2005 SQL Server driver. + */ + MICROSOFT_2005; +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java new file mode 100644 index 000000000..21a62721c --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java @@ -0,0 +1,60 @@ +/* + * 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.auth.sqlserver; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.properties.GuacamoleProperty; + +/** + * A property whose value is a SQLServerDriver. The incoming string values of "jtds", "datadirect", + * "microsoft", and "microsoft2005" into the corresponding SQLServerDriver enum value. Any + * values that are not valid result in a parse error. + */ +public abstract class SQLServerDriverProperty implements GuacamoleProperty { + + @Override + public SQLServerDriver parseValue(String value) throws GuacamoleException { + + // If no value provided, return null. + if (value == null) + return null; + + // jTDS Driver + if (value.equals("jtds")) + return SQLServerDriver.JTDS; + + // Progress DataDirect Driver + if (value.equals("datadirect")) + return SQLServerDriver.DATA_DIRECT; + + // Microsoft Legacy Driver + if (value.equals("microsoft")) + return SQLServerDriver.MICROSOFT_LEGACY; + + // Microsoft 2005 Driver + if (value.equals("microsoft2005")) + return SQLServerDriver.MICROSOFT_2005; + + throw new GuacamoleServerException("SQLServer driver must be one of \"jtds\", \"datadirect\", \"microsoft\", \"microsoft2005\"."); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerEnvironment.java new file mode 100644 index 000000000..20361e630 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerEnvironment.java @@ -0,0 +1,254 @@ +/* + * 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.auth.sqlserver; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.guacamole.auth.jdbc.security.PasswordPolicy; + +/** + * A SQLServer-specific implementation of JDBCEnvironment provides database + * properties specifically for SQLServer. + */ +public class SQLServerEnvironment extends JDBCEnvironment { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(SQLServerEnvironment.class); + + /** + * The default host to connect to, if SQLSERVER_HOSTNAME is not specified. + */ + private static final String DEFAULT_HOSTNAME = "localhost"; + + /** + * The default port to connect to, if SQLSERVER_PORT is not specified. + */ + private static final int DEFAULT_PORT = 1433; + + /** + * Whether a database user account is required by default for authentication + * to succeed. + */ + private static final boolean DEFAULT_USER_REQUIRED = false; + + /** + * The default value for the maximum number of connections to be + * allowed to the Guacamole server overall. + */ + private static final int DEFAULT_ABSOLUTE_MAX_CONNECTIONS = 0; + + /** + * The default value for the default maximum number of connections to be + * allowed per user to any one connection. + */ + private static final int DEFAULT_MAX_CONNECTIONS_PER_USER = 1; + + /** + * The default value for the default maximum number of connections to be + * allowed per user to any one connection group. + */ + private static final int DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER = 1; + + /** + * The default value for the default maximum number of connections to be + * allowed to any one connection. + */ + private static final int DEFAULT_MAX_CONNECTIONS = 0; + + /** + * The default value for the default maximum number of connections to be + * allowed to any one connection group. + */ + private static final int DEFAULT_MAX_GROUP_CONNECTIONS = 0; + + /** + * The default SQLServer driver to use. + */ + public static final SQLServerDriver SQLSERVER_DEFAULT_DRIVER = SQLServerDriver.MICROSOFT_2005; + + /** + * Constructs a new SQLServerEnvironment, providing access to SQLServer-specific + * configuration options. + * + * @throws GuacamoleException + * If an error occurs while setting up the underlying JDBCEnvironment + * or while parsing legacy SQLServer configuration options. + */ + public SQLServerEnvironment() throws GuacamoleException { + + // Init underlying JDBC environment + super(); + + } + + @Override + public boolean isUserRequired() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_USER_REQUIRED, + DEFAULT_USER_REQUIRED + ); + } + + @Override + public int getAbsoluteMaxConnections() throws GuacamoleException { + return getProperty(SQLServerGuacamoleProperties.SQLSERVER_ABSOLUTE_MAX_CONNECTIONS, + DEFAULT_ABSOLUTE_MAX_CONNECTIONS + ); + } + + @Override + public int getDefaultMaxConnections() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_DEFAULT_MAX_CONNECTIONS, + DEFAULT_MAX_CONNECTIONS + ); + } + + @Override + public int getDefaultMaxGroupConnections() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS, + DEFAULT_MAX_GROUP_CONNECTIONS + ); + } + + @Override + public int getDefaultMaxConnectionsPerUser() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_DEFAULT_MAX_CONNECTIONS_PER_USER, + DEFAULT_MAX_CONNECTIONS_PER_USER + ); + } + + @Override + public int getDefaultMaxGroupConnectionsPerUser() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER, + DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER + ); + } + + @Override + public PasswordPolicy getPasswordPolicy() { + return new SQLServerPasswordPolicy(this); + } + + /** + * Returns the hostname of the SQLServer server hosting the Guacamole + * authentication tables. If unspecified, this will be "localhost". + * + * @return + * The URL of the SQLServer server. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value. + */ + public String getSQLServerHostname() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_HOSTNAME, + DEFAULT_HOSTNAME + ); + } + + /** + * Returns the port number of the SQLServer server hosting the Guacamole + * authentication tables. If unspecified, this will be the default + * SQLServer port of 5432. + * + * @return + * The port number of the SQLServer server. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value. + */ + public int getSQLServerPort() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_PORT, + DEFAULT_PORT + ); + } + + /** + * Returns the name of the SQLServer database containing the Guacamole + * authentication tables. + * + * @return + * The name of the SQLServer database. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value, or if the + * value was not set, as this property is required. + */ + public String getSQLServerDatabase() throws GuacamoleException { + return getRequiredProperty(SQLServerGuacamoleProperties.SQLSERVER_DATABASE); + } + + /** + * Returns the username that should be used when authenticating with the + * SQLServer database containing the Guacamole authentication tables. + * + * @return + * The username for the SQLServer database. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value, or if the + * value was not set, as this property is required. + */ + public String getSQLServerUsername() throws GuacamoleException { + return getRequiredProperty(SQLServerGuacamoleProperties.SQLSERVER_USERNAME); + } + + /** + * Returns the password that should be used when authenticating with the + * SQLServer database containing the Guacamole authentication tables. + * + * @return + * The password for the SQLServer database. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value, or if the + * value was not set, as this property is required. + */ + public String getSQLServerPassword() throws GuacamoleException { + return getRequiredProperty(SQLServerGuacamoleProperties.SQLSERVER_PASSWORD); + } + + /** + * Returns which JDBC driver should be used to make the SQLServer/TDS connection. + * + * @return + * Which TDS-compatible JDBC driver should be used. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property value, or if the + * value was not set, as this property is required. + */ + public SQLServerDriver getSQLServerDriver() throws GuacamoleException { + return getProperty( + SQLServerGuacamoleProperties.SQLSERVER_DRIVER, + SQLSERVER_DEFAULT_DRIVER + ); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java new file mode 100644 index 000000000..45635996f --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java @@ -0,0 +1,185 @@ +/* + * 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.auth.sqlserver; + +import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; + +/** + * Properties used by the SQLServer Authentication plugin. + */ +public class SQLServerGuacamoleProperties { + + /** + * This class should not be instantiated. + */ + private SQLServerGuacamoleProperties() {} + + /** + * The URL of the SQLServer server hosting the Guacamole authentication tables. + */ + public static final StringGuacamoleProperty SQLSERVER_HOSTNAME = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-hostname"; } + + }; + + /** + * The port of the SQLServer server hosting the Guacamole authentication + * tables. + */ + public static final IntegerGuacamoleProperty SQLSERVER_PORT = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-port"; } + + }; + + /** + * The name of the SQLServer database containing the Guacamole + * authentication tables. + */ + public static final StringGuacamoleProperty SQLSERVER_DATABASE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-database"; } + + }; + + /** + * The username used to authenticate to the SQLServer database containing + * the Guacamole authentication tables. + */ + public static final StringGuacamoleProperty SQLSERVER_USERNAME = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-username"; } + + }; + + /** + * The password used to authenticate to the SQLServer database containing + * the Guacamole authentication tables. + */ + public static final StringGuacamoleProperty SQLSERVER_PASSWORD = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-password"; } + + }; + + /** + * Whether a user account within the database is required for authentication + * to succeed, even if the user has been authenticated via another + * authentication provider. + */ + public static final BooleanGuacamoleProperty + SQLSERVER_USER_REQUIRED = new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-required"; } + + }; + + /** + * The maximum number of concurrent connections to allow overall. Zero + * denotes unlimited. + */ + public static final IntegerGuacamoleProperty + SQLSERVER_ABSOLUTE_MAX_CONNECTIONS = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-absolute-max-connections"; } + + }; + + /** + * The maximum number of concurrent connections to allow to any one + * connection. Zero denotes unlimited. + */ + public static final IntegerGuacamoleProperty + SQLSERVER_DEFAULT_MAX_CONNECTIONS = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-default-max-connections"; } + + }; + + /** + * The maximum number of concurrent connections to allow to any one + * connection group. Zero denotes unlimited. + */ + public static final IntegerGuacamoleProperty + SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-default-max-group-connections"; } + + }; + + /** + * The maximum number of concurrent connections to allow to any one + * connection by an individual user. Zero denotes unlimited. + */ + public static final IntegerGuacamoleProperty + SQLSERVER_DEFAULT_MAX_CONNECTIONS_PER_USER = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-default-max-connections-per-user"; } + + }; + + /** + * The maximum number of concurrent connections to allow to any one + * connection group by an individual user. Zero denotes + * unlimited. + */ + public static final IntegerGuacamoleProperty + SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-default-max-group-connections-per-user"; } + + }; + + /** + * Which TDS-compatible JDBC driver should be used for the connection. + */ + public static final SQLServerDriverProperty + SQLSERVER_DRIVER = new SQLServerDriverProperty() { + + @Override + public String getName() { return "sqlserver-driver"; } + + }; + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerInjectorProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerInjectorProvider.java new file mode 100644 index 000000000..32d12f6e2 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerInjectorProvider.java @@ -0,0 +1,49 @@ +/* + * 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.auth.sqlserver; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.JDBCAuthenticationProviderModule; +import org.apache.guacamole.auth.jdbc.JDBCInjectorProvider; + +/** + * JDBCInjectorProvider implementation which configures Guice injections for + * connecting to a SQLServer database based on SQLServer-specific options + * provided via guacamole.properties. + */ +public class SQLServerInjectorProvider extends JDBCInjectorProvider { + + @Override + protected Injector create() throws GuacamoleException { + + // Get local environment + SQLServerEnvironment environment = new SQLServerEnvironment(); + + // Set up Guice injector + return Guice.createInjector( + new JDBCAuthenticationProviderModule(environment), + new SQLServerAuthenticationProviderModule(environment) + ); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerPasswordPolicy.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerPasswordPolicy.java new file mode 100644 index 000000000..f30b180bb --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerPasswordPolicy.java @@ -0,0 +1,194 @@ +/* + * 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.auth.sqlserver; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; +import org.apache.guacamole.auth.jdbc.security.PasswordPolicy; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; + +/** + * PasswordPolicy implementation which reads the details of the policy from + * SQLServer-specific properties in guacamole.properties. + */ +public class SQLServerPasswordPolicy implements PasswordPolicy { + + /** + * The property which specifies the minimum length required of all user + * passwords. By default, this will be zero. + */ + private static final IntegerGuacamoleProperty MIN_LENGTH = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-min-length"; } + + }; + + /** + * The property which specifies the minimum number of days which must + * elapse before a user may reset their password. If set to zero, the + * default, then this restriction does not apply. + */ + private static final IntegerGuacamoleProperty MIN_AGE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-min-age"; } + + }; + + /** + * The property which specifies the maximum number of days which may + * elapse before a user is required to reset their password. If set to zero, + * the default, then this restriction does not apply. + */ + private static final IntegerGuacamoleProperty MAX_AGE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-max-age"; } + + }; + + /** + * The property which specifies the number of previous passwords remembered + * for each user. If set to zero, the default, then this restriction does + * not apply. + */ + private static final IntegerGuacamoleProperty HISTORY_SIZE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-history-size"; } + + }; + + /** + * The property which specifies whether all user passwords must have at + * least one lowercase character and one uppercase character. By default, + * no such restriction is imposed. + */ + private static final BooleanGuacamoleProperty REQUIRE_MULTIPLE_CASE = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-require-multiple-case"; } + + }; + + /** + * The property which specifies whether all user passwords must have at + * least one numeric character (digit). By default, no such restriction is + * imposed. + */ + private static final BooleanGuacamoleProperty REQUIRE_DIGIT = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-require-digit"; } + + }; + + /** + * The property which specifies whether all user passwords must have at + * least one non-alphanumeric character (symbol). By default, no such + * restriction is imposed. + */ + private static final BooleanGuacamoleProperty REQUIRE_SYMBOL = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-require-symbol"; } + + }; + + /** + * The property which specifies whether users are prohibited from including + * their own username in their password. By default, no such restriction is + * imposed. + */ + private static final BooleanGuacamoleProperty PROHIBIT_USERNAME = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-user-password-prohibit-username"; } + + }; + + /** + * The Guacamole server environment. + */ + private final JDBCEnvironment environment; + + /** + * Creates a new SQLServerPasswordPolicy which reads the details of the + * policy from the properties exposed by the given environment. + * + * @param environment + * The environment from which password policy properties should be + * read. + */ + public SQLServerPasswordPolicy(JDBCEnvironment environment) { + this.environment = environment; + } + + @Override + public int getMinimumLength() throws GuacamoleException { + return environment.getProperty(MIN_LENGTH, 0); + } + + @Override + public int getMinimumAge() throws GuacamoleException { + return environment.getProperty(MIN_AGE, 0); + } + + @Override + public int getMaximumAge() throws GuacamoleException { + return environment.getProperty(MAX_AGE, 0); + } + + @Override + public int getHistorySize() throws GuacamoleException { + return environment.getProperty(HISTORY_SIZE, 0); + } + + @Override + public boolean isMultipleCaseRequired() throws GuacamoleException { + return environment.getProperty(REQUIRE_MULTIPLE_CASE, false); + } + + @Override + public boolean isNumericRequired() throws GuacamoleException { + return environment.getProperty(REQUIRE_DIGIT, false); + } + + @Override + public boolean isNonAlphanumericRequired() throws GuacamoleException { + return environment.getProperty(REQUIRE_SYMBOL, false); + } + + @Override + public boolean isUsernameProhibited() throws GuacamoleException { + return environment.getProperty(PROHIBIT_USERNAME, false); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerSharedAuthenticationProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerSharedAuthenticationProvider.java new file mode 100644 index 000000000..0a3c8d31f --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerSharedAuthenticationProvider.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.auth.sqlserver; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.InjectedAuthenticationProvider; +import org.apache.guacamole.auth.jdbc.sharing.SharedAuthenticationProviderService; + +/** + * Provides a implementation of AuthenticationProvider which interacts with the + * SQLServer AuthenticationProvider, accepting share keys as credentials and + * providing access to the shared connections. + */ +public class SQLServerSharedAuthenticationProvider extends InjectedAuthenticationProvider { + + /** + * Creates a new SQLServerSharedAuthenticationProvider that provides access + * to shared connections exposed by the SQLServerAuthenticationProvider. + * + * @throws GuacamoleException + * If a required property is missing, or an error occurs while parsing + * a property. + */ + public SQLServerSharedAuthenticationProvider() throws GuacamoleException { + super(new SQLServerInjectorProvider(), SharedAuthenticationProviderService.class); + } + + @Override + public String getIdentifier() { + return "sqlserver-shared"; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/package-info.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/package-info.java new file mode 100644 index 000000000..7bbe1b2e3 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * The SQLServer authentication provider. + */ +package org.apache.guacamole.auth.sqlserver; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..ee61ab578 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/guac-manifest.json @@ -0,0 +1,28 @@ +{ + + "guacamoleVersion" : "0.9.13-incubating", + + "name" : "SQLServer Authentication", + "namespace" : "guac-sqlserver", + + "authProviders" : [ + "org.apache.guacamole.auth.sqlserver.SQLServerAuthenticationProvider", + "org.apache.guacamole.auth.sqlserver.SQLServerSharedAuthenticationProvider" + ], + + "css" : [ + "styles/jdbc.css" + ], + + "html" : [ + "html/shared-connection.html" + ], + + "translations" : [ + "translations/en.json", + "translations/fr.json", + "translations/ru.json" + ] + +} + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml new file mode 100644 index 000000000..3e6819f06 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_connection] + WHERE connection_id = #{identifier,jdbcType=INTEGER} + + + + + + INSERT INTO [guacamole_connection] ( + connection_name, + parent_id, + protocol, + max_connections, + max_connections_per_user, + proxy_hostname, + proxy_port, + proxy_encryption_method, + connection_weight, + failover_only + ) + VALUES ( + #{object.name,jdbcType=VARCHAR}, + #{object.parentIdentifier,jdbcType=INTEGER}, + #{object.protocol,jdbcType=VARCHAR}, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER}, + #{object.proxyHostname,jdbcType=VARCHAR}, + #{object.proxyPort,jdbcType=INTEGER}, + #{object.proxyEncryptionMethod,jdbcType=VARCHAR}, + #{object.connectionWeight,jdbcType=INTEGER}, + #{object.failoverOnly,jdbcType=INTEGER} + ) + + + + + + UPDATE [guacamole_connection] + SET connection_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=INTEGER}, + protocol = #{object.protocol,jdbcType=VARCHAR}, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER}, + proxy_hostname = #{object.proxyHostname,jdbcType=VARCHAR}, + proxy_port = #{object.proxyPort,jdbcType=INTEGER}, + proxy_encryption_method = #{object.proxyEncryptionMethod,jdbcType=VARCHAR}, + connection_weight = #{object.connectionWeight,jdbcType=INTEGER}, + failover_only = #{object.failoverOnly,jdbcType=INTEGER} + WHERE connection_id = #{object.objectID,jdbcType=INTEGER} + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionParameterMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionParameterMapper.xml new file mode 100644 index 000000000..08b337ea9 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionParameterMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_connection_parameter] + WHERE connection_id = #{identifier,jdbcType=INTEGER} + + + + + + INSERT INTO [guacamole_connection_parameter] ( + connection_id, + parameter_name, + parameter_value + ) + VALUES + + (#{parameter.connectionIdentifier,jdbcType=INTEGER}, + #{parameter.name,jdbcType=VARCHAR}, + #{parameter.value,jdbcType=VARCHAR}) + + + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml new file mode 100644 index 000000000..d7ae41c4b --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO [guacamole_connection_history] ( + connection_id, + connection_name, + remote_host, + sharing_profile_id, + sharing_profile_name, + user_id, + username, + start_date, + end_date + ) + VALUES ( + #{record.connectionIdentifier,jdbcType=INTEGER}, + #{record.connectionName,jdbcType=VARCHAR}, + #{record.remoteHost,jdbcType=VARCHAR}, + #{record.sharingProfileIdentifier,jdbcType=INTEGER}, + #{record.sharingProfileName,jdbcType=VARCHAR}, + (SELECT user_id FROM [guacamole_user] + WHERE username = #{record.username,jdbcType=VARCHAR}), + #{record.username,jdbcType=VARCHAR}, + #{record.startDate,jdbcType=TIMESTAMP}, + #{record.endDate,jdbcType=TIMESTAMP} + ) + + + + + + + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml new file mode 100644 index 000000000..452c0a81e --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_connection_group] + WHERE connection_group_id = #{identifier,jdbcType=INTEGER} + + + + + + INSERT INTO [guacamole_connection_group] ( + connection_group_name, + parent_id, + type, + max_connections, + max_connections_per_user, + enable_session_affinity + ) + VALUES ( + #{object.name,jdbcType=VARCHAR}, + #{object.parentIdentifier,jdbcType=INTEGER}, + #{object.type,jdbcType=VARCHAR}, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER}, + #{object.sessionAffinityEnabled,jdbcType=INTEGER} + ) + + + + + + UPDATE [guacamole_connection_group] + SET connection_group_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=INTEGER}, + type = #{object.type,jdbcType=VARCHAR}, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER}, + enable_session_affinity = #{object.sessionAffinityEnabled,jdbcType=INTEGER} + WHERE connection_group_id = #{object.objectID,jdbcType=INTEGER} + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionGroupPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionGroupPermissionMapper.xml new file mode 100644 index 000000000..3cc0988c2 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionGroupPermissionMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_connection_group_permission] + WHERE (user_id, permission, connection_group_id) IN + + (#{permission.userID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}, + #{permission.objectIdentifier,jdbcType=INTEGER}) + + + + + + + + INSERT INTO [guacamole_connection_group_permission] ( + user_id, + permission, + connection_group_id + ) + SELECT DISTINCT + permissions.user_id, + permissions.permission, + permissions.connection_group_id + FROM + + SELECT #{permission.userID,jdbcType=INTEGER} AS user_id, + #{permission.type,jdbcType=VARCHAR} AS permission, + #{permission.objectIdentifier,jdbcType=INTEGER} AS connection_group_id + + AS permissions + WHERE NOT EXISTS (SELECT 1 FROM [guacamole_connection_group_permission] + WHERE [guacamole_connection_group_permission].user_id = permissions.user_id AND + [guacamole_connection_group_permission].permission = permissions.permission AND + [guacamole_connection_group_permission].connection_group_id = permissions.connection_group_id + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionPermissionMapper.xml new file mode 100644 index 000000000..aaa555aee --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/ConnectionPermissionMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_connection_permission] + WHERE + + (user_id = #{permission.userID,jdbcType=INTEGER} AND + permission = #{permission.type,jdbcType=VARCHAR} AND + connection_id = #{permission.objectIdentifier,jdbcType=INTEGER}) + + + + + + + + INSERT INTO [guacamole_connection_permission] ( + user_id, + permission, + connection_id + ) + SELECT DISTINCT + permissions.user_id, + permissions.permission, + permissions.connection_id + FROM + + SELECT #{permission.userID,jdbcType=INTEGER} AS user_id, + #{permission.type,jdbcType=VARCHAR} AS permission, + #{permission.objectIdentifier,jdbcType=INTEGER} AS connection_id + + AS permissions + WHERE NOT EXISTS ( SELECT 1 FROM [guacamole_connection_permission] + WHERE [guacamole_connection_permission].user_id = permissions.user_id AND + [guacamole_connection_permission].permission = permissions.permission AND + [guacamole_connection_permission].connection_id = permissions.connection_id + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SharingProfilePermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SharingProfilePermissionMapper.xml new file mode 100644 index 000000000..ab40d2ade --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SharingProfilePermissionMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_sharing_profile_permission] + WHERE + + (user_id = #{permission.userID,jdbcType=INTEGER} AND + permission = #{permission.type,jdbcType=VARCHAR} AND + sharing_profile_id = #{permission.objectIdentifier,jdbcType=INTEGER}) + + + + + + + + INSERT INTO [guacamole_sharing_profile_permission] ( + user_id, + permission, + sharing_profile_id + ) + SELECT DISTINCT + permissions.user_id, + permissions.permission, + permissions.sharing_profile_id + FROM + + SELECT #{permission.userID,jdbcType=INTEGER} AS user_id, + #{permission.type,jdbcType=VARCHAR} AS permission, + #{permission.objectIdentifier,jdbcType=INTEGER} AS sharing_profile_id + + AS permissions + WHERE NOT EXISTS (SELECT 1 FROM [guacamole_sharing_profile_permission] + WHERE [guacamole_sharing_profile_permission].user_id = permissions.user_id + AND [guacamole_sharing_profile_permission].permission = permissions.permission + AND [guacamole_sharing_profile_permission].sharing_profile_id = permissions.sharing_profile_id + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SystemPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SystemPermissionMapper.xml new file mode 100644 index 000000000..663b94ec1 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/SystemPermissionMapper.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_system_permission] + WHERE + + (user_id = #{permission.userID,jdbcType=INTEGER} + AND permission = #{permission.type,jdbcType=VARCHAR}) + + + + + + + + INSERT INTO [guacamole_system_permission] ( + user_id, + permission + ) + SELECT DISTINCT + permissions.user_id, + permissions.permission + FROM + + SELECT #{permission.userID,jdbcType=INTEGER} AS user_id, + #{permission.type,jdbcType=VARCHAR} AS permission + + AS permissions + WHERE NOT EXISTS (SELECT 1 FROM [guacamole_system_permission] + WHERE [guacamole_system_permission].user_id = permissions.user_id + AND [guacamole_system_permission].permission = permissions.permission + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml new file mode 100644 index 000000000..453777d0c --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_user_permission] + USING [guacamole_user] affected + WHERE + [guacamole_user_permission].affected_user_id = affected.user_id + AND ([guacamole_user_permission].user_id, permission, affected.username) IN + + (#{permission.userID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}, + #{permission.objectIdentifier,jdbcType=INTEGER}) + + + + + + + + INSERT INTO [guacamole_user_permission] ( + user_id, + permission, + affected_user_id + ) + SELECT DISTINCT + permissions.user_id, + permissions.permission, + [guacamole_user].user_id + FROM + + SELECT #{permission.userID,jdbcType=INTEGER} AS user_id, + #{permission.type,jdbcType=VARCHAR} AS permission, + #{permission.objectIdentifier,jdbcType=INTEGER} AS username + + AS permissions + JOIN [guacamole_user] ON [guacamole_user].username = permissions.username + WHERE NOT EXISTS (SELECT 1 FROM [guacamole_user_permission] + WHERE [guacamole_user_permission].user_id = permissions.user_id + AND [guacamole_user_permission].permission = permissions.permission + AND [guacamole_user_permission].affected_user_id = [guacamole_user].user_id + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileMapper.xml new file mode 100644 index 000000000..3b4ba0980 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileMapper.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_sharing_profile] + WHERE sharing_profile_id = #{identifier,jdbcType=INTEGER} + + + + + + INSERT INTO [guacamole_sharing_profile] ( + sharing_profile_name, + primary_connection_id + ) + VALUES ( + #{object.name,jdbcType=VARCHAR}, + #{object.parentIdentifier,jdbcType=INTEGER} + ) + + + + + + UPDATE [guacamole_sharing_profile] + SET sharing_profile_name = #{object.name,jdbcType=VARCHAR}, + primary_connection_id = #{object.parentIdentifier,jdbcType=INTEGER} + WHERE sharing_profile_id = #{object.objectID,jdbcType=INTEGER} + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileParameterMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileParameterMapper.xml new file mode 100644 index 000000000..a4327b4cd --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/sharingprofile/SharingProfileParameterMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_sharing_profile_parameter] + WHERE sharing_profile_id = #{identifier,jdbcType=INTEGER} + + + + + + INSERT INTO [guacamole_sharing_profile_parameter] ( + sharing_profile_id, + parameter_name, + parameter_value + ) + VALUES + + (#{parameter.sharingProfileIdentifier,jdbcType=INTEGER}, + #{parameter.name,jdbcType=VARCHAR}, + #{parameter.value,jdbcType=VARCHAR}) + + + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml new file mode 100644 index 000000000..562a7471f --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + INSERT INTO [guacamole_user_password_history] ( + user_id, + password_hash, + password_salt, + password_date + ) + VALUES ( + #{record.userID,jdbcType=INTEGER}, + #{record.passwordHash,jdbcType=BINARY}, + #{record.passwordSalt,jdbcType=BINARY}, + #{record.passwordDate,jdbcType=TIMESTAMP} + ); + + DELETE FROM [guacamole_user_password_history] + WHERE password_history_id IN ( + SELECT password_history_id + FROM [guacamole_user_password_history] + WHERE user_id = #{record.userID,jdbcType=INTEGER} + ORDER BY password_date DESC + OFFSET #{maxHistorySize} + ); + + + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml new file mode 100644 index 000000000..6df6cf26d --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM [guacamole_user] + WHERE username = #{identifier,jdbcType=VARCHAR} + + + + + + INSERT INTO [guacamole_user] ( + username, + password_hash, + password_salt, + password_date, + disabled, + expired, + access_window_start, + access_window_end, + valid_from, + valid_until, + timezone, + full_name, + email_address, + organization, + organizational_role + ) + VALUES ( + #{object.identifier,jdbcType=VARCHAR}, + #{object.passwordHash,jdbcType=BINARY}, + #{object.passwordSalt,jdbcType=BINARY}, + #{object.passwordDate,jdbcType=TIMESTAMP}, + #{object.disabled,jdbcType=INTEGER}, + #{object.expired,jdbcType=INTEGER}, + #{object.accessWindowStart,jdbcType=TIME}, + #{object.accessWindowEnd,jdbcType=TIME}, + #{object.validFrom,jdbcType=DATE}, + #{object.validUntil,jdbcType=DATE}, + #{object.timeZone,jdbcType=VARCHAR}, + #{object.fullName,jdbcType=VARCHAR}, + #{object.emailAddress,jdbcType=VARCHAR}, + #{object.organization,jdbcType=VARCHAR}, + #{object.organizationalRole,jdbcType=VARCHAR} + ) + + + + + + UPDATE [guacamole_user] + SET password_hash = #{object.passwordHash,jdbcType=BINARY}, + password_salt = #{object.passwordSalt,jdbcType=BINARY}, + password_date = #{object.passwordDate,jdbcType=TIMESTAMP}, + disabled = #{object.disabled,jdbcType=INTEGER}, + expired = #{object.expired,jdbcType=INTEGER}, + access_window_start = #{object.accessWindowStart,jdbcType=TIME}, + access_window_end = #{object.accessWindowEnd,jdbcType=TIME}, + valid_from = #{object.validFrom,jdbcType=DATE}, + valid_until = #{object.validUntil,jdbcType=DATE}, + timezone = #{object.timeZone,jdbcType=VARCHAR}, + full_name = #{object.fullName,jdbcType=VARCHAR}, + email_address = #{object.emailAddress,jdbcType=VARCHAR}, + organization = #{object.organization,jdbcType=VARCHAR}, + organizational_role = #{object.organizationalRole,jdbcType=VARCHAR} + WHERE user_id = #{object.objectID,jdbcType=VARCHAR} + + + diff --git a/extensions/guacamole-auth-jdbc/pom.xml b/extensions/guacamole-auth-jdbc/pom.xml index 2a5ef5b2b..7869c8650 100644 --- a/extensions/guacamole-auth-jdbc/pom.xml +++ b/extensions/guacamole-auth-jdbc/pom.xml @@ -70,6 +70,7 @@ modules/guacamole-auth-jdbc-mysql modules/guacamole-auth-jdbc-postgresql + modules/guacamole-auth-jdbc-sqlserver diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java index 1db4ee915..f9c4a7d2c 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java @@ -103,5 +103,10 @@ public class LDAPAuthenticationProvider implements AuthenticationProvider { return context; } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java index d256ebb3b..eea1a95ac 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java @@ -123,7 +123,7 @@ public class ConnectionService { // Build token filter containing credential tokens TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user.getCredentials()); + StandardTokens.addStandardTokens(tokenFilter, user); // Produce connections for each readable configuration Map connections = new HashMap(); diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java index 2c4703c70..5e19dcaa1 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java @@ -28,17 +28,19 @@ import org.apache.guacamole.auth.ldap.connection.ConnectionService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.ActivityRecord; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; -import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.ConnectionRecord; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.SharingProfile; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.simple.SimpleActivityRecordSet; import org.apache.guacamole.net.auth.simple.SimpleConnectionGroup; import org.apache.guacamole.net.auth.simple.SimpleConnectionGroupDirectory; -import org.apache.guacamole.net.auth.simple.SimpleConnectionRecordSet; import org.apache.guacamole.net.auth.simple.SimpleDirectory; import org.apache.guacamole.net.auth.simple.SimpleUser; import org.slf4j.Logger; @@ -204,9 +206,15 @@ public class UserContext implements org.apache.guacamole.net.auth.UserContext { } @Override - public ConnectionRecordSet getConnectionHistory() + public ActivityRecordSet getConnectionHistory() throws GuacamoleException { - return new SimpleConnectionRecordSet(); + return new SimpleActivityRecordSet(); + } + + @Override + public ActivityRecordSet getUserHistory() + throws GuacamoleException { + return new SimpleActivityRecordSet(); } @Override @@ -229,4 +237,9 @@ public class UserContext implements org.apache.guacamole.net.auth.UserContext { return Collections.emptyList(); } + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/extensions/guacamole-auth-openid/.gitignore b/extensions/guacamole-auth-openid/.gitignore new file mode 100644 index 000000000..30eb48707 --- /dev/null +++ b/extensions/guacamole-auth-openid/.gitignore @@ -0,0 +1,3 @@ +*~ +target/ +src/main/resources/generated/ diff --git a/extensions/guacamole-auth-openid/pom.xml b/extensions/guacamole-auth-openid/pom.xml new file mode 100644 index 000000000..4076998f1 --- /dev/null +++ b/extensions/guacamole-auth-openid/pom.xml @@ -0,0 +1,252 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-openid + jar + 0.9.13-incubating + guacamole-auth-openid + http://guacamole.incubator.apache.org/ + + + UTF-8 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.6 + 1.6 + + -Xlint:all + -Werror + + true + + + + + + com.keithbranton.mojo + angular-maven-plugin + 0.3.2 + + + generate-resources + + html2js + + + + + ${basedir}/src/main/resources + **/*.html + ${basedir}/src/main/resources/generated/templates-main/templates.js + app/ext/guac-openid + + + + + + com.samaxes.maven + minify-maven-plugin + 1.7.5 + + + default-cli + + UTF-8 + + ${basedir}/src/main/resources + ${project.build.directory}/classes + + / + / + openid.css + + + license.txt + + + + **/*.css + + + / + / + openid.js + + + license.txt + + + + **/*.js + + + + + **/*.test.js + + CLOSURE + + + + OFF + OFF + + + + + minify + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack-dependencies + + + runtime + ${project.build.directory}/classes + + + + + + + + maven-assembly-plugin + 2.5.3 + + ${project.artifactId}-${project.version} + false + + src/main/assembly/dist.xml + + + + + make-dist-archive + package + + single + + + + + + + + org.apache.rat + apache-rat-plugin + 0.12 + + + + **/*.json + src/licenses/**/* + src/main/resources/templates/*.html + + + + + + + validate + validate + + check + + + + + + + + + + + + + + org.apache.guacamole + guacamole-ext + 0.9.13-incubating + provided + + + + + org.bitbucket.b_c + jose4j + 0.5.5 + + + org.slf4j + slf4j-api + + + + + + + com.google.inject + guice + 3.0 + + + com.google.inject.extensions + guice-multibindings + 3.0 + + + + + javax.servlet + servlet-api + 2.5 + provided + + + + + diff --git a/extensions/guacamole-auth-openid/src/licenses/DISCLAIMER b/extensions/guacamole-auth-openid/src/licenses/DISCLAIMER new file mode 100644 index 000000000..1a9c3be8d --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/DISCLAIMER @@ -0,0 +1,7 @@ +Apache Guacamole is an effort undergoing incubation at The Apache Software +Foundation (ASF). Incubation is required of all newly accepted projects until a +further review indicates that the infrastructure, communications, and decision +making process have stabilized in a manner consistent with other successful ASF +projects. While incubation status is not necessarily a reflection of the +completeness or stability of the code, it does indicate that the project has +yet to be fully endorsed by the ASF. diff --git a/extensions/guacamole-auth-openid/src/licenses/LICENSE b/extensions/guacamole-auth-openid/src/licenses/LICENSE new file mode 100644 index 000000000..f95ed290f --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/LICENSE @@ -0,0 +1,248 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + + +============================================================================== + +APACHE GUACAMOLE SUBCOMPONENTS + +Apache Guacamole includes a number of subcomponents with separate copyright +notices and license terms. Your use of these subcomponents is subject to the +terms and conditions of the following licenses. + + +AOP Alliance (http://aopalliance.sourceforge.net/) +-------------------------------------------------- + + Version: 1.0 + From: 'AOP Alliance' (http://aopalliance.sourceforge.net/members.html) + License(s): + Public Domain (bundled/aopalliance-1.0/LICENSE) + + +Google Guice (https://github.com/google/guice) +---------------------------------------------- + + Version: 3.0 + From: 'Google Inc.' (http://www.google.com/) + License(s): + Apache v2.0 (bundled/guice-3.0/COPYING) + + +jose.4.j (https://bitbucket.org/b_c/jose4j/) +-------------------------------------------- + + Version: 0.5.5 + From: 'Brian Campbell' (https://bitbucket.org/b_c/) + License(s): + Apache v2.0 (bundled/jose4j-0.5.5/LICENSE) + + +JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/) +---------------------------------------------------------------------------- + + Version: 1 + From: 'JSR-330 Expert Group' (https://jcp.org/en/jsr/detail?id=330) + License(s): + Apache v2.0 (bundled/javax.inject-1/LICENSE-2.0.txt) + diff --git a/extensions/guacamole-auth-openid/src/licenses/NOTICE b/extensions/guacamole-auth-openid/src/licenses/NOTICE new file mode 100644 index 000000000..2ef7e548b --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/NOTICE @@ -0,0 +1,5 @@ +Apache Guacamole +Copyright 2016 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/README b/extensions/guacamole-auth-openid/src/licenses/bundled/README new file mode 100644 index 000000000..47ba19db0 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/README @@ -0,0 +1,4 @@ +Apache Guacamole includes a number of subcomponents with separate copyright +notices and license terms. Your use of these subcomponents is subject to the +terms and conditions of their respective licenses, included within this +directory for reference. diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/aopalliance-1.0/LICENSE b/extensions/guacamole-auth-openid/src/licenses/bundled/aopalliance-1.0/LICENSE new file mode 100644 index 000000000..8e0e3786b --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/aopalliance-1.0/LICENSE @@ -0,0 +1,4 @@ +From http://aopalliance.sourceforge.net/: + + LICENCE: all the source code provided by AOP Alliance is Public Domain. + diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/guice-3.0/COPYING b/extensions/guacamole-auth-openid/src/licenses/bundled/guice-3.0/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/guice-3.0/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt b/extensions/guacamole-auth-openid/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/LICENSE b/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/NOTICE.txt b/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/NOTICE.txt new file mode 100644 index 000000000..46498244b --- /dev/null +++ b/extensions/guacamole-auth-openid/src/licenses/bundled/jose4j-0.5.5/NOTICE.txt @@ -0,0 +1,14 @@ +jose4j +Copyright 2012-2015 Brian Campbell + +EcdsaUsingShaAlgorithm contains code for converting the concatenated +R & S values of the signature to and from DER, which was originally +derived from the Apache Santuario XML Security library's SignatureECDSA +implementation. http://santuario.apache.org/ + +The Base64 implementation in this software was derived from the +Apache Commons Codec project. http://commons.apache.org/proper/commons-codec/ + +JSON processing in this software was derived from the JSON.simple toolkit. +https://code.google.com/p/json-simple/ + diff --git a/extensions/guacamole-auth-openid/src/main/assembly/dist.xml b/extensions/guacamole-auth-openid/src/main/assembly/dist.xml new file mode 100644 index 000000000..b89fd534c --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/assembly/dist.xml @@ -0,0 +1,53 @@ + + + + + dist + ${project.artifactId}-${project.version} + + + + tar.gz + + + + + + + + + src/licenses + + + + + target + + + *.jar + + + + + + diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java new file mode 100644 index 000000000..47d99fff6 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java @@ -0,0 +1,132 @@ +/* + * 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.auth.openid; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.util.Arrays; +import javax.servlet.http.HttpServletRequest; +import org.apache.guacamole.auth.openid.conf.ConfigurationService; +import org.apache.guacamole.auth.openid.form.TokenField; +import org.apache.guacamole.auth.openid.token.NonceService; +import org.apache.guacamole.auth.openid.token.TokenValidationService; +import org.apache.guacamole.auth.openid.user.AuthenticatedUser; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.form.Field; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.credentials.CredentialsInfo; +import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service providing convenience functions for the OpenID AuthenticationProvider + * implementation. + */ +public class AuthenticationProviderService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); + + /** + * Service for retrieving OpenID configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Service for validating and generating unique nonce values. + */ + @Inject + private NonceService nonceService; + + /** + * Service for validating received ID tokens. + */ + @Inject + private TokenValidationService tokenService; + + /** + * Provider for AuthenticatedUser objects. + */ + @Inject + private Provider authenticatedUserProvider; + + /** + * Returns an AuthenticatedUser representing the user authenticated by the + * given credentials. + * + * @param credentials + * The credentials to use for authentication. + * + * @return + * An AuthenticatedUser representing the user authenticated by the + * given credentials. + * + * @throws GuacamoleException + * If an error occurs while authenticating the user, or if access is + * denied. + */ + public AuthenticatedUser authenticateUser(Credentials credentials) + throws GuacamoleException { + + String username = null; + + // Validate OpenID token in request, if present, and derive username + HttpServletRequest request = credentials.getRequest(); + if (request != null) { + String token = request.getParameter(TokenField.PARAMETER_NAME); + if (token != null) + username = tokenService.processUsername(token); + } + + // If the username was successfully retrieved from the token, produce + // authenticated user + if (username != null) { + + // Create corresponding authenticated user + AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(username, credentials); + return authenticatedUser; + + } + + // Request OpenID token + throw new GuacamoleInvalidCredentialsException("Invalid login.", + new CredentialsInfo(Arrays.asList(new Field[] { + + // OpenID-specific token (will automatically redirect the user + // to the authorization page via JavaScript) + new TokenField( + confService.getAuthorizationEndpoint(), + confService.getScope(), + confService.getClientID(), + confService.getRedirectURI(), + nonceService.generate(confService.getMaxNonceValidity() * 60000L) + ) + + })) + ); + + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java new file mode 100644 index 000000000..57b483183 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java @@ -0,0 +1,115 @@ +/* + * 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.auth.openid; + +import com.google.inject.Guice; +import com.google.inject.Injector; +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; + +/** + * Guacamole authentication backend which authenticates users using an + * arbitrary external system implementing OpenID. No storage for connections is + * provided - only authentication. Storage must be provided by some other + * extension. + */ +public class OpenIDAuthenticationProvider implements AuthenticationProvider { + + /** + * Injector which will manage the object graph of this authentication + * provider. + */ + private final Injector injector; + + /** + * Creates a new OpenIDAuthenticationProvider that authenticates users + * against an OpenID service. + * + * @throws GuacamoleException + * If a required property is missing, or an error occurs while parsing + * a property. + */ + public OpenIDAuthenticationProvider() throws GuacamoleException { + + // Set up Guice injector. + injector = Guice.createInjector( + new OpenIDAuthenticationProviderModule(this) + ); + + } + + @Override + public String getIdentifier() { + return "openid"; + } + + @Override + public Object getResource() throws GuacamoleException { + return null; + } + + @Override + public AuthenticatedUser authenticateUser(Credentials credentials) + throws GuacamoleException { + + // Attempt to authenticate user with given credentials + AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class); + return authProviderService.authenticateUser(credentials); + + } + + @Override + public AuthenticatedUser updateAuthenticatedUser( + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + + // No update necessary + return authenticatedUser; + + } + + @Override + public UserContext getUserContext(AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // No associated data whatsoever + return null; + + } + + @Override + public UserContext updateUserContext(UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + + // No update necessary + return context; + + } + + @Override + public void shutdown() { + // Do nothing + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java new file mode 100644 index 000000000..17510cbe5 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java @@ -0,0 +1,83 @@ +/* + * 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.auth.openid; + +import com.google.inject.AbstractModule; +import org.apache.guacamole.auth.openid.conf.ConfigurationService; +import org.apache.guacamole.auth.openid.token.NonceService; +import org.apache.guacamole.auth.openid.token.TokenValidationService; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.environment.LocalEnvironment; +import org.apache.guacamole.net.auth.AuthenticationProvider; + +/** + * Guice module which configures openid-specific injections. + */ +public class OpenIDAuthenticationProviderModule extends AbstractModule { + + /** + * Guacamole server environment. + */ + private final Environment environment; + + /** + * A reference to the OpenIDAuthenticationProvider on behalf of which this + * module has configured injection. + */ + private final AuthenticationProvider authProvider; + + /** + * Creates a new OpenID authentication provider module which configures + * injection for the OpenIDAuthenticationProvider. + * + * @param authProvider + * The AuthenticationProvider for which injection is being configured. + * + * @throws GuacamoleException + * If an error occurs while retrieving the Guacamole server + * environment. + */ + public OpenIDAuthenticationProviderModule(AuthenticationProvider authProvider) + throws GuacamoleException { + + // Get local environment + this.environment = new LocalEnvironment(); + + // Store associated auth provider + this.authProvider = authProvider; + + } + + @Override + protected void configure() { + + // Bind core implementations of guacamole-ext classes + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(Environment.class).toInstance(environment); + + // Bind openid-specific services + bind(ConfigurationService.class); + bind(NonceService.class); + bind(TokenValidationService.class); + + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java new file mode 100644 index 000000000..c742d8991 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java @@ -0,0 +1,362 @@ +/* + * 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.auth.openid.conf; + +import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; + +/** + * Service for retrieving configuration information regarding the OpenID + * service. + */ +public class ConfigurationService { + + /** + * The default claim type to use to retrieve an authenticated user's + * username. + */ + private static final String DEFAULT_USERNAME_CLAIM_TYPE = "email"; + + /** + * The default space-separated list of OpenID scopes to request. + */ + private static final String DEFAULT_SCOPE = "openid email profile"; + + /** + * The default amount of clock skew tolerated for timestamp comparisons + * between the Guacamole server and OpenID service clocks, in seconds. + */ + private static final int DEFAULT_ALLOWED_CLOCK_SKEW = 30; + + /** + * The default maximum amount of time that an OpenID token should remain + * valid, in minutes. + */ + private static final int DEFAULT_MAX_TOKEN_VALIDITY = 300; + + /** + * The default maximum amount of time that a nonce generated by the + * Guacamole server should remain valid, in minutes. + */ + private static final int DEFAULT_MAX_NONCE_VALIDITY = 10; + + /** + * The authorization endpoint (URI) of the OpenID service. + */ + private static final StringGuacamoleProperty OPENID_AUTHORIZATION_ENDPOINT = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-authorization-endpoint"; } + + }; + + /** + * The endpoint (URI) of the JWKS service which defines how received ID + * tokens (JWTs) shall be validated. + */ + private static final StringGuacamoleProperty OPENID_JWKS_ENDPOINT = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-jwks-endpoint"; } + + }; + + /** + * The issuer to expect for all received ID tokens. + */ + private static final StringGuacamoleProperty OPENID_ISSUER = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-issuer"; } + + }; + + /** + * The claim type which contains the authenticated user's username within + * any valid JWT. + */ + private static final StringGuacamoleProperty OPENID_USERNAME_CLAIM_TYPE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-username-claim-type"; } + + }; + + /** + * The space-separated list of OpenID scopes to request. + */ + private static final StringGuacamoleProperty OPENID_SCOPE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-scope"; } + + }; + + /** + * The amount of clock skew tolerated for timestamp comparisons between the + * Guacamole server and OpenID service clocks, in seconds. + */ + private static final IntegerGuacamoleProperty OPENID_ALLOWED_CLOCK_SKEW = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "openid-allowed-clock-skew"; } + + }; + + /** + * The maximum amount of time that an OpenID token should remain valid, in + * minutes. + */ + private static final IntegerGuacamoleProperty OPENID_MAX_TOKEN_VALIDITY = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "openid-max-token-validity"; } + + }; + + /** + * The maximum amount of time that a nonce generated by the Guacamole server + * should remain valid, in minutes. As each OpenID request has a unique + * nonce value, this imposes an upper limit on the amount of time any + * particular OpenID request can result in successful authentication within + * Guacamole. + */ + private static final IntegerGuacamoleProperty OPENID_MAX_NONCE_VALIDITY = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "openid-max-nonce-validity"; } + + }; + + /** + * OpenID client ID which should be submitted to the OpenID service when + * necessary. This value is typically provided by the OpenID service when + * OpenID credentials are generated for your application. + */ + private static final StringGuacamoleProperty OPENID_CLIENT_ID = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-client-id"; } + + }; + + /** + * The URI that the OpenID service should redirect to after the + * authentication process is complete. This must be the full URL that a + * user would enter into their browser to access Guacamole. + */ + private static final StringGuacamoleProperty OPENID_REDIRECT_URI = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "openid-redirect-uri"; } + + }; + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * Returns the authorization endpoint (URI) of the OpenID service as + * configured with guacamole.properties. + * + * @return + * The authorization endpoint of the OpenID service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the authorization + * endpoint property is missing. + */ + public String getAuthorizationEndpoint() throws GuacamoleException { + return environment.getRequiredProperty(OPENID_AUTHORIZATION_ENDPOINT); + } + + /** + * Returns the OpenID client ID which should be submitted to the OpenID + * service when necessary, as configured with guacamole.properties. This + * value is typically provided by the OpenID service when OpenID credentials + * are generated for your application. + * + * @return + * The client ID to use when communicating with the OpenID service, + * as configured with guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the client ID + * property is missing. + */ + public String getClientID() throws GuacamoleException { + return environment.getRequiredProperty(OPENID_CLIENT_ID); + } + + /** + * Returns the URI that the OpenID service should redirect to after + * the authentication process is complete, as configured with + * guacamole.properties. This must be the full URL that a user would enter + * into their browser to access Guacamole. + * + * @return + * The client secret to use when communicating with the OpenID service, + * as configured with guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the redirect URI + * property is missing. + */ + public String getRedirectURI() throws GuacamoleException { + return environment.getRequiredProperty(OPENID_REDIRECT_URI); + } + + /** + * Returns the issuer to expect for all received ID tokens, as configured + * with guacamole.properties. + * + * @return + * The issuer to expect for all received ID tokens, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the issuer property + * is missing. + */ + public String getIssuer() throws GuacamoleException { + return environment.getRequiredProperty(OPENID_ISSUER); + } + + /** + * Returns the endpoint (URI) of the JWKS service which defines how + * received ID tokens (JWTs) shall be validated, as configured with + * guacamole.properties. + * + * @return + * The endpoint (URI) of the JWKS service which defines how received ID + * tokens (JWTs) shall be validated, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the JWKS endpoint + * property is missing. + */ + public String getJWKSEndpoint() throws GuacamoleException { + return environment.getRequiredProperty(OPENID_JWKS_ENDPOINT); + } + + /** + * Returns the claim type which contains the authenticated user's username + * within any valid JWT, as configured with guacamole.properties. By + * default, this will be "email". + * + * @return + * The claim type which contains the authenticated user's username + * within any valid JWT, as configured with guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getUsernameClaimType() throws GuacamoleException { + return environment.getProperty(OPENID_USERNAME_CLAIM_TYPE, DEFAULT_USERNAME_CLAIM_TYPE); + } + + /** + * Returns the space-separated list of OpenID scopes to request. By default, + * this will be "openid email profile". The OpenID scopes determine the + * information returned within the OpenID token, and thus affect what + * values can be used as an authenticated user's username. + * + * @return + * The space-separated list of OpenID scopes to request when identifying + * a user. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getScope() throws GuacamoleException { + return environment.getProperty(OPENID_SCOPE, DEFAULT_SCOPE); + } + + /** + * Returns the amount of clock skew tolerated for timestamp comparisons + * between the Guacamole server and OpenID service clocks, in seconds. Too + * much clock skew will affect token expiration calculations, possibly + * allowing old tokens to be used. By default, this will be 30. + * + * @return + * The amount of clock skew tolerated for timestamp comparisons, in + * seconds. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public int getAllowedClockSkew() throws GuacamoleException { + return environment.getProperty(OPENID_ALLOWED_CLOCK_SKEW, DEFAULT_ALLOWED_CLOCK_SKEW); + } + + /** + * Returns the maximum amount of time that an OpenID token should remain + * valid, in minutes. A token received from an OpenID service which is + * older than this amount of time will be rejected, even if it is otherwise + * valid. By default, this will be 300 (5 hours). + * + * @return + * The maximum amount of time that an OpenID token should remain valid, + * in minutes. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public int getMaxTokenValidity() throws GuacamoleException { + return environment.getProperty(OPENID_MAX_TOKEN_VALIDITY, DEFAULT_MAX_TOKEN_VALIDITY); + } + + /** + * Returns the maximum amount of time that a nonce generated by the + * Guacamole server should remain valid, in minutes. As each OpenID request + * has a unique nonce value, this imposes an upper limit on the amount of + * time any particular OpenID request can result in successful + * authentication within Guacamole. By default, this will be 10. + * + * @return + * The maximum amount of time that a nonce generated by the Guacamole + * server should remain valid, in minutes. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public int getMaxNonceValidity() throws GuacamoleException { + return environment.getProperty(OPENID_MAX_NONCE_VALIDITY, DEFAULT_MAX_NONCE_VALIDITY); + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java new file mode 100644 index 000000000..d99c3672d --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java @@ -0,0 +1,106 @@ +/* + * 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.auth.openid.form; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import org.apache.guacamole.form.Field; + +/** + * Field definition which represents the token returned by an OpenID Connect + * service. + */ +public class TokenField extends Field { + + /** + * The standard HTTP parameter which will be included within the URL by all + * OpenID services upon successful authentication and redirect. + */ + public static final String PARAMETER_NAME = "id_token"; + + /** + * The full URI which the field should link to. + */ + private final String authorizationURI; + + /** + * Creates a new field which requests authentication via OpenID connect. + * Successful authentication at the OpenID Connect service will result in + * the client being redirected to the specified redirect URI. The OpenID + * token will be embedded in the fragment (the part following the hash + * symbol) of that URI, which the JavaScript side of this extension will + * move to the query parameters. + * + * @param authorizationEndpoint + * The full URL of the endpoint accepting OpenID authentication + * requests. + * + * @param scope + * The space-delimited list of OpenID scopes to request from the + * identity provider, such as "openid" or "openid email profile". + * + * @param clientID + * The ID of the OpenID client. This is normally determined ahead of + * time by the OpenID service through some manual credential request + * procedure. + * + * @param redirectURI + * The URI that the OpenID service should redirect to upon successful + * authentication. + * + * @param nonce + * A random string unique to this request. To defend against replay + * attacks, this value must cease being valid after its first use. + */ + public TokenField(String authorizationEndpoint, String scope, + String clientID, String redirectURI, String nonce) { + + // Init base field properties + super(PARAMETER_NAME, "GUAC_OPENID_TOKEN"); + + // Build authorization URI from given values + try { + this.authorizationURI = authorizationEndpoint + + "?scope=" + URLEncoder.encode(scope, "UTF-8") + + "&response_type=id_token" + + "&client_id=" + URLEncoder.encode(clientID, "UTF-8") + + "&redirect_uri=" + URLEncoder.encode(redirectURI, "UTF-8") + + "&nonce=" + nonce; + } + + // Java is required to provide UTF-8 support + catch (UnsupportedEncodingException e) { + throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e); + } + + } + + /** + * Returns the full URI that this field should link to when a new token + * needs to be obtained from the OpenID service. + * + * @return + * The full URI that this field should link to. + */ + public String getAuthorizationURI() { + return authorizationURI; + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/NonceService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/NonceService.java new file mode 100644 index 000000000..778112a76 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/NonceService.java @@ -0,0 +1,135 @@ +/* + * 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.auth.openid.token; + +import com.google.inject.Singleton; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Service for generating and validating single-use random tokens (nonces). + */ +@Singleton +public class NonceService { + + /** + * Cryptographically-secure random number generator for generating the + * required nonce. + */ + private final SecureRandom random = new SecureRandom(); + + /** + * Map of all generated nonces to their corresponding expiration timestamps. + * This Map must be periodically swept of expired nonces to avoid growing + * without bound. + */ + private final Map nonces = new ConcurrentHashMap(); + + /** + * The timestamp of the last expired nonce sweep. + */ + private long lastSweep = System.currentTimeMillis(); + + /** + * The minimum amount of time to wait between sweeping expired nonces from + * the Map. + */ + private static final long SWEEP_INTERVAL = 60000; + + /** + * Iterates through the entire Map of generated nonces, removing any nonce + * that has exceeded its expiration timestamp. If insufficient time has + * elapsed since the last sweep, as dictated by SWEEP_INTERVAL, this + * function has no effect. + */ + private void sweepExpiredNonces() { + + // Do not sweep until enough time has elapsed since the last sweep + long currentTime = System.currentTimeMillis(); + if (currentTime - lastSweep < SWEEP_INTERVAL) + return; + + // Record time of sweep + lastSweep = currentTime; + + // For each stored nonce + Iterator> entries = nonces.entrySet().iterator(); + while (entries.hasNext()) { + + // Remove all entries which have expired + Map.Entry current = entries.next(); + if (current.getValue() <= System.currentTimeMillis()) + entries.remove(); + + } + + } + + /** + * Generates a cryptographically-secure nonce value. The nonce is intended + * to be used to prevent replay attacks. + * + * @param maxAge + * The maximum amount of time that the generated nonce should remain + * valid, in milliseconds. + * + * @return + * A cryptographically-secure nonce value. + */ + public String generate(long maxAge) { + + // Sweep expired nonces if enough time has passed + sweepExpiredNonces(); + + // Generate and store nonce, along with expiration timestamp + String nonce = new BigInteger(130, random).toString(32); + nonces.put(nonce, System.currentTimeMillis() + maxAge); + return nonce; + + } + + /** + * Returns whether the give nonce value is valid. A nonce is valid if and + * only if it was generated by this instance of the NonceService. Testing + * nonce validity through this function immediately and permanently + * invalidates that nonce. + * + * @param nonce + * The nonce value to test. + * + * @return + * true if the provided nonce is valid, false otherwise. + */ + public boolean isValid(String nonce) { + + // Remove nonce, verifying whether it was present at all + Long expires = nonces.remove(nonce); + if (expires == null) + return false; + + // Nonce is only valid if it hasn't expired + return expires > System.currentTimeMillis(); + + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java new file mode 100644 index 000000000..cde4f89a6 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java @@ -0,0 +1,140 @@ +/* + * 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.auth.openid.token; + +import com.google.inject.Inject; +import org.apache.guacamole.auth.openid.conf.ConfigurationService; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.jwk.HttpsJwks; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.MalformedClaimException; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for validating ID tokens forwarded to us by the client, verifying + * that they did indeed come from the OpenID service. + */ +public class TokenValidationService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(TokenValidationService.class); + + /** + * Service for retrieving OpenID configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Service for validating and generating unique nonce values. + */ + @Inject + private NonceService nonceService; + + /** + * Validates and parses the given ID token, returning the username contained + * therein, as defined by the username claim type given in + * guacamole.properties. If the username claim type is missing or the ID + * token is invalid, null is returned. + * + * @param token + * The ID token to validate and parse. + * + * @return + * The username contained within the given ID token, or null if the ID + * token is not valid or the username claim type is missing, + * + * @throws GuacamoleException + * If guacamole.properties could not be parsed. + */ + public String processUsername(String token) throws GuacamoleException { + + // Validating the token requires a JWKS key resolver + HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint()); + HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); + + // Create JWT consumer for validating received token + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) + .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) + .setRequireSubject() + .setExpectedIssuer(confService.getIssuer()) + .setExpectedAudience(confService.getClientID()) + .setVerificationKeyResolver(resolver) + .build(); + + try { + + String usernameClaim = confService.getUsernameClaimType(); + + // Validate JWT + JwtClaims claims = jwtConsumer.processToClaims(token); + + // Verify a nonce is present + String nonce = claims.getStringClaimValue("nonce"); + if (nonce == null) { + logger.info("Rejected OpenID token without nonce."); + return null; + } + + // Verify that we actually generated the nonce, and that it has not + // already been used + if (!nonceService.isValid(nonce)) { + logger.debug("Rejected OpenID token with invalid/old nonce."); + return null; + } + + // Pull username from claims + String username = claims.getStringClaimValue(usernameClaim); + if (username != null) + return username; + + // Warn if username was not present in token, as it likely means + // the system is not set up correctly + logger.warn("Username claim \"{}\" missing from token. Perhaps the " + + "OpenID scope and/or username claim type are " + + "misconfigured?", usernameClaim); + + } + + // Log any failures to validate/parse the JWT + catch (InvalidJwtException e) { + logger.info("Rejected invalid OpenID token: {}", e.getMessage()); + logger.debug("Invalid JWT received.", e); + } + catch (MalformedClaimException e) { + logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage()); + logger.debug("Malformed claim within received JWT.", e); + } + + // Could not retrieve username from JWT + return null; + + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/user/AuthenticatedUser.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/user/AuthenticatedUser.java new file mode 100644 index 000000000..b7ff12549 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/user/AuthenticatedUser.java @@ -0,0 +1,71 @@ +/* + * 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.auth.openid.user; + +import com.google.inject.Inject; +import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; + +/** + * An openid-specific implementation of AuthenticatedUser, associating a + * username and particular set of credentials with the OpenID authentication + * provider. + */ +public class AuthenticatedUser extends AbstractAuthenticatedUser { + + /** + * Reference to the authentication provider associated with this + * authenticated user. + */ + @Inject + private AuthenticationProvider authProvider; + + /** + * The credentials provided when this user was authenticated. + */ + private Credentials credentials; + + /** + * Initializes this AuthenticatedUser using the given username and + * credentials. + * + * @param username + * The username of the user that was authenticated. + * + * @param credentials + * The credentials provided when this user was authenticated. + */ + public void init(String username, Credentials credentials) { + this.credentials = credentials; + setIdentifier(username); + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + @Override + public Credentials getCredentials() { + return credentials; + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/resources/config/openidConfig.js b/extensions/guacamole-auth-openid/src/main/resources/config/openidConfig.js new file mode 100644 index 000000000..12bc0dabb --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/config/openidConfig.js @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/** + * Config block which registers openid-specific field types. + */ +angular.module('guacOpenID').config(['formServiceProvider', + function guacOpenIDConfig(formServiceProvider) { + + // Define field for token from OpenID service + formServiceProvider.registerFieldType("GUAC_OPENID_TOKEN", { + templateUrl : 'app/ext/guac-openid/templates/openidTokenField.html', + controller : 'guacOpenIDController', + module : 'guacOpenID' + }); + +}]); + +/** + * Config block which augments the existing routing, providing special handling + * for the "id_token=" fragments provided by OpenID Connect. + */ +angular.module('index').config(['$routeProvider', + function indexRouteConfig($routeProvider) { + + // Transform "/#/id_token=..." to "/#/?id_token=..." + $routeProvider.when('/id_token=:response', { + + template : '', + controller : ['$location', function reroute($location) { + var params = $location.path().substring(1); + $location.url('/'); + $location.search(params); + }] + + }); + +}]); diff --git a/extensions/guacamole-auth-openid/src/main/resources/controllers/openidController.js b/extensions/guacamole-auth-openid/src/main/resources/controllers/openidController.js new file mode 100644 index 000000000..a1fad885c --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/controllers/openidController.js @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/** + * Controller for the "GUAC_OPENID_TOKEN" field which simply redirects the user + * immediately to the authorization URI. + */ +angular.module('guacOpenID').controller('guacOpenIDController', ['$scope', + function guacOpenIDController($scope) { + + // Redirect to authorization URI + window.location = $scope.field.authorizationURI; + +}]); diff --git a/extensions/guacamole-auth-openid/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-openid/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..e71c140fa --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/guac-manifest.json @@ -0,0 +1,28 @@ +{ + + "guacamoleVersion" : "0.9.13-incubating", + + "name" : "OpenID Authentication Extension", + "namespace" : "guac-openid", + + "authProviders" : [ + "org.apache.guacamole.auth.openid.OpenIDAuthenticationProvider" + ], + + "translations" : [ + "translations/en.json" + ], + + "js" : [ + "openid.min.js" + ], + + "css" : [ + "openid.min.css" + ], + + "resources" : { + "templates/openidTokenField.html" : "text/html" + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/resources/license.txt b/extensions/guacamole-auth-openid/src/main/resources/license.txt new file mode 100644 index 000000000..042f3ce1f --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/license.txt @@ -0,0 +1,18 @@ +/* + * 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. + */ diff --git a/extensions/guacamole-auth-openid/src/main/resources/openidModule.js b/extensions/guacamole-auth-openid/src/main/resources/openidModule.js new file mode 100644 index 000000000..e8fce23e2 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/openidModule.js @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/** + * Module which provides handling for OpenID authentication. + */ +angular.module('guacOpenID', [ + 'form' +]); + +// Ensure the OpenID module is loaded along with the rest of the app +angular.module('index').requires.push('guacOpenID'); diff --git a/extensions/guacamole-auth-openid/src/main/resources/styles/openid.css b/extensions/guacamole-auth-openid/src/main/resources/styles/openid.css new file mode 100644 index 000000000..eab7f935d --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/styles/openid.css @@ -0,0 +1,35 @@ +/* + * 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. + */ + +.openid-token-field-container { + height: 100%; + width: 100%; + position: fixed; + left: 0; + top: 0; + display: table; + background: white; +} + +.openid-token-field { + width: 100%; + display: table-cell; + vertical-align: middle; + text-align: center; +} diff --git a/extensions/guacamole-auth-openid/src/main/resources/templates/openidTokenField.html b/extensions/guacamole-auth-openid/src/main/resources/templates/openidTokenField.html new file mode 100644 index 000000000..49f6c6f0a --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/templates/openidTokenField.html @@ -0,0 +1,5 @@ +
+
+

{{ 'LOGIN.INFO_REDIRECT_PENDING' | translate }}

+
+
diff --git a/extensions/guacamole-auth-openid/src/main/resources/translations/en.json b/extensions/guacamole-auth-openid/src/main/resources/translations/en.json new file mode 100644 index 000000000..6bc52410e --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/resources/translations/en.json @@ -0,0 +1,12 @@ +{ + + "DATA_SOURCE_OPENID" : { + "NAME" : "OpenID SSO Backend" + }, + + "LOGIN" : { + "FIELD_HEADER_ID_TOKEN" : "", + "INFO_REDIRECT_PENDING" : "Please wait, redirecting to identity provider..." + } + +} diff --git a/guacamole-common-js/src/main/webapp/modules/Tunnel.js b/guacamole-common-js/src/main/webapp/modules/Tunnel.js index cc5fbbb1e..508aa4c28 100644 --- a/guacamole-common-js/src/main/webapp/modules/Tunnel.js +++ b/guacamole-common-js/src/main/webapp/modules/Tunnel.js @@ -306,7 +306,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain) { var message_xmlhttprequest = new XMLHttpRequest(); message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel.uuid); message_xmlhttprequest.withCredentials = withCredentials; - message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8"); + message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream"); // Once response received, send next queued event. message_xmlhttprequest.onreadystatechange = function() { diff --git a/guacamole-docker/bin/build-guacamole.sh b/guacamole-docker/bin/build-guacamole.sh index 4df6289f0..41e01b500 100755 --- a/guacamole-docker/bin/build-guacamole.sh +++ b/guacamole-docker/bin/build-guacamole.sh @@ -54,7 +54,6 @@ mkdir -p "$DESTINATION" cd "$BUILD_DIR" mvn package -rm -Rf ~/.m2 # # Copy guacamole.war to destination diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java index 730eec004..08b9b4e56 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java @@ -29,4 +29,9 @@ public abstract class AbstractAuthenticatedUser extends AbstractIdentifiable // Prior functionality now resides within AbstractIdentifiable + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecord.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecord.java new file mode 100644 index 000000000..2324b0ee4 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecord.java @@ -0,0 +1,78 @@ +/* + * 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.net.auth; + +import java.util.Date; + +/** + * A logging record describing when a user started and ended a particular + * activity. + */ +public interface ActivityRecord { + + /** + * Returns the date and time the activity began. + * + * @return + * The date and time the activity began. + */ + public Date getStartDate(); + + /** + * Returns the date and time the activity ended, if applicable. + * + * @return + * The date and time the activity ended, or null if the activity is + * still ongoing or if the end time is unknown. + */ + public Date getEndDate(); + + /** + * Returns the hostname or IP address of the remote host that performed the + * activity associated with this record, if known. If the hostname or IP + * address is not known, null is returned. + * + * @return + * The hostname or IP address of the remote host, or null if this + * information is not available. + */ + public String getRemoteHost(); + + /** + * Returns the name of the user who performed or is performing the activity + * at the times given by this record. + * + * @return + * The name of the user who performed or is performing the associated + * activity. + */ + public String getUsername(); + + /** + * Returns whether the activity associated with this record is still + * ongoing. + * + * @return + * true if the activity associated with this record is still ongoing, + * false otherwise. + */ + public boolean isActive(); + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecordSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecordSet.java new file mode 100644 index 000000000..4cce03e65 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActivityRecordSet.java @@ -0,0 +1,128 @@ +/* + * 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.net.auth; + +import java.util.Collection; +import org.apache.guacamole.GuacamoleException; + +/** + * A set of all available records related to a type of activity which has a + * defined start and end time, such as a user being logged in or connected, or a + * subset of those records. + * + * @param + * The type of ActivityRecord contained within this set. + */ +public interface ActivityRecordSet { + + /** + * All properties of activity records which can be used as sorting + * criteria. + */ + enum SortableProperty { + + /** + * The date and time when the activity associated with the record + * began. + */ + START_DATE + + }; + + /** + * Returns all records within this set as a standard Collection. + * + * @return + * A collection containing all records within this set. + * + * @throws GuacamoleException + * If an error occurs while retrieving the records within this set. + */ + Collection asCollection() throws GuacamoleException; + + /** + * Returns the subset of records which contain the given value. The + * properties and semantics involved with determining whether a particular + * record "contains" the given value is implementation dependent. This + * function may affect the contents of the current ActivityRecordSet. The + * contents of the current ActivityRecordSet should NOT be relied upon + * after this function is called. + * + * @param value + * The value which all records within the resulting subset should + * contain. + * + * @return + * The subset of records which contain the specified value. + * + * @throws GuacamoleException + * If an error occurs while restricting the current subset. + */ + ActivityRecordSet contains(String value) + throws GuacamoleException; + + /** + * Returns the subset of records containing only the first + * limit records. If the subset has fewer than + * limit records, then this function has no effect. This + * function may also affect the contents of the current ActivityRecordSet. + * The contents of the current ActivityRecordSet should NOT be relied upon + * after this function is called. + * + * @param limit + * The maximum number of records that the new subset should contain. + * + * @return + * The subset of records that containing only the first + * limit records. + * + * @throws GuacamoleException + * If an error occurs while limiting the current subset. + */ + ActivityRecordSet limit(int limit) throws GuacamoleException; + + /** + * Returns a ActivityRecordSet containing identically the records within + * this set, sorted according to the specified criteria. The sort operation + * performed is guaranteed to be stable with respect to any past call to + * sort(). This function may also affect the contents of the current + * ActivityRecordSet. The contents of the current ActivityRecordSet + * should NOT be relied upon after this function is called. + * + * @param property + * The property by which the records within the resulting set should be + * sorted. + * + * @param desc + * Whether the records should be sorted according to the specified + * property in descending order. If false, records will be sorted + * according to the specified property in ascending order. + * + * @return + * The ActivityRecordSet, sorted according to the specified criteria. + * + * @throws GuacamoleException + * If an error occurs while sorting the current subset, or if the given + * property is not supported by the implementation. + */ + ActivityRecordSet sort(SortableProperty property, boolean desc) + throws GuacamoleException; + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java index 7f363f28d..f6ceb3abe 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java @@ -49,4 +49,11 @@ public interface AuthenticatedUser extends Identifiable { */ Credentials getCredentials(); + /** + * Invalidates this authenticated user and their associated token such that + * they are no longer logged in. This function will be automatically + * invoked when the user logs out, or when their session expires. + */ + void invalidate(); + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticationProvider.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticationProvider.java index 3acc5127c..448f495f1 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticationProvider.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticationProvider.java @@ -167,5 +167,12 @@ public interface AuthenticationProvider { UserContext updateUserContext(UserContext context, AuthenticatedUser authenticatedUser, Credentials credentials) throws GuacamoleException; + + /** + * Frees all resources associated with this AuthenticationProvider. This + * function will be automatically invoked when the Guacamole server is + * shutting down. + */ + void shutdown(); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java index b0795e2a1..85fd1680d 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java @@ -19,6 +19,7 @@ package org.apache.guacamole.net.auth; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -102,6 +103,18 @@ public interface Connection extends Identifiable, Connectable { */ void setAttributes(Map attributes); + /** + * Returns the date and time that this connection was last used. If the + * connection was never used, the time that the connection was last used is + * unknown, or this information is not visible to the current user, this + * may be null. + * + * @return + * The date and time this connection was last used, or null if this + * information is unavailable or inapplicable. + */ + Date getLastActive(); + /** * Returns a list of ConnectionRecords representing the usage history * of this Connection, including any active users. ConnectionRecords diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecord.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecord.java index 95c5f6ca1..21e30a98e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecord.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecord.java @@ -19,13 +19,11 @@ package org.apache.guacamole.net.auth; -import java.util.Date; - /** * A logging record describing when a user started and ended usage of a * particular connection. */ -public interface ConnectionRecord { +public interface ConnectionRecord extends ActivityRecord { /** * Returns the identifier of the connection associated with this @@ -72,48 +70,4 @@ public interface ConnectionRecord { */ public String getSharingProfileName(); - /** - * Returns the date and time the connection began. - * - * @return The date and time the connection began. - */ - public Date getStartDate(); - - /** - * Returns the date and time the connection ended, if applicable. - * - * @return The date and time the connection ended, or null if the - * connection is still running or if the end time is unknown. - */ - public Date getEndDate(); - - /** - * Returns the hostname or IP address of the remote host that used the - * connection associated with this record, if known. If the hostname or IP - * address is not known, null is returned. - * - * @return - * The hostname or IP address of the remote host, or null if this - * information is not available. - */ - public String getRemoteHost(); - - /** - * Returns the name of the user who used or is using the connection at the - * times given by this connection record. - * - * @return The name of the user who used or is using the associated - * connection. - */ - public String getUsername(); - - /** - * Returns whether the connection associated with this record is still - * active. - * - * @return true if the connection associated with this record is still - * active, false otherwise. - */ - public boolean isActive(); - } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecordSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecordSet.java index 5198cfa80..62c84ec44 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecordSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionRecordSet.java @@ -19,107 +19,13 @@ package org.apache.guacamole.net.auth; -import java.util.Collection; -import org.apache.guacamole.GuacamoleException; - /** * The set of all available connection records, or a subset of those records. + * + * @deprecated + * Use {@link ActivityRecordSet}<{@link ConnectionRecord}> instead. */ -public interface ConnectionRecordSet { - - /** - * All properties of connection records which can be used as sorting - * criteria. - */ - enum SortableProperty { - - /** - * The date and time when the connection associated with the - * connection record began. - */ - START_DATE - - }; - - /** - * Returns all connection records within this set as a standard Collection. - * - * @return - * A collection containing all connection records within this set. - * - * @throws GuacamoleException - * If an error occurs while retrieving the connection records within - * this set. - */ - Collection asCollection() throws GuacamoleException; - - /** - * Returns the subset of connection records to only those where the - * connection name, user identifier, or any associated date field contain - * the given value. This function may also affect the contents of the - * current ConnectionRecordSet. The contents of the current - * ConnectionRecordSet should NOT be relied upon after this function is - * called. - * - * @param value - * The value which all connection records within the resulting subset - * should contain within their associated connection name or user - * identifier. - * - * @return - * The subset of connection history records which contain the specified - * value within their associated connection name or user identifier. - * - * @throws GuacamoleException - * If an error occurs while restricting the current subset. - */ - ConnectionRecordSet contains(String value) throws GuacamoleException; - - /** - * Returns the subset of connection history records containing only the - * first limit records. If the subset has fewer than - * limit records, then this function has no effect. This - * function may also affect the contents of the current - * ConnectionRecordSet. The contents of the current ConnectionRecordSet - * should NOT be relied upon after this function is called. - * - * @param limit - * The maximum number of records that the new subset should contain. - * - * @return - * The subset of connection history records that containing only the - * first limit records. - * - * @throws GuacamoleException - * If an error occurs while limiting the current subset. - */ - ConnectionRecordSet limit(int limit) throws GuacamoleException; - - /** - * Returns a ConnectionRecordSet containing identically the records within - * this set, sorted according to the specified criteria. The sort operation - * performed is guaranteed to be stable with respect to any past call to - * sort(). This function may also affect the contents of the current - * ConnectionRecordSet. The contents of the current ConnectionRecordSet - * should NOT be relied upon after this function is called. - * - * @param property - * The property by which the connection records within the resulting - * set should be sorted. - * - * @param desc - * Whether the records should be sorted according to the specified - * property in descending order. If false, records will be sorted - * according to the specified property in ascending order. - * - * @return - * The ConnnectionRecordSet, sorted according to the specified - * criteria. - * - * @throws GuacamoleException - * If an error occurs while sorting the current subset. - */ - ConnectionRecordSet sort(SortableProperty property, boolean desc) - throws GuacamoleException; - +@Deprecated +public interface ConnectionRecordSet + extends ActivityRecordSet { } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/User.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/User.java index 88756e495..f7bd61ca2 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/User.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/User.java @@ -19,6 +19,8 @@ package org.apache.guacamole.net.auth; +import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; @@ -100,6 +102,34 @@ public interface User extends Identifiable { */ void setAttributes(Map attributes); + /** + * Returns the date and time that this user was last active. If the user + * was never active, the time that the user was last active is unknown, or + * this information is not visible to the current user, this may be null. + * + * @return + * The date and time this user was last active, or null if this + * information is unavailable or inapplicable. + */ + Date getLastActive(); + + /** + * Returns a list of ActivityRecords representing the login history + * of this user, including any active sessions. ActivityRecords + * in this list will be sorted in descending order of end time (active + * sessions are first), and then in descending order of start time + * (newer sessions are first). + * + * @return + * A list of ActivityRecords representing the login history of this + * User. + * + * @throws GuacamoleException + * If an error occurs while reading the history of this user, or if + * permission is denied. + */ + List getHistory() throws GuacamoleException; + /** * Returns all system-level permissions given to this user. * diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/UserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/UserContext.java index 5c6f97428..1c82f9ce8 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/UserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/UserContext.java @@ -143,9 +143,11 @@ public interface UserContext { throws GuacamoleException; /** - * Retrieves all connection records visible to current user. The resulting - * set of connection records can be further filtered and ordered using the - * methods defined on ConnectionRecordSet. + * Retrieves all connection records visible to current user. Connection + * history records describe the start and end times of connections, and + * correspond to the times that users connect or disconnect to individual + * remote desktops. The resulting set of connection records can be further + * filtered and ordered using the methods defined on ActivityRecordSet. * * @return * A set of all connection records visible to the current user. @@ -153,7 +155,23 @@ public interface UserContext { * @throws GuacamoleException * If an error occurs while retrieving the connection records. */ - ConnectionRecordSet getConnectionHistory() throws GuacamoleException; + ActivityRecordSet getConnectionHistory() + throws GuacamoleException; + + /** + * Retrieves all user history records visible to current user. User history + * records describe the start and end times of user sessions, and correspond + * to the times that users logged in or out. The resulting set of user + * records can be further filtered and ordered using the methods defined on + * ActivityRecordSet. + * + * @return + * A set of all user records visible to the current user. + * + * @throws GuacamoleException + * If an error occurs while retrieving the user records. + */ + ActivityRecordSet getUserHistory() throws GuacamoleException; /** * Retrieves a connection group which can be used to view and manipulate @@ -212,4 +230,11 @@ public interface UserContext { */ Collection getSharingProfileAttributes(); + /** + * Invalidates this user context, releasing all associated resources. This + * function will be invoked when the user logs out, or when their session + * is automatically invalidated. + */ + void invalidate(); + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleActivityRecordSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleActivityRecordSet.java new file mode 100644 index 000000000..a9a3c3eed --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleActivityRecordSet.java @@ -0,0 +1,62 @@ +/* + * 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.net.auth.simple; + +import java.util.Collection; +import java.util.Collections; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.ActivityRecord; +import org.apache.guacamole.net.auth.ActivityRecordSet; +import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty; + +/** + * An immutable and empty ActivityRecordSet. + * + * @param + * The type of ActivityRecord contained within this set. + */ +public class SimpleActivityRecordSet + implements ActivityRecordSet { + + @Override + public Collection asCollection() + throws GuacamoleException { + return Collections.emptyList(); + } + + @Override + public ActivityRecordSet contains(String value) + throws GuacamoleException { + return this; + } + + @Override + public ActivityRecordSet limit(int limit) + throws GuacamoleException { + return this; + } + + @Override + public ActivityRecordSet sort(SortableProperty property, + boolean desc) throws GuacamoleException { + return this; + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java index 96766cb31..83ac79476 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java @@ -260,4 +260,9 @@ public abstract class SimpleAuthenticationProvider } + @Override + public void shutdown() { + // Do nothing + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java index 2251a9edf..85783a0a4 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java @@ -20,6 +20,7 @@ package org.apache.guacamole.net.auth.simple; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import org.apache.guacamole.GuacamoleException; @@ -136,6 +137,11 @@ public class SimpleConnection extends AbstractConnection { } + @Override + public Date getLastActive() { + return null; + } + @Override public List getHistory() throws GuacamoleException { return Collections.emptyList(); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionRecordSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionRecordSet.java index f18b3d6eb..41971c9ae 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionRecordSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionRecordSet.java @@ -23,12 +23,16 @@ import java.util.Collection; import java.util.Collections; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.ConnectionRecord; -import org.apache.guacamole.net.auth.ConnectionRecordSet; /** * An immutable and empty ConnectionRecordSet. + * + * @deprecated + * Use {@link SimpleActivityRecordSet}<{@link ConnectionRecord}> + * instead. */ -public class SimpleConnectionRecordSet implements ConnectionRecordSet { +@Deprecated +public class SimpleConnectionRecordSet implements org.apache.guacamole.net.auth.ConnectionRecordSet { @Override public Collection asCollection() @@ -37,19 +41,19 @@ public class SimpleConnectionRecordSet implements ConnectionRecordSet { } @Override - public ConnectionRecordSet contains(String value) + public org.apache.guacamole.net.auth.ConnectionRecordSet contains(String value) throws GuacamoleException { return this; } @Override - public ConnectionRecordSet limit(int limit) + public org.apache.guacamole.net.auth.ConnectionRecordSet limit(int limit) throws GuacamoleException { return this; } @Override - public ConnectionRecordSet sort(SortableProperty property, boolean desc) + public org.apache.guacamole.net.auth.ConnectionRecordSet sort(SortableProperty property, boolean desc) throws GuacamoleException { return this; } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java index d6db92fa5..19ed35731 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java @@ -21,11 +21,14 @@ package org.apache.guacamole.net.auth.simple; import java.util.Collection; 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.GuacamoleException; import org.apache.guacamole.net.auth.AbstractUser; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.permission.ObjectPermission; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.permission.SystemPermissionSet; @@ -163,6 +166,16 @@ public class SimpleUser extends AbstractUser { // Do nothing - there are no attributes } + @Override + public Date getLastActive() { + return null; + } + + @Override + public List getHistory() throws GuacamoleException { + return Collections.emptyList(); + } + @Override public SystemPermissionSet getSystemPermissions() throws GuacamoleException { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java index 1e55db92f..1678d8827 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java @@ -27,10 +27,12 @@ import java.util.UUID; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.ActivityRecord; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; -import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.ConnectionRecord; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.SharingProfile; import org.apache.guacamole.net.auth.User; @@ -209,9 +211,15 @@ public class SimpleUserContext implements UserContext { } @Override - public ConnectionRecordSet getConnectionHistory() + public ActivityRecordSet getConnectionHistory() throws GuacamoleException { - return new SimpleConnectionRecordSet(); + return new SimpleActivityRecordSet(); + } + + @Override + public ActivityRecordSet getUserHistory() + throws GuacamoleException { + return new SimpleActivityRecordSet(); } @Override @@ -234,4 +242,9 @@ public class SimpleUserContext implements UserContext { return Collections.emptyList(); } + @Override + public void invalidate() { + // Nothing to invalidate + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java index 2e5ae3a17..c72d669fa 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java @@ -26,6 +26,11 @@ import org.apache.guacamole.net.auth.UserContext; * An event which is triggered whenever a user's credentials pass * authentication. The credentials that passed authentication are included * within this event, and can be retrieved using getCredentials(). + *

+ * If a {@link org.apache.guacamole.net.event.listener.Listener} throws + * a GuacamoleException when handling an event of this type, successful authentication + * is effectively vetoed and will be subsequently processed as though the + * authentication failed. */ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java index ab453e87e..c0e2a622e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java @@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext; * being closed can be accessed through getTunnel(), and the UserContext * associated with the request which is closing the tunnel can be retrieved * with getUserContext(). + *

+ * If a {@link org.apache.guacamole.net.event.listener.Listener} throws + * a GuacamoleException when handling an event of this type, the request to close + * the tunnel is effectively vetoed and will remain connected. */ public class TunnelCloseEvent implements UserEvent, CredentialEvent, TunnelEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java index acf5e8922..62828db8f 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java @@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext; * being connected can be accessed through getTunnel(), and the UserContext * associated with the request which is connecting the tunnel can be retrieved * with getUserContext(). + *

+ * If a {@link org.apache.guacamole.net.event.listener.Listener} throws + * a GuacamoleException when handling an event of this type, the tunnel connection + * is effectively vetoed and will be subsequently closed. */ public class TunnelConnectEvent implements UserEvent, CredentialEvent, TunnelEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java index 5fcd27b67..6e707e6c5 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java @@ -26,19 +26,26 @@ import org.apache.guacamole.net.event.AuthenticationFailureEvent; * A listener whose authenticationFailed() hook will fire immediately * after a user's authentication attempt fails. Note that this hook cannot * be used to cancel the authentication failure. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface. */ -public interface AuthenticationFailureListener { +@Deprecated +public interface AuthenticationFailureListener { /** * Event hook which fires immediately after a user's authentication attempt * fails. * - * @param e The AuthenticationFailureEvent describing the authentication - * failure that just occurred. - * @throws GuacamoleException If an error occurs while handling the - * authentication failure event. Note that - * throwing an exception will NOT cause the - * authentication failure to be canceled. + * @param e + * The AuthenticationFailureEvent describing the authentication + * failure that just occurred. + * + * @throws GuacamoleException + * If an error occurs while handling the authentication failure event. + * Note that throwing an exception will NOT cause the authentication + * failure to be canceled (which makes no sense), but it will prevent + * subsequent listeners from receiving the notification. */ void authenticationFailed(AuthenticationFailureEvent e) throws GuacamoleException; diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java index 7db072c36..6ba05a3f1 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java @@ -27,7 +27,11 @@ import org.apache.guacamole.net.event.AuthenticationSuccessEvent; * authentication attempt succeeds. If a user successfully authenticates, * the authenticationSucceeded() hook has the opportunity to cancel the * authentication and force it to fail. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface. */ +@Deprecated public interface AuthenticationSuccessListener { /** @@ -35,15 +39,18 @@ public interface AuthenticationSuccessListener { * succeeds. The return value of this hook dictates whether the * successful authentication attempt is canceled. * - * @param e The AuthenticationFailureEvent describing the authentication - * failure that just occurred. - * @return true if the successful authentication attempt should be - * allowed, or false if the attempt should be denied, causing - * the attempt to effectively fail. - * @throws GuacamoleException If an error occurs while handling the - * authentication success event. Throwing an - * exception will also cancel the authentication - * success. + * @param e + * The AuthenticationFailureEvent describing the authentication + * failure that just occurred. + * + * @return + * true if the successful authentication attempt should be + * allowed, or false if the attempt should be denied, causing + * the attempt to effectively fail. + * + * @throws GuacamoleException + * If an error occurs while handling the authentication success event. + * Throwing an exception will also cancel the authentication success. */ boolean authenticationSucceeded(AuthenticationSuccessEvent e) throws GuacamoleException; diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java new file mode 100644 index 000000000..af480b7e0 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java @@ -0,0 +1,51 @@ +/* + * 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.net.event.listener; + +import org.apache.guacamole.GuacamoleException; + +/** + * A listener for events that occur in handing various Guacamole requests + * such as authentication, tunnel connect/close, etc. Listeners are registered + * through the extension manifest mechanism. When an event occurs, listeners + * are notified in the order in which they are declared in the manifest and + * continues until either all listeners have been notified or with the first + * listener that throws a GuacamoleException or other runtime exception. + */ +public interface Listener { + + /** + * Notifies the recipient that an event has occurred. + *

+ * Throwing an exception from an event listener can act to veto an action in + * progress for some event types. See the Javadoc for specific event types for + * details. + * + * @param event + * An object that describes the event that has occurred. + * + * @throws GuacamoleException + * If the listener wishes to stop notification of the event to subsequent + * listeners. For some event types, this acts to veto an action in progress; + * e.g. treating a successful authentication as though it failed. + */ + void handleEvent(Object event) throws GuacamoleException; + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java index 784e4e932..84d765815 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java @@ -23,26 +23,33 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.event.TunnelCloseEvent; /** - * A listener whose tunnelClosed() hook will fire immediately after an + * A listener whose tunnelClosed() hook will fire immediately before an * existing tunnel is closed. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface. */ +@Deprecated public interface TunnelCloseListener { /** - * Event hook which fires immediately after an existing tunnel is closed. + * Event hook which fires immediately before an existing tunnel is closed. * The return value of this hook dictates whether the tunnel is allowed to * be closed. * - * @param e The TunnelCloseEvent describing the tunnel being closed and - * any associated credentials. - * @return true if the tunnel should be allowed to be closed, or false - * if the attempt should be denied, causing the attempt to - * effectively fail. - * @throws GuacamoleException If an error occurs while handling the - * tunnel close event. Throwing an exception - * will also stop the tunnel from being closed. + * @param e + * The TunnelCloseEvent describing the tunnel being closed and + * any associated credentials. + * + * @return + * true if the tunnel should be allowed to be closed, or false + * if the attempt should be denied, causing the attempt to + * effectively fail. + * + * @throws GuacamoleException + * If an error occurs while handling the tunnel close event. Throwing + * an exception will also stop the tunnel from being closed. */ - boolean tunnelClosed(TunnelCloseEvent e) - throws GuacamoleException; + boolean tunnelClosed(TunnelCloseEvent e) throws GuacamoleException; } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java index da14fe277..e224f7430 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java @@ -25,25 +25,32 @@ import org.apache.guacamole.net.event.TunnelConnectEvent; /** * A listener whose tunnelConnected() hook will fire immediately after a new * tunnel is connected. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface. */ +@Deprecated public interface TunnelConnectListener { /** - * Event hook which fires immediately after a new tunnel is connected. - * The return value of this hook dictates whether the tunnel is made visible - * to the session. - * - * @param e The TunnelConnectEvent describing the tunnel being connected and - * any associated credentials. - * @return true if the tunnel should be allowed to be connected, or false - * if the attempt should be denied, causing the attempt to - * effectively fail. - * @throws GuacamoleException If an error occurs while handling the - * tunnel connect event. Throwing an exception - * will also stop the tunnel from being made - * visible to the session. - */ - boolean tunnelConnected(TunnelConnectEvent e) - throws GuacamoleException; + * Event hook which fires immediately after a new tunnel is connected. + * The return value of this hook dictates whether the tunnel is made visible + * to the session. + * + * @param e + * The TunnelConnectEvent describing the tunnel being connected and + * any associated credentials. + * + * @return + * true if the tunnel should be allowed to be connected, or false + * if the attempt should be denied, causing the attempt to + * effectively fail. + * + * @throws GuacamoleException + * If an error occurs while handling the tunnel connect event. Throwing + * an exception will also stop the tunnel from being made visible to the + * session. + */ + boolean tunnelConnected(TunnelConnectEvent e) throws GuacamoleException; } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java index c13477c4b..b1b280bee 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java @@ -21,6 +21,7 @@ package org.apache.guacamole.token; import java.text.SimpleDateFormat; import java.util.Date; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; /** @@ -138,4 +139,31 @@ public class StandardTokens { } + /** + * Adds tokens which are standardized by guacamole-ext to the given + * TokenFilter using the values from the given AuthenticatedUser object, + * including any associated credentials. These standardized tokens include + * the current username (GUAC_USERNAME), password (GUAC_PASSWORD), and the + * server date and time (GUAC_DATE and GUAC_TIME respectively). If either + * the username or password are not set within the given user or their + * provided credentials, the corresponding token(s) will remain unset. + * + * @param filter + * The TokenFilter to add standard tokens to. + * + * @param user + * The AuthenticatedUser to use when populating the GUAC_USERNAME and + * GUAC_PASSWORD tokens. + */ + public static void addStandardTokens(TokenFilter filter, AuthenticatedUser user) { + + // Default to the authenticated user's username for the GUAC_USERNAME + // token + filter.setToken(USERNAME_TOKEN, user.getIdentifier()); + + // Add tokens specific to credentials + addStandardTokens(filter, user.getCredentials()); + + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index 72fe27313..e8ad6a81f 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -21,14 +21,17 @@ package org.apache.guacamole; import org.apache.guacamole.tunnel.TunnelModule; import com.google.inject.Guice; +import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Stage; import com.google.inject.servlet.GuiceServletContextListener; +import java.util.List; import javax.servlet.ServletContextEvent; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.extension.ExtensionModule; import org.apache.guacamole.log.LogModule; +import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.rest.RESTServiceModule; import org.apache.guacamole.rest.auth.HashTokenSessionMap; import org.apache.guacamole.rest.auth.TokenSessionMap; @@ -56,6 +59,12 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener */ private TokenSessionMap sessionMap; + /** + * List of all authentication providers from all loaded extensions. + */ + @Inject + private List authProviders; + @Override public void contextInitialized(ServletContextEvent servletContextEvent) { @@ -75,13 +84,21 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener @Override protected Injector getInjector() { - return Guice.createInjector(Stage.PRODUCTION, + + // Create injector + Injector injector = Guice.createInjector(Stage.PRODUCTION, new EnvironmentModule(environment), new LogModule(environment), new ExtensionModule(environment), new RESTServiceModule(sessionMap), new TunnelModule() ); + + // Inject any annotated members of this class + injector.injectMembers(this); + + return injector; + } @Override @@ -93,6 +110,12 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener if (sessionMap != null) sessionMap.shutdown(); + // Unload all extensions + if (authProviders != null) { + for (AuthenticationProvider authProvider : authProviders) + authProvider.shutdown(); + } + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java index de8145380..e723c0a9e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java @@ -252,6 +252,13 @@ public class GuacamoleSession { } } + // Invalidate all user contextx + for (UserContext userContext : userContexts) + userContext.invalidate(); + + // Invalidate the authenticated user object + authenticatedUser.invalidate(); + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java index e1ed5ff68..8dfbe7fee 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java @@ -19,7 +19,6 @@ 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; @@ -66,58 +65,8 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { * The AuthenticationProvider subclass to instantiate. */ public AuthenticationProviderFacade(Class 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; - + authProvider = ProviderFactory.newInstance("authentication provider", + authProviderClass); } @Override @@ -209,4 +158,10 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { } + @Override + public void shutdown() { + if (authProvider != null) + authProvider.shutdown(); + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java index 3183fa21c..dc43b8f00 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; +import org.apache.guacamole.net.event.listener.Listener; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.ObjectMapper; import org.apache.guacamole.GuacamoleException; @@ -109,6 +110,11 @@ public class Extension { */ private final Collection> authenticationProviderClasses; + /** + * The collection of all Listener classes defined within the extension. + */ + private final Collection> listenerClasses; + /** * The resource for the small favicon for the extension. If provided, this * will replace the default Guacamole icon. @@ -265,6 +271,80 @@ public class Extension { } + /** + * Retrieve the Listener subclass having the given name. If + * the class having the given name does not exist or isn't actually a + * subclass of Listener, an exception will be thrown. + * + * @param name + * The name of the Listener class to retrieve. + * + * @return + * The subclass of Listener having the given name. + * + * @throws GuacamoleException + * If no such class exists, or if the class with the given name is not + * a subclass of Listener. + */ + @SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom() + private Class getListenerClass(String name) + throws GuacamoleException { + + try { + + // Get listener class + Class listenerClass = classLoader.loadClass(name); + + // Verify the located class is actually a subclass of Listener + if (!Listener.class.isAssignableFrom(listenerClass)) + throw new GuacamoleServerException("Listeners MUST implement a Listener subclass."); + + // Return located class + return (Class) listenerClass; + + } + catch (ClassNotFoundException e) { + throw new GuacamoleException("Listener class not found.", e); + } + catch (LinkageError e) { + throw new GuacamoleException("Listener class cannot be loaded (wrong version of API?).", e); + } + + } + + /** + * Returns a new collection of all Listener subclasses having the given names. + * If any class does not exist or isn't actually subclass of Listener, an + * exception will be thrown, an no further Listener 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> getListenerClasses(Collection names) + throws GuacamoleException { + + // If no classnames are provided, just return an empty list + if (names == null) + return Collections.>emptyList(); + + // Define all auth provider classes + Collection> classes = new ArrayList>(names.size()); + for (String name : names) + classes.add(getListenerClass(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. @@ -363,6 +443,9 @@ public class Extension { // Define authentication providers authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders()); + // Define listeners + listenerClasses = getListenerClasses(manifest.getListeners()); + // Get small icon resource if provided if (manifest.getSmallIcon() != null) smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon()); @@ -488,6 +571,17 @@ public class Extension { return authenticationProviderClasses; } + /** + * Returns all declared listener classes associated wit this extension. Listeners are + * declared within the extension manifest. + * + * @return + * All declared listener classes with this extension. + */ + public Collection> getListenerClasses() { + return listenerClasses; + } + /** * Returns the resource for the small favicon for the extension. If * provided, this will replace the default Guacamole icon. diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java index 9b9bd9bee..2ed6c7579 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java @@ -87,6 +87,11 @@ public class ExtensionManifest { */ private Collection authProviders; + /** + * The names of all listener classes within this extension, if any. + */ + private Collection listeners; + /** * The path to the small favicon. If provided, this will replace the default * Guacamole icon. @@ -355,6 +360,32 @@ public class ExtensionManifest { this.authProviders = authProviders; } + /** + * Returns the classnames of all listener classes within the extension. + * These classnames are defined within the manifest by the "listeners" + * property as an array of strings, where each string is a listener + * class name. + * + * @return + * A collection of classnames for all listeners within the extension. + */ + public Collection getListeners() { + return listeners; + } + + /** + * Sets the classnames of all listener classes within the extension. + * These classnames are defined within the manifest by the "listeners" + * property as an array of strings, where each string is a listener + * class name. + * + * @param listeners + * A collection of classnames for all listeners within the extension. + */ + public void setListeners(Collection listeners) { + this.listeners = listeners; + } + /** * Returns the path to the small favicon, relative to the root of the * extension. diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java index 792066c87..a74c4c0d6 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.event.listener.Listener; import org.apache.guacamole.resource.Resource; import org.apache.guacamole.resource.ResourceServlet; import org.apache.guacamole.resource.SequenceResource; @@ -91,6 +92,12 @@ public class ExtensionModule extends ServletModule { private final List boundAuthenticationProviders = new ArrayList(); + /** + * All currently-bound authentication providers, if any. + */ + private final List boundListeners = + new ArrayList(); + /** * Service for adding and retrieving language resources. */ @@ -187,6 +194,49 @@ public class ExtensionModule extends ServletModule { return Collections.unmodifiableList(boundAuthenticationProviders); } + /** + * Binds the given provider class such that a listener is bound for each + * listener interface implemented by the provider and such that all bound + * listener instances can be obtained via injection. + * + * @param providerClass + * The listener class to bind. + */ + private void bindListener(Class providerClass) { + + logger.debug("[{}] Binding listener \"{}\".", + boundListeners.size(), providerClass.getName()); + boundListeners.addAll(ListenerFactory.createListeners(providerClass)); + + } + + /** + * Binds each of the the given Listener classes such that any + * service requiring access to the Listener can obtain it via + * injection. + * + * @param listeners + * The Listener classes to bind. + */ + private void bindListeners(Collection> listeners) { + + // Bind each listener within extension + for (Class listener : listeners) + bindListener(listener); + } + + /** + * Returns a list of all currently-bound Listener instances. + * + * @return + * A List of all currently-bound Listener instances. The List is + * not modifiable. + */ + @Provides + public List getListeners() { + return Collections.unmodifiableList(boundListeners); + } + /** * Serves each of the given resources as a language resource. Language * resources are served from within the "/translations" directory as JSON @@ -327,6 +377,9 @@ public class ExtensionModule extends ServletModule { // Attempt to load all authentication providers bindAuthenticationProviders(extension.getAuthenticationProviderClasses()); + // Attempt to load all listeners + bindListeners(extension.getListenerClasses()); + // Add any translation resources serveLanguageResources(extension.getTranslationResources()); diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java b/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java new file mode 100644 index 000000000..8aa6babb4 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java @@ -0,0 +1,278 @@ +/* + * 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.extension; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.net.event.AuthenticationFailureEvent; +import org.apache.guacamole.net.event.AuthenticationSuccessEvent; +import org.apache.guacamole.net.event.TunnelCloseEvent; +import org.apache.guacamole.net.event.TunnelConnectEvent; +import org.apache.guacamole.net.event.listener.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A factory that reflectively instantiates Listener objects for a given + * provider class. + */ +class ListenerFactory { + + /** + * Creates all listeners represented by an instance of the given provider class. + *

+ * If a provider class implements the simple Listener interface, that is the + * only listener type that will be returned. Otherwise, a list of Listener + * objects that adapt the legacy listener interfaces will be returned. + * + * @param providerClass + * A class that represents a listener. + * + * @return + * The list of listeners represented by the given provider class. + */ + static List createListeners(Class providerClass) { + + Object provider = ProviderFactory.newInstance("listener", providerClass); + + if (provider instanceof Listener) { + return Collections.singletonList((Listener) provider); + } + + return createListenerAdapters(provider); + + } + + /** + * Creates a list of adapters for the given object, based on the legacy + * listener interfaces it implements. + * + * @param provider + * An object that implements zero or more legacy listener interfaces. + * + * @return + * The list of listeners represented by the given provider class. + */ + @SuppressWarnings("deprecation") + private static List createListenerAdapters(Object provider) { + + final List listeners = new ArrayList(); + + if (provider instanceof AuthenticationSuccessListener) { + listeners.add(new AuthenticationSuccessListenerAdapter( + (AuthenticationSuccessListener) provider)); + } + + if (provider instanceof AuthenticationFailureListener) { + listeners.add(new AuthenticationFailureListenerAdapter( + (AuthenticationFailureListener) provider)); + } + + if (provider instanceof TunnelConnectListener) { + listeners.add(new TunnelConnectListenerAdapter( + (TunnelConnectListener) provider)); + } + + if (provider instanceof TunnelCloseListener) { + listeners.add(new TunnelCloseListenerAdapter( + (TunnelCloseListener) provider)); + } + + return listeners; + + } + + /** + * An adapter the allows an AuthenticationSuccessListener to be used + * as an ordinary Listener. + */ + @SuppressWarnings("deprecation") + private static class AuthenticationSuccessListenerAdapter implements Listener { + + /** + * The delegate listener for this adapter. + */ + private final AuthenticationSuccessListener delegate; + + /** + * Constructs a new adapter that delivers events to the given delegate. + * + * @param delegate + * The delegate listener. + */ + AuthenticationSuccessListenerAdapter(AuthenticationSuccessListener delegate) { + this.delegate = delegate; + } + + /** + * Handles an AuthenticationSuccessEvent by passing the event to the delegate + * listener. If the delegate returns false, the adapter throws a GuacamoleException + * to veto the authentication success event. All other event types are ignored. + * + * @param event + * An object that describes the event that occurred. + * + * @throws GuacamoleException + * If thrown by the delegate listener. + */ + @Override + public void handleEvent(Object event) throws GuacamoleException { + if (event instanceof AuthenticationSuccessEvent) { + if (!delegate.authenticationSucceeded((AuthenticationSuccessEvent) event)) { + throw new GuacamoleSecurityException( + "listener vetoed successful authentication"); + } + } + } + + } + + /** + * An adapter the allows an AuthenticationFailureListener to be used + * as an ordinary Listener. + */ + @SuppressWarnings("deprecation") + private static class AuthenticationFailureListenerAdapter implements Listener { + + /** + * The delegate listener for this adapter. + */ + private final AuthenticationFailureListener delegate; + + /** + * Constructs a new adapter that delivers events to the given delegate. + * + * @param delegate + * The delegate listener. + */ + AuthenticationFailureListenerAdapter(AuthenticationFailureListener delegate) { + this.delegate = delegate; + } + + /** + * Handles an AuthenticationFailureEvent by passing the event to the delegate + * listener. All other event types are ignored. + * + * @param event + * An object that describes the event that occurred. + * + * @throws GuacamoleException + * If thrown by the delegate listener. + */ + @Override + public void handleEvent(Object event) throws GuacamoleException { + if (event instanceof AuthenticationFailureEvent) { + delegate.authenticationFailed((AuthenticationFailureEvent) event); + } + } + + } + + /** + * An adapter the allows a TunnelConnectListener to be used as an ordinary + * Listener. + */ + @SuppressWarnings("deprecation") + private static class TunnelConnectListenerAdapter implements Listener { + + /** + * The delegate listener for this adapter. + */ + private final TunnelConnectListener delegate; + + /** + * Constructs a new adapter that delivers events to the given delegate. + * + * @param delegate + * The delegate listener. + */ + TunnelConnectListenerAdapter(TunnelConnectListener delegate) { + this.delegate = delegate; + } + + /** + * Handles a TunnelConnectEvent by passing the event to the delegate listener. + * If the delegate returns false, the adapter throws a GuacamoleException + * to veto the tunnel connect event. All other event types are ignored. + * + * @param event + * An object that describes the event that occurred. + * + * @throws GuacamoleException + * If thrown by the delegate listener. + */ + @Override + public void handleEvent(Object event) throws GuacamoleException { + if (event instanceof TunnelConnectEvent) { + if (!delegate.tunnelConnected((TunnelConnectEvent) event)) { + throw new GuacamoleException("listener vetoed tunnel connection"); + } + } + } + + } + + /** + * An adapter the allows a TunnelCloseListener to be used as an ordinary + * Listener. + */ + @SuppressWarnings("deprecation") + private static class TunnelCloseListenerAdapter implements Listener { + + /** + * The delegate listener for this adapter. + */ + private final TunnelCloseListener delegate; + + /** + * Constructs a new adapter that delivers events to the given delegate. + * + * @param delegate + * The delegate listener. + */ + TunnelCloseListenerAdapter(TunnelCloseListener delegate) { + this.delegate = delegate; + } + + /** + * Handles a TunnelCloseEvent by passing the event to the delegate listener. + * If the delegate returns false, the adapter throws a GuacamoleException + * to veto the tunnel connect event. All other event types are ignored. + * + * @param event + * An object that describes the event that occurred. + * + * @throws GuacamoleException + * If thrown by the delegate listener. + */ + @Override + public void handleEvent(Object event) throws GuacamoleException { + if (event instanceof TunnelCloseEvent) { + if (!delegate.tunnelClosed((TunnelCloseEvent) event)) { + throw new GuacamoleException("listener vetoed tunnel close request"); + } + } + } + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java b/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java new file mode 100644 index 000000000..01fda5719 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java @@ -0,0 +1,107 @@ +/* + * 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.extension; + +import org.apache.guacamole.GuacamoleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; + +/** + * A utility for creating provider instances and logging unexpected outcomes + * with sufficient detail to allow debugging. + */ +class ProviderFactory { + + /** + * Logger used to log unexpected outcomes. + */ + private static final Logger logger = LoggerFactory.getLogger(ProviderFactory.class); + + /** + * Creates an instance of the specified provider class using the no-arg constructor. + * + * @param typeName + * The provider type name used for log messages; e.g. "authentication provider". + * + * @param providerClass + * The provider class to instantiate. + * + * @param + * The provider type. + * + * @return + * A provider instance or null if no instance was created due to error. + */ + static T newInstance(String typeName, Class providerClass) { + T instance = null; + + try { + // Attempt to instantiate the provider + instance = providerClass.getConstructor().newInstance(); + } + catch (NoSuchMethodException e) { + logger.error("The {} 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.", typeName); + logger.debug("{} is missing a default constructor.", + providerClass.getName(), e); + } + catch (SecurityException e) { + logger.error("The Java security manager is preventing extensions " + + "from being loaded. Please check the configuration of Java or your " + + "servlet container."); + logger.debug("Creation of {} disallowed by security manager.", + providerClass.getName(), e); + } + catch (InstantiationException e) { + logger.error("The {} 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.", typeName); + logger.debug("{} cannot be instantiated.", providerClass.getName(), e); + } + catch (IllegalAccessException e) { + logger.error("The {} 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 {} is not public.", typeName, e); + } + catch (IllegalArgumentException e) { + logger.error("The {} 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.", typeName); + logger.debug("Default constructor of {} cannot accept zero arguments.", + providerClass.getName(), 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("{} extension failed to start: {}", typeName, cause.getMessage()); + logger.debug("{} instantiation failed.", providerClass.getName(), e); + } + + return instance; + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java index cab4d973b..587d8338e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest; +import org.apache.guacamole.rest.event.ListenerService; import org.apache.guacamole.rest.session.UserContextResourceFactory; import org.apache.guacamole.rest.session.SessionRESTService; import com.google.inject.Scopes; @@ -76,6 +77,7 @@ public class RESTServiceModule extends ServletModule { bind(TokenSessionMap.class).toInstance(tokenSessionMap); // Bind low-level services + bind(ListenerService.class); bind(AuthenticationService.class); bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index 31abee5f5..b18f00f4a 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -24,9 +24,11 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; + import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleUnauthorizedException; +import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -35,7 +37,9 @@ import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException; import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; -import org.apache.guacamole.GuacamoleSession; +import org.apache.guacamole.net.event.AuthenticationFailureEvent; +import org.apache.guacamole.net.event.AuthenticationSuccessEvent; +import org.apache.guacamole.rest.event.ListenerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +78,12 @@ public class AuthenticationService { @Inject private AuthTokenGenerator authTokenGenerator; + /** + * The service to use to notify registered authentication listeners. + */ + @Inject + private ListenerService listenerService; + /** * Regular expression which matches any IPv4 address. */ @@ -207,6 +217,47 @@ public class AuthenticationService { } + /** + * Notify all bound listeners that a successful authentication + * has occurred. + * + * @param authenticatedUser + * The user that was successfully authenticated. + * + * @param session + * The existing session for the user (if any). + * + * @throws GuacamoleException + * If thrown by a listener. + */ + private void fireAuthenticationSuccessEvent( + AuthenticatedUser authenticatedUser, GuacamoleSession session) + throws GuacamoleException { + + UserContext userContext = null; + if (session != null) { + userContext = session.getUserContext( + authenticatedUser.getAuthenticationProvider().getIdentifier()); + } + + listenerService.handleEvent(new AuthenticationSuccessEvent( + userContext, authenticatedUser.getCredentials())); + } + + /** + * Notify all bound listeners that an authentication attempt has failed. + * + * @param credentials + * The credentials that failed to authenticate. + * + * @throws GuacamoleException + * If thrown by a listener. + */ + private void fireAuthenticationFailedEvent(Credentials credentials) + throws GuacamoleException { + listenerService.handleEvent(new AuthenticationFailureEvent(credentials)); + } + /** * Returns the AuthenticatedUser associated with the given session and * credentials, performing a fresh authentication and creating a new @@ -232,11 +283,17 @@ public class AuthenticationService { try { // Re-authenticate user if session exists - if (existingSession != null) - return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials); + if (existingSession != null) { + AuthenticatedUser updatedUser = updateAuthenticatedUser( + existingSession.getAuthenticatedUser(), credentials); + fireAuthenticationSuccessEvent(updatedUser, existingSession); + return updatedUser; + } // Otherwise, attempt authentication as a new user AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials); + fireAuthenticationSuccessEvent(authenticatedUser, null); + if (logger.isInfoEnabled()) logger.info("User \"{}\" successfully authenticated from {}.", authenticatedUser.getIdentifier(), @@ -249,6 +306,8 @@ public class AuthenticationService { // Log and rethrow any authentication errors catch (GuacamoleException e) { + fireAuthenticationFailedEvent(credentials); + // Get request and username for sake of logging HttpServletRequest request = credentials.getRequest(); String username = credentials.getUsername(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java index e06e338db..3a987e544 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java @@ -20,6 +20,7 @@ package org.apache.guacamole.rest.connection; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -131,6 +132,11 @@ public class APIConnectionWrapper implements Connection { throw new UnsupportedOperationException("Operation not supported."); } + @Override + public Date getLastActive() { + return null; + } + @Override public List getHistory() throws GuacamoleException { return Collections.emptyList(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java b/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java new file mode 100644 index 000000000..e92cc8a66 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java @@ -0,0 +1,57 @@ +/* + * 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.rest.event; + +import java.util.List; +import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.event.listener.Listener; + +/** + * A service used to notify listeners registered by extensions when events of + * interest occur. + */ +public class ListenerService implements Listener { + + /** + * The collection of registered listeners. + */ + @Inject + private List listeners; + + /** + * Notifies registered listeners than an event has occurred. Notification continues + * until a given listener throws a GuacamoleException or other runtime exception, or + * until all listeners have been notified. + * + * @param event + * An object that describes the event that has occurred. + * + * @throws GuacamoleException + * If a registered listener throws a GuacamoleException. + */ + @Override + public void handleEvent(Object event) throws GuacamoleException { + for (final Listener listener : listeners) { + listener.handleEvent(event); + } + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java index 2a4c81878..d2281d037 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java @@ -21,7 +21,7 @@ package org.apache.guacamole.rest.history; import javax.ws.rs.core.Response; import org.apache.guacamole.GuacamoleClientException; -import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.rest.APIException; /** @@ -38,7 +38,7 @@ public class APIConnectionRecordSortPredicate { /** * All possible property name strings and their corresponding - * ConnectionRecordSet.SortableProperty values. + * ActivityRecordSet.SortableProperty values. */ public enum SortableProperty { @@ -46,24 +46,24 @@ public class APIConnectionRecordSortPredicate { * The date that the connection associated with the connection record * began (connected). */ - startDate(ConnectionRecordSet.SortableProperty.START_DATE); + startDate(ActivityRecordSet.SortableProperty.START_DATE); /** - * The ConnectionRecordSet.SortableProperty that this property name + * The ActivityRecordSet.SortableProperty that this property name * string represents. */ - public final ConnectionRecordSet.SortableProperty recordProperty; + public final ActivityRecordSet.SortableProperty recordProperty; /** * Creates a new SortableProperty which associates the property name * string (identical to its own name) with the given - * ConnectionRecordSet.SortableProperty value. + * ActivityRecordSet.SortableProperty value. * * @param recordProperty - * The ConnectionRecordSet.SortableProperty value to associate with + * The ActivityRecordSet.SortableProperty value to associate with * the new SortableProperty. */ - SortableProperty(ConnectionRecordSet.SortableProperty recordProperty) { + SortableProperty(ActivityRecordSet.SortableProperty recordProperty) { this.recordProperty = recordProperty; } @@ -72,7 +72,7 @@ public class APIConnectionRecordSortPredicate { /** * The property to use when sorting ConnectionRecords. */ - private ConnectionRecordSet.SortableProperty property; + private ActivityRecordSet.SortableProperty property; /** * Whether the requested sort order is descending (true) or ascending @@ -102,7 +102,7 @@ public class APIConnectionRecordSortPredicate { value = value.substring(DESCENDING_PREFIX.length()); } - // Parse sorting property into ConnectionRecordSet.SortableProperty + // Parse sorting property into ActivityRecordSet.SortableProperty try { this.property = SortableProperty.valueOf(value).recordProperty; } @@ -118,15 +118,15 @@ public class APIConnectionRecordSortPredicate { } /** - * Returns the SortableProperty defined by ConnectionRecordSet which + * Returns the SortableProperty defined by ActivityRecordSet which * represents the property requested. * * @return - * The ConnectionRecordSet.SortableProperty which refers to the same + * The ActivityRecordSet.SortableProperty which refers to the same * property as the string originally provided when this * APIConnectionRecordSortPredicate was created. */ - public ConnectionRecordSet.SortableProperty getProperty() { + public ActivityRecordSet.SortableProperty getProperty() { return property; } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java index 49695c4d3..53a8cdba8 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java @@ -28,8 +28,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.ConnectionRecord; -import org.apache.guacamole.net.auth.ConnectionRecordSet; import org.apache.guacamole.net.auth.UserContext; /** @@ -92,7 +92,7 @@ public class HistoryResource { throws GuacamoleException { // Retrieve overall connection history - ConnectionRecordSet history = userContext.getConnectionHistory(); + ActivityRecordSet history = userContext.getConnectionHistory(); // Restrict to records which contain the specified strings for (String required : requiredContents) { diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUserWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUserWrapper.java index 35769faa7..c4b85f9ae 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUserWrapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUserWrapper.java @@ -19,9 +19,13 @@ package org.apache.guacamole.rest.user; +import java.util.Collections; +import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.permission.SystemPermissionSet; @@ -112,4 +116,14 @@ public class APIUserWrapper implements User { throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access."); } + @Override + public Date getLastActive() { + return null; + } + + @Override + public List getHistory() throws GuacamoleException { + return Collections.emptyList(); + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index 628386916..b029a3050 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -29,10 +29,14 @@ import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.TunnelCloseEvent; +import org.apache.guacamole.net.event.TunnelConnectEvent; import org.apache.guacamole.rest.auth.AuthenticationService; import org.apache.guacamole.protocol.GuacamoleClientInformation; +import org.apache.guacamole.rest.event.ListenerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +61,58 @@ public class TunnelRequestService { @Inject private AuthenticationService authenticationService; + /** + * A service for notifying listeners about tunnel connect/closed events. + */ + @Inject + private ListenerService listenerService; + + /** + * Notifies bound listeners that a new tunnel has been connected. + * Listeners may veto a connected tunnel by throwing any GuacamoleException. + * + * @param userContext + * The UserContext associated with the user for whom the tunnel is + * being created. + * + * @param credentials + * Credentials that authenticate the user. + * + * @param tunnel + * The tunnel that was connected. + * + * @throws GuacamoleException + * If thrown by a listener or if any listener vetoes the connected tunnel. + */ + private void fireTunnelConnectEvent(UserContext userContext, + Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { + listenerService.handleEvent(new TunnelConnectEvent(userContext, credentials, tunnel)); + } + + /** + * Notifies bound listeners that a tunnel is to be closed. + * Listeners are allowed to veto a request to close a tunnel by throwing any + * GuacamoleException. + * + * @param userContext + * The UserContext associated with the user for whom the tunnel is + * being created. + * + * @param credentials + * Credentials that authenticate the user. + * + * @param tunnel + * The tunnel that was connected. + * + * @throws GuacamoleException + * If thrown by a listener. + */ + private void fireTunnelClosedEvent(UserContext userContext, + Credentials credentials, GuacamoleTunnel tunnel) + throws GuacamoleException { + listenerService.handleEvent(new TunnelCloseEvent(userContext, credentials, tunnel)); + } + /** * Reads and returns the client information provided within the given * request. @@ -226,7 +282,7 @@ public class TunnelRequestService { * @throws GuacamoleException * If an error occurs while obtaining the tunnel. */ - protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel, + protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleTunnel tunnel, final String authToken, final GuacamoleSession session, final UserContext context, final TunnelRequest.Type type, final String id) throws GuacamoleException { @@ -243,6 +299,10 @@ public class TunnelRequestService { @Override public void close() throws GuacamoleException { + // notify listeners to allow close request to be vetoed + fireTunnelClosedEvent(context, + session.getAuthenticatedUser().getCredentials(), tunnel); + long connectionEndTime = System.currentTimeMillis(); long duration = connectionEndTime - connectionStartTime; @@ -328,6 +388,10 @@ public class TunnelRequestService { // Create connected tunnel using provided connection ID and client information GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); + // Notify listeners to allow connection to be vetoed + fireTunnelConnectEvent(userContext, + session.getAuthenticatedUser().getCredentials(), tunnel); + // Associate tunnel with session return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id); diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index f0529d8b8..06abb048a 100644 --- a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js @@ -44,6 +44,14 @@ angular.module('clipboard').factory('clipboardService', ['$injector', */ var CLIPBOARD_READ_DELAY = 100; + /** + * The promise associated with the current pending clipboard read attempt. + * If no clipboard read is active, this will be null. + * + * @type Promise. + */ + var pendingRead = null; + /** * Reference to the window.document object. * @@ -57,10 +65,9 @@ angular.module('clipboard').factory('clipboardService', ['$injector', * * @type Element */ - var clipboardContent = document.createElement('div'); + var clipboardContent = document.createElement('textarea'); // Ensure clipboard target is selectable but not visible - clipboardContent.setAttribute('contenteditable', 'true'); clipboardContent.className = 'clipboard-service-target'; // Add clipboard target to DOM @@ -79,6 +86,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', }; // Prevent events generated due to execCommand() from disturbing external things + clipboardContent.addEventListener('cut', stopEventPropagation); clipboardContent.addEventListener('copy', stopEventPropagation); clipboardContent.addEventListener('paste', stopEventPropagation); @@ -136,14 +144,23 @@ angular.module('clipboard').factory('clipboardService', ['$injector', */ var selectAll = function selectAll(element) { - // Generate a range which selects all nodes within the given element - var range = document.createRange(); - range.selectNodeContents(element); + // Use the select() function defined for input elements, if available + if (element.select) + element.select(); - // Replace any current selection with the generated range - var selection = $window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); + // Fallback to manual manipulation of the selection + else { + + // Generate a range which selects all nodes within the given element + var range = document.createRange(); + range.selectNodeContents(element); + + // Replace any current selection with the generated range + var selection = $window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + } }; @@ -167,7 +184,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', // Copy the given value into the clipboard DOM element if (typeof data.data === 'string') - clipboardContent.textContent = data.data; + clipboardContent.value = data.data; else { clipboardContent.innerHTML = ''; var img = document.createElement('img'); @@ -176,6 +193,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', } // Select all data within the clipboard target + clipboardContent.focus(); selectAll(clipboardContent); // Attempt to copy data from clipboard element into local clipboard @@ -389,8 +407,16 @@ angular.module('clipboard').factory('clipboardService', ['$injector', */ service.getLocalClipboard = function getLocalClipboard() { + // If the clipboard is already being read, do not overlap the read + // attempts; instead share the result across all requests + if (pendingRead) + return pendingRead; + var deferred = $q.defer(); + // Mark read attempt as in progress + pendingRead = deferred.promise; + // Wait for the next event queue run before attempting to read // clipboard data (in case the copy/cut has not yet completed) $window.setTimeout(function deferredClipboardRead() { @@ -399,53 +425,84 @@ angular.module('clipboard').factory('clipboardService', ['$injector', var originalElement = document.activeElement; pushSelection(); + /** + * Attempts to paste the clipboard contents into the + * currently-focused element. The promise related to the current + * attempt to read the clipboard will be resolved or rejected + * depending on whether the attempt to paste succeeds. + */ + var performPaste = function performPaste() { + + // Attempt paste local clipboard into clipboard DOM element + if (document.execCommand('paste')) { + + // If the pasted data is a single image, resolve with a blob + // containing that image + var currentImage = service.getImageContent(clipboardContent); + if (currentImage) { + + // Convert the image's data URL into a blob + var blob = service.parseDataURL(currentImage); + if (blob) { + deferred.resolve(new ClipboardData({ + type : blob.type, + data : blob + })); + } + + // Reject if conversion fails + else + deferred.reject(); + + } // end if clipboard is an image + + // Otherwise, assume the clipboard contains plain text + else + deferred.resolve(new ClipboardData({ + type : 'text/plain', + data : clipboardContent.value + })); + + } + + // Otherwise, reading from the clipboard has failed + else + deferred.reject(); + + }; + + // Clean up event listener and selection once the paste attempt has + // completed + deferred.promise['finally'](function cleanupReadAttempt() { + + // Do not use future changes in focus + clipboardContent.removeEventListener('focus', performPaste); + + // Unfocus the clipboard DOM event to avoid mobile keyboard opening, + // restoring whichever element was originally focused + clipboardContent.blur(); + originalElement.focus(); + popSelection(); + + // No read is pending any longer + pendingRead = null; + + }); + + // Ensure clipboard element is blurred (and that the "focus" event + // will fire) + clipboardContent.blur(); + clipboardContent.addEventListener('focus', performPaste); + // Clear and select the clipboard DOM element - clipboardContent.innerHTML = ''; + clipboardContent.value = ''; clipboardContent.focus(); selectAll(clipboardContent); - // Attempt paste local clipboard into clipboard DOM element - if (document.activeElement === clipboardContent && document.execCommand('paste')) { - - // If the pasted data is a single image, resolve with a blob - // containing that image - var currentImage = service.getImageContent(clipboardContent); - if (currentImage) { - - // Convert the image's data URL into a blob - var blob = service.parseDataURL(currentImage); - if (blob) { - deferred.resolve(new ClipboardData({ - type : blob.type, - data : blob - })); - } - - // Reject if conversion fails - else - deferred.reject(); - - } // end if clipboard is an image - - // Otherwise, assume the clipboard contains plain text - else - deferred.resolve(new ClipboardData({ - type : 'text/plain', - data : clipboardContent.textContent - })); - - } - - // Otherwise, reading from the clipboard has failed - else + // If focus failed to be set, we cannot read the clipboard + if (document.activeElement !== clipboardContent) deferred.reject(); - // Unfocus the clipboard DOM event to avoid mobile keyboard opening, - // restoring whichever element was originally focused - clipboardContent.blur(); - originalElement.focus(); - popSelection(); - }, CLIPBOARD_READ_DELAY); return deferred.promise; diff --git a/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css b/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css index b4513e1b0..3d61c2be3 100644 --- a/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css +++ b/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css @@ -52,10 +52,10 @@ .clipboard-service-target { position: fixed; - left: -1px; - right: -1px; - width: 1px; - height: 1px; + left: -1em; + right: -1em; + width: 1em; + height: 1em; white-space: pre; overflow: hidden; } diff --git a/guacamole/src/main/webapp/app/form/services/formService.js b/guacamole/src/main/webapp/app/form/services/formService.js index c117bbf7f..64a171310 100644 --- a/guacamole/src/main/webapp/app/form/services/formService.js +++ b/guacamole/src/main/webapp/app/form/services/formService.js @@ -244,9 +244,16 @@ angular.module('form').provider('formService', function formServiceProvider() { } // If no raw HTML template is provided, retrieve template from URL - else + else if (fieldType.templateUrl) templateRequest = $templateRequest(fieldType.templateUrl); + // Otherwise, use empty template + else { + var emptyTemplate= $q.defer(); + emptyTemplate.resolve(''); + templateRequest = emptyTemplate.promise; + } + // Defer compilation of template pending successful retrieval var compiledTemplate = $q.defer(); diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 0f4d78dc5..3a9230bf7 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -137,8 +137,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Attempt to read the clipboard if it may have changed $window.addEventListener('load', checkClipboard, true); - $window.addEventListener('copy', checkClipboard, true); - $window.addEventListener('cut', checkClipboard, true); + $window.addEventListener('copy', checkClipboard); + $window.addEventListener('cut', checkClipboard); $window.addEventListener('focus', function focusGained(e) { // Only recheck clipboard if it's the window itself that gained focus diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js index 0a3ed6e9d..d906c3dc0 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -192,6 +192,9 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe $scope.canChangePassword = PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, username); + }) + .error(function permissionsFailed(error) { + $scope.canChangePassword = false; }); /** diff --git a/pom.xml b/pom.xml index 41317a316..55228f5aa 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,7 @@ extensions/guacamole-auth-jdbc extensions/guacamole-auth-ldap extensions/guacamole-auth-noauth + extensions/guacamole-auth-openid doc/guacamole-example