diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java index 53935e62f..93cc7f7a3 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java @@ -137,4 +137,15 @@ public abstract class JDBCEnvironment extends LocalEnvironment { */ public abstract PasswordPolicy getPasswordPolicy(); + /** + * Returns whether the database supports recursive queries. Many database + * engines support recursive queries through CTEs. If recursive queries are + * not supported, queries that are intended to be recursive may need to be + * invoked multiple times to retrieve the same data. + * + * @return + * true if the database supports recursive queries, false otherwise. + */ + public abstract boolean isRecursiveQuerySupported(); + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityMapper.java index 31efad5fc..53b029091 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityMapper.java @@ -47,6 +47,12 @@ public interface EntityMapper { * member, taking into account the given collection of known group * memberships which are not necessarily defined within the database. * + * NOTE: This query is expected to handle recursion through the membership + * graph on its own. If the database engine does not support recursive + * queries (isRecursiveQuerySupported() of JDBCEnvironment returns false), + * then this query will only return one level of depth past the effective + * groups given and will need to be invoked multiple times. + * * @param entity * The entity whose effective groups should be returned. * diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityService.java index fa71feee0..1e40bb0ae 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/EntityService.java @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.jdbc.base; import com.google.inject.Inject; import java.util.Collection; import java.util.Set; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; /** * Service which provides convenience methods for creating, retrieving, and @@ -29,6 +30,12 @@ import java.util.Set; */ public class EntityService { + /** + * The Guacamole server environment. + */ + @Inject + private JDBCEnvironment environment; + /** * Mapper for Entity model objects. */ @@ -59,7 +66,23 @@ public class EntityService { */ public Set retrieveEffectiveGroups(ModeledPermissions entity, Collection effectiveGroups) { - return entityMapper.selectEffectiveGroupIdentifiers(entity.getModel(), effectiveGroups); + + // Retrieve the effective user groups of the given entity, recursively if possible + Set identifiers = entityMapper.selectEffectiveGroupIdentifiers(entity.getModel(), effectiveGroups); + + // If the set of user groups retrieved was not produced recursively, + // manually repeat the query to expand the set until all effective + // groups have been found + if (!environment.isRecursiveQuerySupported() && !identifiers.isEmpty()) { + Set previousIdentifiers; + do { + previousIdentifiers = identifiers; + identifiers = entityMapper.selectEffectiveGroupIdentifiers(entity.getModel(), previousIdentifiers); + } while (identifiers.size() > previousIdentifiers.size()); + } + + return identifiers; + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLEnvironment.java index dc676db89..062d6dfa4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLEnvironment.java @@ -225,5 +225,10 @@ public class MySQLEnvironment extends JDBCEnvironment { public String getMySQLPassword() throws GuacamoleException { return getRequiredProperty(MySQLGuacamoleProperties.MYSQL_PASSWORD); } - + + @Override + public boolean isRecursiveQuerySupported() { + return false; // Only very recent versions of MySQL / MariaDB support recursive queries through CTEs + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/PostgreSQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/PostgreSQLEnvironment.java index da0caead2..d5d259e6f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/PostgreSQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/PostgreSQLEnvironment.java @@ -242,5 +242,10 @@ public class PostgreSQLEnvironment extends JDBCEnvironment { public String getPostgreSQLPassword() throws GuacamoleException { return getRequiredProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_PASSWORD); } + + @Override + public boolean isRecursiveQuerySupported() { + return true; // All versions of PostgreSQL support recursive queries through CTEs + } } 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 index 20361e630..03f2cf865 100644 --- 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 @@ -250,5 +250,10 @@ public class SQLServerEnvironment extends JDBCEnvironment { SQLSERVER_DEFAULT_DRIVER ); } - + + @Override + public boolean isRecursiveQuerySupported() { + return true; // All versions of SQL Server support recursive queries through CTEs + } + }