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-sqlserver/.gitignore b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/.gitignore
new file mode 100644
index 000000000..42f4a1a64
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/.gitignore
@@ -0,0 +1,2 @@
+target/
+*~
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/pom.xml
new file mode 100644
index 000000000..82776f7d0
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/pom.xml
@@ -0,0 +1,128 @@
+
+
+
+
+ 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-mysqlmodules/guacamole-auth-jdbc-postgresql
+ modules/guacamole-auth-jdbc-sqlserver