diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 20a840fcc289..cb6a029e0491 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -881,6 +881,7 @@ jobs:
- suite-delta-lake-databricks143
- suite-delta-lake-databricks154
- suite-databricks-unity-http-hms
+ - suite-apache-ranger
- suite-gcs
- suite-clients
- suite-functions
diff --git a/core/trino-server/src/main/provisio/trino.xml b/core/trino-server/src/main/provisio/trino.xml
index 9b96d3d97f2e..6d290e42b81b 100644
--- a/core/trino-server/src/main/provisio/trino.xml
+++ b/core/trino-server/src/main/provisio/trino.xml
@@ -33,6 +33,12 @@
+
+
+
+
+
+
diff --git a/docs/src/main/sphinx/security.md b/docs/src/main/sphinx/security.md
index e80f32c5cc2b..b0c51440451b 100644
--- a/docs/src/main/sphinx/security.md
+++ b/docs/src/main/sphinx/security.md
@@ -51,6 +51,7 @@ security/group-file
security/built-in-system-access-control
security/file-system-access-control
security/opa-access-control
+security/apache-ranger-access-control
```
## Security inside the cluster
diff --git a/docs/src/main/sphinx/security/apache-ranger-access-control.md b/docs/src/main/sphinx/security/apache-ranger-access-control.md
new file mode 100644
index 000000000000..129682f53dd2
--- /dev/null
+++ b/docs/src/main/sphinx/security/apache-ranger-access-control.md
@@ -0,0 +1,164 @@
+# Apache Ranger access control
+
+The Apache Ranger access control plugin supports use of Apache Ranger policies to authorize data access in Trino on catalogs, schemas, tables, and columns. The plugin also supports column-masking, row-filtering and audit logging.
+
+## Requirements
+
+* Access to a Apache Ranger deployment with the desired authorization policies.
+* Access to an audit store using Solr, HDFS, Log4J, or S3 to save audit logs.
+* Apache Ranger 2.5.0 and greater include the required Trino service definition. Earlier versions of Apache Ranger require an update of the service definition available in the version [here](
+ https://github.com/apache/ranger/blob/ranger-2.5/agents-common/src/main/resources/service-defs/ranger-servicedef-trino.json).
+
+## Configuration
+
+To use only Ranger for access control, create the file `etc/access-control.properties` on the coordinator,
+with the following configuration, and configurations listed in the table below:
+
+```properties
+access-control.name=apache-ranger
+```
+
+
+To combine Ranger access control with file-based or other access control systems, create the file
+`etc/access-control.properties` on the coordinator, with the following configuration that lists
+multiple access control configuration file paths:
+
+```properties
+access-control.config-files=etc/trino/file-based.properties,etc/trino/apache-ranger.properties
+```
+
+Order the configuration files list in the desired order of the different systems
+for overall access control. Configure each access-control system in the
+specified files.
+
+The following table lists the configuration properties for the Ranger access control:
+
+:::{list-table} Apache Ranger access control configuration properties
+:widths: 30, 70
+:header-rows: 1
+
+* - Name
+ - Description
+* - `apache-ranger.service.name`
+ - Name of the service having policies to be enforced by the plugin
+* - `apache-ranger.plugin.config.resource`
+ - List of Ranger plugin configuration files, comma separated. Relative paths will be resolved dynamically by searching in the classpath.
+* - `apache-ranger.hadoop.config.resource`
+ - List of Hadoop configuration files, comma separated. Relative paths will be resolved dynamically by searching in the classpath.
+:::
+
+### ranger-trino-security.xml
+```
+
+
+
+ ranger.plugin.trino.policy.rest.url
+ https://ranger-hostname:6182
+ MANDATORY: a comma separated list of URLs to Apache Ranger instances in a deployment
+
+
+
+ ranger.plugin.trino.access.cluster.name
+
+ Name to identify the cluster running the Trino instance. This is recorded in audit logs generated by the plugin
+
+
+
+ ranger.plugin.trino.use.rangerGroups
+ false
+ Boolean flag to specify whether user-to-groups mapping should be obtained from in Apache Ranger. Default: false
+
+
+
+ ranger.plugin.trino.use.only.rangerGroups
+ false
+ Boolean flag. true: use only user-to-groups mapping from Apache Ranger; false: use user-to-groups mappings from Apache Ranger and Trino. Default: false
+
+
+
+ ranger.plugin.trino.super.users
+
+ Comma separated list of user names. Superusers will be authorized for all accesses, without requiring explicit policy grants.
+
+
+
+ ranger.plugin.trino.super.groups
+
+ Comma separated list of group names. Users in supergroups will be authorized for all accesses, without requiring explicit policy grants
+
+
+```
+
+### ranger-trino-audit.xml
+```
+
+
+
+ xasecure.audit.is.enabled
+ true
+ Boolean flag to specify if the plugin should generate access audit logs. Default: true
+
+
+
+ xasecure.audit.solr.is.enabled
+ false
+ Boolean flag to specify if audit logs should be stored in Solr. Default: false
+
+
+
+ xasecure.audit.solr.solr_url
+
+ URL to Solr deployment where the plugin should send access audits to
+
+
+```
+
+### ranger-policymgr-ssl.xml
+```
+
+
+
+
+ xasecure.policymgr.clientssl.keystore
+
+ Path to keystore file. Only required for two-way SSL. This property should not be included for one-way SSL
+
+
+
+ xasecure.policymgr.clientssl.keystore.type
+ jks
+ Type of keystore. Default: jks
+
+
+
+ xasecure.policymgr.clientssl.keystore.credential.file
+
+ Path to credential file for the keystore; the credential should be in alias sslKeyStore. Only required for two-way SSL. This property should not be included for one-way SSL
+
+
+
+ xasecure.policymgr.clientssl.truststore
+
+ Path to truststore file
+
+
+
+ xasecure.policymgr.clientssl.truststore.type
+ jks
+ Type of truststore. Default: jks
+
+
+
+ xasecure.policymgr.clientssl.truststore.credential.file
+
+ Path to credential file for the truststore; the credential should be in alias sslTrustStore
+
+
+```
+
+## Required policies
+
+* Users will need permission to execute queries in Trino. Without a policy in Apache Ranger to grant this permission, users will not be able to execute any query.
+ * To allow this, create a policy in Apache Ranger for `queryId` resource having value `*`, with `execute` permission for user `{USER}`.
+* Users will need permission to impersonate themselves in Trino. Without a policy in Apache Ranger to grant this permission, users will not be able to execute any query.
+ * To allow this, create a policy in Apache Ranger for `trinouser` resource having value `{USER}`, with `impersonate` permission for user `{USER}`.
diff --git a/docs/src/main/sphinx/security/built-in-system-access-control.md b/docs/src/main/sphinx/security/built-in-system-access-control.md
index 3ac4ca693fbd..f991e1e0538d 100644
--- a/docs/src/main/sphinx/security/built-in-system-access-control.md
+++ b/docs/src/main/sphinx/security/built-in-system-access-control.md
@@ -42,6 +42,9 @@ Trino offers the following built-in system access control implementations:
* - `opa`
- Use Open Policy Agent (OPA) for authorization. See
[](/security/opa-access-control).
+* - `ranger`
+ - Use Apache Ranger policies for authorization. See
+ [](/security/apache-ranger-access-control).
:::
If you want to limit access on a system level in any other way than the ones
diff --git a/docs/src/main/sphinx/security/overview.md b/docs/src/main/sphinx/security/overview.md
index f44568e7732c..a2887201e84f 100644
--- a/docs/src/main/sphinx/security/overview.md
+++ b/docs/src/main/sphinx/security/overview.md
@@ -116,6 +116,9 @@ To implement access control, use:
the catalog, schema, or table level.
- [](opa-access-control), where you use Open Policy Agent to make access control
decisions on a fined-grained level.
+- [](apache-ranger-access-control), where you use Apache Ranger to make fine-grained
+ access control decisions, apply dynamic row-filters and column-masking at
+ query execution time, and generate audit logs.
In addition, Trino {doc}`provides an API ` that
allows you to create a custom access control method, or to extend an existing
diff --git a/plugin/trino-apache-ranger/pom.xml b/plugin/trino-apache-ranger/pom.xml
new file mode 100644
index 000000000000..82902045679c
--- /dev/null
+++ b/plugin/trino-apache-ranger/pom.xml
@@ -0,0 +1,198 @@
+
+
+ 4.0.0
+
+
+ io.trino
+ trino-root
+ 466-SNAPSHOT
+ ../../pom.xml
+
+
+ trino-apache-ranger
+
+ trino-plugin
+ Trino - Apache Ranger access control
+ Trino - Apache Ranger access control
+
+
+ 3.2.2
+ 24.1.0
+ 1.2.0
+ 0.8.1
+ 1.19.3
+ 2.5.0
+
+
+
+
+ com.google.guava
+ guava
+
+
+ com.google.inject
+ guice
+
+
+ io.airlift
+ bootstrap
+
+
+ io.airlift
+ configuration
+
+
+ io.airlift
+ log
+
+
+ io.trino.hadoop
+ hadoop-apache
+
+
+ org.apache.ranger
+ ranger-plugins-common
+ ${dep.ranger.version}
+
+
+ commons-logging
+ commons-logging
+
+
+ org.apache.hadoop
+ hadoop-common
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ provided
+
+
+ io.opentelemetry
+ opentelemetry-api
+ provided
+
+
+ io.opentelemetry
+ opentelemetry-context
+ provided
+
+
+ io.trino
+ trino-spi
+ provided
+
+
+ com.amazonaws
+ aws-java-sdk-logs
+ ${dep.aws-sdk.version}
+ runtime
+
+
+ com.carrotsearch
+ hppc
+ ${dep.hppc.version}
+ runtime
+
+
+ com.google.code.gson
+ gson
+ runtime
+
+
+ commons-collections
+ commons-collections
+ ${dep.commons.collections.version}
+ runtime
+
+
+ org.apache.hadoop.thirdparty
+ hadoop-shaded-guava
+ ${dep.hadoop-shaded-guava.version}
+ runtime
+
+
+ org.apache.ranger
+ ranger-plugins-audit
+ ${dep.ranger.version}
+ runtime
+
+
+ commons-logging
+ commons-logging
+
+
+ org.apache.hadoop
+ hadoop-common
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ runtime
+
+
+ org.graalvm.js
+ js-scriptengine
+ ${dep.graalvm.version}
+ runtime
+
+
+ org.graalvm.polyglot
+ inspect-community
+ ${dep.graalvm.version}
+ pom
+ runtime
+
+
+ org.graalvm.polyglot
+ js-community
+ ${dep.graalvm.version}
+ pom
+ runtime
+
+
+ org.graalvm.polyglot
+ polyglot
+ ${dep.graalvm.version}
+ runtime
+
+
+ org.graalvm.polyglot
+ profiler-community
+ ${dep.graalvm.version}
+ pom
+ runtime
+
+
+ org.graalvm.sdk
+ graal-sdk
+ ${dep.graalvm.version}
+ runtime
+
+
+ org.graalvm.truffle
+ truffle-api
+ ${dep.graalvm.version}
+ runtime
+
+
+ io.airlift
+ junit-extensions
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/ApacheRangerPlugin.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/ApacheRangerPlugin.java
new file mode 100644
index 000000000000..0bd1800bcff4
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/ApacheRangerPlugin.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import io.trino.spi.Plugin;
+import io.trino.spi.security.SystemAccessControlFactory;
+
+import static java.util.Collections.singletonList;
+
+public class ApacheRangerPlugin
+ implements Plugin
+{
+ @Override
+ public Iterable getSystemAccessControlFactories()
+ {
+ return singletonList(new RangerSystemAccessControlFactory());
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java
new file mode 100644
index 000000000000..a35662d5dd83
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import com.google.common.collect.ImmutableList;
+import io.airlift.configuration.Config;
+import io.airlift.configuration.ConfigDescription;
+import io.airlift.configuration.validation.FileExists;
+
+import java.io.File;
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+public class RangerConfig
+{
+ private String serviceName;
+ private List pluginConfigResource = ImmutableList.of();
+ private List hadoopConfigResource = ImmutableList.of();
+
+ public String getServiceName()
+ {
+ return serviceName;
+ }
+
+ @Config("apache-ranger.service.name")
+ @ConfigDescription("Name of Ranger service containing policies to enforce")
+ public RangerConfig setServiceName(String serviceName)
+ {
+ this.serviceName = serviceName;
+ return this;
+ }
+
+ public List<@FileExists File> getPluginConfigResource()
+ {
+ return pluginConfigResource;
+ }
+
+ @Config("apache-ranger.plugin.config.resource")
+ @ConfigDescription("List of paths to Ranger plugin configuration files")
+ public RangerConfig setPluginConfigResource(List pluginConfigResource)
+ {
+ this.pluginConfigResource = pluginConfigResource.stream()
+ .map(File::new)
+ .collect(toImmutableList());
+ return this;
+ }
+
+ @Config("apache-ranger.hadoop.config.resource")
+ @ConfigDescription("List of paths to hadoop configuration files")
+ @SuppressWarnings("unused")
+ public RangerConfig setHadoopConfigResource(List hadoopConfigResource)
+ {
+ this.hadoopConfigResource = hadoopConfigResource.stream()
+ .map(File::new)
+ .collect(toImmutableList());
+ return this;
+ }
+
+ public List<@FileExists File> getHadoopConfigResource()
+ {
+ return hadoopConfigResource;
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java
new file mode 100644
index 000000000000..560ff24106d1
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java
@@ -0,0 +1,1017 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+import io.airlift.log.Logger;
+import io.trino.spi.QueryId;
+import io.trino.spi.connector.CatalogSchemaName;
+import io.trino.spi.connector.CatalogSchemaRoutineName;
+import io.trino.spi.connector.CatalogSchemaTableName;
+import io.trino.spi.connector.EntityKindAndName;
+import io.trino.spi.connector.EntityPrivilege;
+import io.trino.spi.connector.SchemaTableName;
+import io.trino.spi.eventlistener.EventListener;
+import io.trino.spi.function.SchemaFunctionName;
+import io.trino.spi.security.Identity;
+import io.trino.spi.security.Privilege;
+import io.trino.spi.security.SystemAccessControl;
+import io.trino.spi.security.SystemSecurityContext;
+import io.trino.spi.security.TrinoPrincipal;
+import io.trino.spi.security.ViewExpression;
+import io.trino.spi.type.Type;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+
+import java.io.File;
+import java.net.URL;
+import java.security.Principal;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.ALTER;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.CREATE;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.DELETE;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.DROP;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.EXECUTE;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.IMPERSONATE;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.INSERT;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.READ_SYSINFO;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.SELECT;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.SHOW;
+import static io.trino.plugin.ranger.RangerTrinoAccessType.WRITE_SYSINFO;
+import static io.trino.plugin.ranger.RangerTrinoAccessType._ANY;
+import static io.trino.spi.security.AccessDeniedException.denyAddColumn;
+import static io.trino.spi.security.AccessDeniedException.denyAlterColumn;
+import static io.trino.spi.security.AccessDeniedException.denyCommentColumn;
+import static io.trino.spi.security.AccessDeniedException.denyCommentTable;
+import static io.trino.spi.security.AccessDeniedException.denyCommentView;
+import static io.trino.spi.security.AccessDeniedException.denyCreateCatalog;
+import static io.trino.spi.security.AccessDeniedException.denyCreateFunction;
+import static io.trino.spi.security.AccessDeniedException.denyCreateMaterializedView;
+import static io.trino.spi.security.AccessDeniedException.denyCreateSchema;
+import static io.trino.spi.security.AccessDeniedException.denyCreateTable;
+import static io.trino.spi.security.AccessDeniedException.denyCreateView;
+import static io.trino.spi.security.AccessDeniedException.denyCreateViewWithSelect;
+import static io.trino.spi.security.AccessDeniedException.denyDeleteTable;
+import static io.trino.spi.security.AccessDeniedException.denyDropCatalog;
+import static io.trino.spi.security.AccessDeniedException.denyDropColumn;
+import static io.trino.spi.security.AccessDeniedException.denyDropFunction;
+import static io.trino.spi.security.AccessDeniedException.denyDropMaterializedView;
+import static io.trino.spi.security.AccessDeniedException.denyDropSchema;
+import static io.trino.spi.security.AccessDeniedException.denyDropTable;
+import static io.trino.spi.security.AccessDeniedException.denyDropView;
+import static io.trino.spi.security.AccessDeniedException.denyExecuteProcedure;
+import static io.trino.spi.security.AccessDeniedException.denyExecuteQuery;
+import static io.trino.spi.security.AccessDeniedException.denyExecuteTableProcedure;
+import static io.trino.spi.security.AccessDeniedException.denyImpersonateUser;
+import static io.trino.spi.security.AccessDeniedException.denyInsertTable;
+import static io.trino.spi.security.AccessDeniedException.denyReadSystemInformationAccess;
+import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView;
+import static io.trino.spi.security.AccessDeniedException.denyRenameColumn;
+import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView;
+import static io.trino.spi.security.AccessDeniedException.denyRenameSchema;
+import static io.trino.spi.security.AccessDeniedException.denyRenameTable;
+import static io.trino.spi.security.AccessDeniedException.denyRenameView;
+import static io.trino.spi.security.AccessDeniedException.denySelectColumns;
+import static io.trino.spi.security.AccessDeniedException.denySetCatalogSessionProperty;
+import static io.trino.spi.security.AccessDeniedException.denySetMaterializedViewProperties;
+import static io.trino.spi.security.AccessDeniedException.denySetSchemaAuthorization;
+import static io.trino.spi.security.AccessDeniedException.denySetSystemSessionProperty;
+import static io.trino.spi.security.AccessDeniedException.denySetTableAuthorization;
+import static io.trino.spi.security.AccessDeniedException.denySetTableProperties;
+import static io.trino.spi.security.AccessDeniedException.denySetUser;
+import static io.trino.spi.security.AccessDeniedException.denySetViewAuthorization;
+import static io.trino.spi.security.AccessDeniedException.denyShowColumns;
+import static io.trino.spi.security.AccessDeniedException.denyShowCreateFunction;
+import static io.trino.spi.security.AccessDeniedException.denyShowCreateSchema;
+import static io.trino.spi.security.AccessDeniedException.denyShowCreateTable;
+import static io.trino.spi.security.AccessDeniedException.denyShowFunctions;
+import static io.trino.spi.security.AccessDeniedException.denyShowSchemas;
+import static io.trino.spi.security.AccessDeniedException.denyShowTables;
+import static io.trino.spi.security.AccessDeniedException.denyTruncateTable;
+import static io.trino.spi.security.AccessDeniedException.denyUpdateTableColumns;
+import static io.trino.spi.security.AccessDeniedException.denyWriteSystemInformationAccess;
+import static java.util.Objects.requireNonNullElse;
+import static java.util.function.Predicate.not;
+
+public class RangerSystemAccessControl
+ implements SystemAccessControl
+{
+ private static final Logger LOG = Logger.get(RangerSystemAccessControl.class);
+
+ public static final String RANGER_TRINO_SERVICETYPE = "trino";
+ public static final String RANGER_TRINO_APPID = "trino";
+
+ private final RangerBasePlugin rangerPlugin;
+ private final RangerTrinoEventListener eventListener = new RangerTrinoEventListener();
+
+ @Inject
+ public RangerSystemAccessControl(RangerConfig config)
+ throws Exception
+ {
+ checkArgument(!isNullOrEmpty(config.getServiceName()), "apache-ranger.service.name is not configured");
+
+ Configuration hadoopConf = new Configuration();
+
+ for (File configPath : config.getHadoopConfigResource()) {
+ URL url = configPath.toURI().toURL();
+ LOG.info("Loading Hadoop config %s from url %s", configPath, url);
+ hadoopConf.addResource(url);
+ }
+
+ UserGroupInformation.setConfiguration(hadoopConf);
+ RangerPluginConfig pluginConfig = new RangerPluginConfig(RANGER_TRINO_SERVICETYPE, config.getServiceName(), RANGER_TRINO_APPID, null, null, null);
+
+ for (File configPath : config.getPluginConfigResource()) {
+ pluginConfig.addResourceIfReadable(configPath.getAbsolutePath());
+ }
+
+ rangerPlugin = new RangerBasePlugin(pluginConfig);
+ rangerPlugin.init();
+ rangerPlugin.setResultProcessor(new RangerDefaultAuditHandler());
+ }
+
+ @Override
+ public void checkCanImpersonateUser(Identity identity, String userName)
+ {
+ if (!hasPermission(RangerTrinoResource.forUser(userName), identity, null, IMPERSONATE, "ImpersonateUser")) {
+ denyImpersonateUser(identity.getUser(), userName);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public void checkCanSetUser(Optional principal, String userName)
+ {
+ if (!hasPermission(RangerTrinoResource.forUser(userName), principal, null, IMPERSONATE, "SetUser")) {
+ denySetUser(principal, userName);
+ }
+ }
+
+ @Override
+ public void checkCanExecuteQuery(Identity identity, QueryId queryId)
+ {
+ if (!hasPermission(RangerTrinoResource.forQueryId(queryId.getId()), identity, queryId, EXECUTE, "ExecuteQuery")) {
+ denyExecuteQuery();
+ }
+ }
+
+ @Override
+ public void checkCanViewQueryOwnedBy(Identity identity, Identity queryOwner)
+ {
+ if (!hasPermission(RangerTrinoResource.forUser(queryOwner.getUser()), identity, null, IMPERSONATE, "ViewQueryOwnedBy")) {
+ denyImpersonateUser(identity.getUser(), queryOwner.getUser());
+ }
+ }
+
+ @Override
+ public Collection filterViewQueryOwnedBy(Identity identity, Collection queryOwners)
+ {
+ Set toExclude = new HashSet<>();
+
+ for (Identity queryOwner : queryOwners) {
+ if (!hasPermissionForFilter(RangerTrinoResource.forUser(queryOwner.getUser()), identity, null, IMPERSONATE, "filterViewQueryOwnedBy")) {
+ toExclude.add(queryOwner);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return queryOwners;
+ }
+
+ return queryOwners.stream().filter(not(toExclude::contains)).collect(Collectors.toList());
+ }
+
+ @Override
+ public void checkCanKillQueryOwnedBy(Identity identity, Identity queryOwner)
+ {
+ if (!hasPermission(RangerTrinoResource.forUser(queryOwner.getUser()), identity, null, IMPERSONATE, "KillQueryOwnedBy")) {
+ denyImpersonateUser(identity.getUser(), queryOwner.getUser());
+ }
+ }
+
+ @Override
+ public void checkCanReadSystemInformation(Identity identity)
+ {
+ if (!hasPermission(RangerTrinoResource.forSystemInformation(), identity, null, READ_SYSINFO, "ReadSystemInformation")) {
+ denyReadSystemInformationAccess();
+ }
+ }
+
+ @Override
+ public void checkCanWriteSystemInformation(Identity identity)
+ {
+ if (!hasPermission(RangerTrinoResource.forSystemInformation(), identity, null, WRITE_SYSINFO, "WriteSystemInformation")) {
+ denyWriteSystemInformationAccess();
+ }
+ }
+
+ @Override
+ public void checkCanSetSystemSessionProperty(Identity identity, QueryId queryId, String propertyName)
+ {
+ if (!hasPermission(RangerTrinoResource.forSystemProperty(propertyName), identity, queryId, ALTER, "SetSystemSessionProperty")) {
+ denySetSystemSessionProperty(propertyName);
+ }
+ }
+
+ @Override
+ public boolean canAccessCatalog(SystemSecurityContext context, String catalogName)
+ {
+ return hasPermission(RangerTrinoResource.forCatalog(catalogName), context, _ANY, "AccessCatalog");
+ }
+
+ @Override
+ public void checkCanCreateCatalog(SystemSecurityContext context, String catalogName)
+ {
+ if (!hasPermission(RangerTrinoResource.forCatalog(catalogName), context, CREATE, "CreateCatalog")) {
+ denyCreateCatalog(catalogName);
+ }
+ }
+
+ @Override
+ public void checkCanDropCatalog(SystemSecurityContext context, String catalogName)
+ {
+ if (!hasPermission(RangerTrinoResource.forCatalog(catalogName), context, DROP, "DropCatalog")) {
+ denyDropCatalog(catalogName);
+ }
+ }
+
+ @Override
+ public void checkCanSetCatalogSessionProperty(SystemSecurityContext context, String catalogName, String propertyName)
+ {
+ if (!hasPermission(RangerTrinoResource.forSessionProperty(catalogName, propertyName), context, ALTER, "SetCatalogSessionProperty")) {
+ denySetCatalogSessionProperty(catalogName, propertyName);
+ }
+ }
+
+ @Override
+ public Set filterCatalogs(SystemSecurityContext context, Set catalogs)
+ {
+ Set toExclude = new HashSet<>();
+
+ for (String catalog : catalogs) {
+ if (!hasPermissionForFilter(RangerTrinoResource.forCatalog(catalog), context, _ANY, "filterCatalogs")) {
+ toExclude.add(catalog);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return catalogs;
+ }
+
+ return catalogs.stream().filter(not(toExclude::contains)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void checkCanCreateSchema(SystemSecurityContext context, CatalogSchemaName schema, Map properties)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, CREATE, "CreateSchema")) {
+ denyCreateSchema(schema.getSchemaName());
+ }
+ }
+
+ @Override
+ public void checkCanDropSchema(SystemSecurityContext context, CatalogSchemaName schema)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, DROP, "DropSchema")) {
+ denyDropSchema(schema.getSchemaName());
+ }
+ }
+
+ @Override
+ public void checkCanRenameSchema(SystemSecurityContext context, CatalogSchemaName schema, String newSchemaName)
+ {
+ boolean isAllowed = hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, ALTER, "RenameSchema:source") &&
+ hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), newSchemaName), context, ALTER, "RenameSchema:target");
+
+ if (!isAllowed) {
+ denyRenameSchema(schema.getSchemaName(), newSchemaName);
+ }
+ }
+
+ @Override
+ public void checkCanSetSchemaAuthorization(SystemSecurityContext context, CatalogSchemaName schema, TrinoPrincipal principal)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, ALTER, "SetSchemaAuthorization")) {
+ denySetSchemaAuthorization(schema.getSchemaName(), principal);
+ }
+ }
+
+ @Override
+ public void checkCanShowSchemas(SystemSecurityContext context, String catalogName)
+ {
+ if (!hasPermission(RangerTrinoResource.forCatalog(catalogName), context, _ANY, "ShowSchemas")) {
+ denyShowSchemas(catalogName);
+ }
+ }
+
+ @Override
+ public Set filterSchemas(SystemSecurityContext context, String catalogName, Set schemaNames)
+ {
+ Set toExclude = new HashSet<>();
+
+ for (String schemaName : schemaNames) {
+ if (!hasPermissionForFilter(RangerTrinoResource.forSchema(catalogName, schemaName), context, _ANY, "filterSchemas")) {
+ toExclude.add(schemaName);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return schemaNames;
+ }
+
+ return schemaNames.stream().filter(not(toExclude::contains)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void checkCanShowCreateSchema(SystemSecurityContext context, CatalogSchemaName schema)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, SHOW, "ShowCreateSchema")) {
+ denyShowCreateSchema(schema.getSchemaName());
+ }
+ }
+
+ @Override
+ public void checkCanCreateTable(SystemSecurityContext context, CatalogSchemaTableName table, Map properties)
+ {
+ if (!hasPermission(createTableResource(table), context, CREATE, "CreateTable")) {
+ denyCreateTable(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanDropTable(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, DROP, "DropTable")) {
+ denyDropTable(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanRenameTable(SystemSecurityContext context, CatalogSchemaTableName table, CatalogSchemaTableName newTable)
+ {
+ boolean isAllowed = hasPermission(createTableResource(table), context, ALTER, "RenameTable:source") &&
+ hasPermission(createTableResource(newTable), context, ALTER, "RenameTable:target");
+
+ if (!isAllowed) {
+ denyRenameTable(table.getSchemaTableName().getTableName(), newTable.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanSetTableProperties(SystemSecurityContext context, CatalogSchemaTableName table, Map> properties)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "SetTableProperties")) {
+ denySetTableProperties(table.toString());
+ }
+ }
+
+ @Override
+ public void checkCanSetTableComment(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "SetTableComment")) {
+ denyCommentTable(table.toString());
+ }
+ }
+
+ @Override
+ public void checkCanSetTableAuthorization(SystemSecurityContext context, CatalogSchemaTableName table, TrinoPrincipal principal)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "SetTableAuthorization")) {
+ denySetTableAuthorization(table.toString(), principal);
+ }
+ }
+
+ @Override
+ public void checkCanShowTables(SystemSecurityContext context, CatalogSchemaName schema)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, _ANY, "ShowTables")) {
+ denyShowTables(schema.toString());
+ }
+ }
+
+ @Override
+ public void checkCanShowCreateTable(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, SHOW, "ShowCreateTable")) {
+ denyShowCreateTable(table.toString());
+ }
+ }
+
+ @Override
+ public void checkCanInsertIntoTable(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, INSERT, "InsertIntoTable")) {
+ denyInsertTable(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanDeleteFromTable(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, DELETE, "DeleteFromTable")) {
+ denyDeleteTable(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanTruncateTable(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, DELETE, "TruncateTable")) {
+ denyTruncateTable(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public Set filterTables(SystemSecurityContext context, String catalogName, Set tableNames)
+ {
+ Set toExclude = new HashSet<>();
+
+ for (SchemaTableName tableName : tableNames) {
+ RangerTrinoResource resource = RangerTrinoResource.forTable(catalogName, tableName.getSchemaName(), tableName.getTableName());
+
+ if (!hasPermissionForFilter(resource, context, _ANY, "filterTables")) {
+ toExclude.add(tableName);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return tableNames;
+ }
+
+ return tableNames.stream().filter(not(toExclude::contains)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void checkCanAddColumn(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "AddColumn")) {
+ denyAddColumn(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanAlterColumn(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "AlterColumn")) {
+ denyAlterColumn(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanDropColumn(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "DropColumn")) {
+ denyDropColumn(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanRenameColumn(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "RenameColumn")) {
+ denyRenameColumn(table.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanSetColumnComment(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, ALTER, "SetColumnComment")) {
+ denyCommentColumn(table.toString());
+ }
+ }
+
+ @Override
+ public void checkCanShowColumns(SystemSecurityContext context, CatalogSchemaTableName table)
+ {
+ if (!hasPermission(createTableResource(table), context, _ANY, "ShowColumns")) {
+ denyShowColumns(table.toString());
+ }
+ }
+
+ @Override
+ public void checkCanSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns)
+ {
+ for (RangerTrinoResource resource : RangerTrinoResource.forColumns(table.getCatalogName(), table.getSchemaTableName().getSchemaName(), table.getSchemaTableName().getTableName(), columns)) {
+ if (!hasPermission(resource, context, SELECT, "SelectFromColumns")) {
+ denySelectColumns(table.getSchemaTableName().getTableName(), columns);
+ }
+ }
+ }
+
+ @Override
+ public void checkCanUpdateTableColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set updatedColumnNames)
+ {
+ if (!hasPermission(createTableResource(table), context, INSERT, "UpdateTableColumns")) {
+ denyUpdateTableColumns(table.getSchemaTableName().getTableName(), updatedColumnNames);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public Set filterColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns)
+ {
+ Set toExclude = new HashSet<>();
+ String catalogName = table.getCatalogName();
+ String schemaName = table.getSchemaTableName().getSchemaName();
+ String tableName = table.getSchemaTableName().getTableName();
+
+ for (String column : columns) {
+ RangerTrinoResource resource = RangerTrinoResource.forColumn(catalogName, schemaName, tableName, column);
+
+ if (!hasPermissionForFilter(resource, context, _ANY, "filterColumns")) {
+ toExclude.add(column);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return columns;
+ }
+
+ return columns.stream().filter(not(toExclude::contains)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void checkCanCreateView(SystemSecurityContext context, CatalogSchemaTableName view)
+ {
+ if (!hasPermission(createTableResource(view), context, CREATE, "CreateView")) {
+ denyCreateView(view.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanDropView(SystemSecurityContext context, CatalogSchemaTableName view)
+ {
+ if (!hasPermission(createTableResource(view), context, DROP, "DropView")) {
+ denyDropView(view.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanRenameView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView)
+ {
+ boolean isAllowed = hasPermission(createTableResource(view), context, ALTER, "RenameView:source") &&
+ hasPermission(createTableResource(newView), context, ALTER, "RenameView:target");
+
+ if (!isAllowed) {
+ denyRenameView(view.toString(), newView.toString());
+ }
+ }
+
+ @Override
+ public void checkCanSetViewAuthorization(SystemSecurityContext context, CatalogSchemaTableName view, TrinoPrincipal principal)
+ {
+ if (!hasPermission(createTableResource(view), context, ALTER, "SetViewAuthorization")) {
+ denySetViewAuthorization(view.toString(), principal);
+ }
+ }
+
+ @Override
+ public void checkCanCreateViewWithSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns)
+ {
+ for (RangerTrinoResource resource : RangerTrinoResource.forColumns(table.getCatalogName(), table.getSchemaTableName().getSchemaName(), table.getSchemaTableName().getTableName(), columns)) {
+ if (!hasPermission(resource, context, SELECT, "CreateViewWithSelectFromColumns")) {
+ denyCreateViewWithSelect(table.getSchemaTableName().getTableName(), context.getIdentity());
+ }
+ }
+ }
+
+ @Override
+ public void checkCanSetViewComment(SystemSecurityContext context, CatalogSchemaTableName view)
+ {
+ if (!hasPermission(createTableResource(view), context, ALTER, "SetViewComment")) {
+ denyCommentView(view.toString());
+ }
+ }
+
+ @Override
+ public void checkCanCreateMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView, Map properties)
+ {
+ if (!hasPermission(createTableResource(materializedView), context, CREATE, "CreateMaterializedView")) {
+ denyCreateMaterializedView(materializedView.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanRefreshMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView)
+ {
+ if (!hasPermission(createTableResource(materializedView), context, ALTER, "RefreshMaterializedView")) {
+ denyRefreshMaterializedView(materializedView.toString());
+ }
+ }
+
+ @Override
+ public void checkCanSetMaterializedViewProperties(SystemSecurityContext context, CatalogSchemaTableName materializedView, Map> properties)
+ {
+ if (!hasPermission(createTableResource(materializedView), context, ALTER, "SetMaterializedViewProperties")) {
+ denySetMaterializedViewProperties(materializedView.toString());
+ }
+ }
+
+ @Override
+ public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView)
+ {
+ if (!hasPermission(createTableResource(materializedView), context, DROP, "DropMaterializedView")) {
+ denyDropMaterializedView(materializedView.getSchemaTableName().getTableName());
+ }
+ }
+
+ @Override
+ public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView, CatalogSchemaTableName newView)
+ {
+ boolean isAllowed = hasPermission(createTableResource(materializedView), context, ALTER, "RenameMaterializedView:source") &&
+ hasPermission(createTableResource(newView), context, ALTER, "RenameMaterializedView:target");
+
+ if (!isAllowed) {
+ denyRenameMaterializedView(materializedView.toString(), newView.toString());
+ }
+ }
+
+ @Override
+ public void checkCanGrantSchemaPrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaName schema, TrinoPrincipal grantee, boolean grantOption)
+ {
+ }
+
+ @Override
+ public void checkCanDenySchemaPrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaName schema, TrinoPrincipal grantee)
+ {
+ }
+
+ @Override
+ public void checkCanRevokeSchemaPrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaName schema, TrinoPrincipal revokee, boolean grantOption)
+ {
+ }
+
+ @Override
+ public void checkCanGrantTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal grantee, boolean withGrantOption)
+ {
+ }
+
+ @Override
+ public void checkCanDenyTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal grantee)
+ {
+ }
+
+ @Override
+ public void checkCanRevokeTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal revokee, boolean grantOptionFor)
+ {
+ }
+
+ @Override
+ public void checkCanGrantEntityPrivilege(SystemSecurityContext context, EntityPrivilege privilege, EntityKindAndName entity, TrinoPrincipal grantee, boolean grantOption)
+ {
+ }
+
+ @Override
+ public void checkCanDenyEntityPrivilege(SystemSecurityContext context, EntityPrivilege privilege, EntityKindAndName entity, TrinoPrincipal grantee)
+ {
+ }
+
+ @Override
+ public void checkCanRevokeEntityPrivilege(SystemSecurityContext context, EntityPrivilege privilege, EntityKindAndName entity, TrinoPrincipal revokee, boolean grantOption)
+ {
+ }
+
+ @Override
+ public void checkCanCreateRole(SystemSecurityContext context, String role, Optional grantor)
+ {
+ }
+
+ @Override
+ public void checkCanDropRole(SystemSecurityContext context, String role)
+ {
+ }
+
+ @Override
+ public void checkCanShowRoles(SystemSecurityContext context)
+ {
+ }
+
+ @Override
+ public void checkCanGrantRoles(SystemSecurityContext context, Set roles, Set grantees, boolean adminOption, Optional grantor)
+ {
+ }
+
+ @Override
+ public void checkCanRevokeRoles(SystemSecurityContext context, Set roles, Set grantees, boolean adminOption, Optional grantor)
+ {
+ }
+
+ @Override
+ public void checkCanShowCurrentRoles(SystemSecurityContext context)
+ {
+ }
+
+ @Override
+ public void checkCanShowRoleGrants(SystemSecurityContext context)
+ {
+ }
+
+ @Override
+ public void checkCanExecuteProcedure(SystemSecurityContext context, CatalogSchemaRoutineName procedure)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchemaProcedure(procedure.getCatalogName(), procedure.getSchemaRoutineName().getSchemaName(), procedure.getSchemaRoutineName().getRoutineName()), context, EXECUTE, "ExecuteProcedure")) {
+ denyExecuteProcedure(procedure.getSchemaRoutineName().getRoutineName());
+ }
+ }
+
+ @Override
+ public void checkCanExecuteTableProcedure(SystemSecurityContext context, CatalogSchemaTableName catalogSchemaTableName, String procedure)
+ {
+ if (!hasPermission(createTableResource(catalogSchemaTableName), context, ALTER, "ExecuteTableProcedure")) {
+ denyExecuteTableProcedure(catalogSchemaTableName.toString(), procedure);
+ }
+ }
+
+ @Override
+ public void checkCanCreateFunction(SystemSecurityContext context, CatalogSchemaRoutineName functionName)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchemaFunction(functionName.getCatalogName(), functionName.getSchemaRoutineName().getSchemaName(), functionName.getSchemaRoutineName().getRoutineName()), context, CREATE, "CreateFunction")) {
+ denyCreateFunction(functionName.toString());
+ }
+ }
+
+ @Override
+ public void checkCanDropFunction(SystemSecurityContext context, CatalogSchemaRoutineName functionName)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchemaFunction(functionName.getCatalogName(), functionName.getSchemaRoutineName().getSchemaName(), functionName.getSchemaRoutineName().getRoutineName()), context, DROP, "DropFunction")) {
+ denyDropFunction(functionName.toString());
+ }
+ }
+
+ @Override
+ public void checkCanShowCreateFunction(SystemSecurityContext context, CatalogSchemaRoutineName functionName)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchemaFunction(functionName.getCatalogName(), functionName.getSchemaRoutineName().getSchemaName(), functionName.getSchemaRoutineName().getRoutineName()), context, SHOW, "ShowCreateFunction")) {
+ denyShowCreateFunction(functionName.toString());
+ }
+ }
+
+ @Override
+ public void checkCanShowFunctions(SystemSecurityContext context, CatalogSchemaName schema)
+ {
+ if (!hasPermission(RangerTrinoResource.forSchema(schema.getCatalogName(), schema.getSchemaName()), context, _ANY, "ShowFunctions")) {
+ denyShowFunctions(schema.toString());
+ }
+ }
+
+ @Override
+ public boolean canExecuteFunction(SystemSecurityContext context, CatalogSchemaRoutineName functionName)
+ {
+ return hasPermission(RangerTrinoResource.forSchemaFunction(functionName.getCatalogName(), functionName.getSchemaRoutineName().getSchemaName(), functionName.getSchemaRoutineName().getRoutineName()), context, EXECUTE, "ExecuteFunction");
+ }
+
+ @Override
+ public boolean canCreateViewWithExecuteFunction(SystemSecurityContext context, CatalogSchemaRoutineName functionName)
+ {
+ return hasPermission(RangerTrinoResource.forSchemaFunction(functionName.getCatalogName(), functionName.getSchemaRoutineName().getSchemaName(), functionName.getSchemaRoutineName().getRoutineName()), context, EXECUTE, "CreateViewWithExecuteFunction");
+ }
+
+ @Override
+ public Set filterFunctions(SystemSecurityContext context, String catalogName, Set functionNames)
+ {
+ Set toExclude = new HashSet<>();
+
+ for (SchemaFunctionName functionName : functionNames) {
+ RangerTrinoResource resource = RangerTrinoResource.forSchemaFunction(catalogName, functionName.getSchemaName(), functionName.getFunctionName());
+
+ if (!hasPermissionForFilter(resource, context, _ANY, "filterFunctions")) {
+ toExclude.add(functionName);
+ }
+ }
+
+ if (toExclude.isEmpty()) {
+ return functionNames;
+ }
+ else {
+ return functionNames.stream().filter(not(toExclude::contains)).collect(Collectors.toSet());
+ }
+ }
+
+ @Override
+ public List getRowFilters(SystemSecurityContext context, CatalogSchemaTableName tableName)
+ {
+ RangerAccessResult result = getRowFilterResult(createAccessRequest(createTableResource(tableName), context, SELECT, "getRowFilters"));
+
+ if (!isRowFilterEnabled(result)) {
+ return Collections.emptyList();
+ }
+
+ String filter = result.getFilterExpr();
+ ViewExpression viewExpression = ViewExpression.builder().identity(context.getIdentity().getUser())
+ .catalog(tableName.getCatalogName())
+ .schema(tableName.getSchemaTableName().getSchemaName())
+ .expression(filter).build();
+
+ return ImmutableList.of(viewExpression);
+ }
+
+ @Override
+ public Optional getColumnMask(SystemSecurityContext context, CatalogSchemaTableName tableName, String columnName, Type type)
+ {
+ RangerAccessResult result = getDataMaskResult(createAccessRequest(RangerTrinoResource.forColumn(tableName.getCatalogName(), tableName.getSchemaTableName().getSchemaName(), tableName.getSchemaTableName().getTableName(), columnName), context, SELECT, "getColumnMask"));
+
+ if (!isDataMaskEnabled(result)) {
+ return Optional.empty();
+ }
+
+ String maskType = result.getMaskType();
+ RangerServiceDef.RangerDataMaskTypeDef maskTypeDef = result.getMaskTypeDef();
+ String transformer = null;
+
+ if (maskTypeDef != null) {
+ transformer = maskTypeDef.getTransformer();
+ }
+
+ if (RangerPolicy.MASK_TYPE_NULL.equalsIgnoreCase(maskType)) {
+ transformer = "NULL";
+ }
+ else if (RangerPolicy.MASK_TYPE_CUSTOM.equalsIgnoreCase(maskType)) {
+ String maskedValue = result.getMaskedValue();
+
+ transformer = requireNonNullElse(maskedValue, "NULL");
+ }
+
+ if (transformer == null) {
+ return Optional.empty();
+ }
+ else {
+ transformer = transformer.replace("{col}", columnName).replace("{type}", type.getDisplayName());
+
+ return Optional.of(ViewExpression.builder().identity(context.getIdentity().getUser()).catalog(tableName.getCatalogName()).schema(tableName.getSchemaTableName().getSchemaName()).expression(transformer).build());
+ }
+ }
+
+ @Override
+ public Iterable getEventListeners()
+ {
+ return ImmutableList.of(eventListener);
+ }
+
+ @Override
+ public void shutdown()
+ {
+ rangerPlugin.cleanup();
+ }
+
+ private RangerAccessResult getDataMaskResult(RangerTrinoAccessRequest request)
+ {
+ return rangerPlugin.evalDataMaskPolicies(request, rangerPlugin.getResultProcessor());
+ }
+
+ private RangerAccessResult getRowFilterResult(RangerTrinoAccessRequest request)
+ {
+ return rangerPlugin.evalRowFilterPolicies(request, rangerPlugin.getResultProcessor());
+ }
+
+ private boolean isDataMaskEnabled(RangerAccessResult result)
+ {
+ return result.isMaskEnabled();
+ }
+
+ private boolean isRowFilterEnabled(RangerAccessResult result)
+ {
+ return result.isRowFilterEnabled();
+ }
+
+ private RangerTrinoAccessRequest createAccessRequest(RangerTrinoResource resource, SystemSecurityContext context, RangerTrinoAccessType accessType, String action)
+ {
+ Set userGroups = context.getIdentity().getGroups();
+
+ return new RangerTrinoAccessRequest(resource, context.getIdentity().getUser(), userGroups, getQueryTime(context), getClientAddress(context), getClientType(context), getQueryText(context), accessType, action);
+ }
+
+ private RangerTrinoAccessRequest createAccessRequest(RangerTrinoResource resource, Identity identity, QueryId queryId, RangerTrinoAccessType accessType, String action)
+ {
+ Set userGroups = identity.getGroups();
+
+ return new RangerTrinoAccessRequest(resource, identity.getUser(), userGroups, getQueryTime(queryId), getClientAddress(queryId), getClientType(queryId), getQueryText(queryId), accessType, action);
+ }
+
+ private Optional getClientAddress(QueryId queryId)
+ {
+ return queryId != null ? eventListener.getClientAddress(queryId.getId()) : Optional.empty();
+ }
+
+ private Optional getClientType(QueryId queryId)
+ {
+ return queryId != null ? eventListener.getClientType(queryId.getId()) : Optional.empty();
+ }
+
+ private Optional getQueryText(QueryId queryId)
+ {
+ return queryId != null ? eventListener.getQueryText(queryId.getId()) : Optional.empty();
+ }
+
+ private Optional getQueryTime(QueryId queryId)
+ {
+ return queryId != null ? eventListener.getQueryTime(queryId.getId()) : Optional.empty();
+ }
+
+ private Optional getClientAddress(SystemSecurityContext context)
+ {
+ return context != null ? getClientAddress(context.getQueryId()) : Optional.empty();
+ }
+
+ private Optional getClientType(SystemSecurityContext context)
+ {
+ return context != null ? getClientType(context.getQueryId()) : Optional.empty();
+ }
+
+ private Optional getQueryText(SystemSecurityContext context)
+ {
+ return context != null ? getQueryText(context.getQueryId()) : Optional.empty();
+ }
+
+ private Optional getQueryTime(SystemSecurityContext context)
+ {
+ return context != null ? getQueryTime(context.getQueryId()) : Optional.empty();
+ }
+
+ private boolean hasPermission(RangerTrinoResource resource, SystemSecurityContext context, RangerTrinoAccessType accessType, String action)
+ {
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(createAccessRequest(resource, context, accessType, action));
+
+ return result != null && result.getIsAllowed();
+ }
+
+ private boolean hasPermissionForFilter(RangerTrinoResource resource, SystemSecurityContext context, RangerTrinoAccessType accessType, String action)
+ {
+ RangerTrinoAccessRequest request = createAccessRequest(resource, context, accessType, action);
+
+ request.setResourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF_OR_DESCENDANTS);
+
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(request, null);
+
+ return result != null && result.getIsAllowed();
+ }
+
+ private boolean hasPermission(RangerTrinoResource resource, Identity identity, QueryId queryId, RangerTrinoAccessType accessType, String action)
+ {
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(createAccessRequest(resource, identity, queryId, accessType, action));
+
+ return result != null && result.getIsAllowed();
+ }
+
+ private boolean hasPermissionForFilter(RangerTrinoResource resource, Identity identity, QueryId queryId, RangerTrinoAccessType accessType, String action)
+ {
+ RangerTrinoAccessRequest request = createAccessRequest(resource, identity, queryId, accessType, action);
+
+ request.setResourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF_OR_DESCENDANTS);
+
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(request, null);
+
+ return result != null && result.getIsAllowed();
+ }
+
+ private boolean hasPermission(RangerTrinoResource resource, Optional principal, QueryId queryId, RangerTrinoAccessType accessType, String action)
+ {
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(createAccessRequest(resource, toIdentity(principal), queryId, accessType, action));
+
+ return result != null && result.getIsAllowed();
+ }
+
+ private static RangerTrinoResource createTableResource(CatalogSchemaTableName catalogSchemaTableName)
+ {
+ return RangerTrinoResource.forTable(catalogSchemaTableName.getCatalogName(), catalogSchemaTableName.getSchemaTableName().getSchemaName(), catalogSchemaTableName.getSchemaTableName().getTableName());
+ }
+
+ private static Identity toIdentity(Optional principal)
+ {
+ if (principal.isPresent()) {
+ return Identity.ofUser(principal.get().getName());
+ }
+
+ return Identity.ofUser("");
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java
new file mode 100644
index 000000000000..390b733868d5
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import com.google.inject.Injector;
+import com.google.inject.Scopes;
+import io.airlift.bootstrap.Bootstrap;
+import io.trino.spi.security.SystemAccessControl;
+import io.trino.spi.security.SystemAccessControlFactory;
+
+import java.util.Map;
+
+import static io.airlift.configuration.ConfigBinder.configBinder;
+import static java.util.Objects.requireNonNull;
+
+public class RangerSystemAccessControlFactory
+ implements SystemAccessControlFactory
+{
+ private static final String NAME = "apache-ranger";
+
+ @Override
+ public String getName()
+ {
+ return NAME;
+ }
+
+ @Deprecated
+ @Override
+ public SystemAccessControl create(Map config)
+ {
+ return null;
+ }
+
+ @Override
+ public SystemAccessControl create(Map config, SystemAccessControlContext context)
+ {
+ requireNonNull(config, "config is null");
+
+ Bootstrap app = new Bootstrap(binder ->
+ {
+ configBinder(binder).bindConfig(RangerConfig.class);
+ binder.bind(RangerSystemAccessControl.class).in(Scopes.SINGLETON);
+ });
+
+ Injector injector = app
+ .doNotInitializeLogging()
+ .setRequiredConfigurationProperties(config)
+ .initialize();
+
+ return injector.getInstance(RangerSystemAccessControl.class);
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java
new file mode 100644
index 000000000000..c6ec5f6ca5ce
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Locale.ENGLISH;
+
+class RangerTrinoAccessRequest
+ extends RangerAccessRequestImpl
+{
+ public RangerTrinoAccessRequest(RangerTrinoResource resource, String user, Set userGroups, Optional queryTime, Optional clientAddress, Optional clientType, Optional queryText, RangerTrinoAccessType trinoAccessType, String action)
+ {
+ super(resource, trinoAccessType.name().toLowerCase(ENGLISH), user, userGroups, null);
+
+ setAction(action);
+ setAccessTime(queryTime.map(instant -> new Date(instant.getEpochSecond() * 1000)).orElseGet(Date::new));
+ setClientIPAddress(clientAddress.orElse(null));
+ setClientType(clientType.orElse(null));
+ setRequestData(queryText.orElse(null));
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessType.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessType.java
new file mode 100644
index 000000000000..e6945d15a746
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessType.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+enum RangerTrinoAccessType {
+ CREATE,
+ DROP,
+ SELECT,
+ INSERT,
+ DELETE,
+ USE,
+ ALTER,
+ ALL,
+ GRANT,
+ REVOKE,
+ SHOW,
+ IMPERSONATE,
+ EXECUTE,
+ READ_SYSINFO,
+ WRITE_SYSINFO,
+ _ANY
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoEventListener.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoEventListener.java
new file mode 100644
index 000000000000..a25016683955
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoEventListener.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import io.trino.spi.eventlistener.EventListener;
+import io.trino.spi.eventlistener.QueryCompletedEvent;
+import io.trino.spi.eventlistener.QueryContext;
+import io.trino.spi.eventlistener.QueryCreatedEvent;
+import io.trino.spi.eventlistener.QueryMetadata;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+class RangerTrinoEventListener
+ implements EventListener
+{
+ private final Map activeQueries = new ConcurrentHashMap<>();
+
+ public Optional getClientAddress(String queryId)
+ {
+ QueryCreatedEvent event = activeQueries.get(queryId);
+ QueryContext context = event != null ? event.getContext() : null;
+
+ return context != null && context.getRemoteClientAddress().isPresent() ? Optional.of(context.getRemoteClientAddress().get()) : Optional.empty();
+ }
+
+ public Optional getQueryText(String queryId)
+ {
+ QueryCreatedEvent event = activeQueries.get(queryId);
+ QueryMetadata metadata = event != null ? event.getMetadata() : null;
+
+ return metadata != null ? Optional.of(metadata.getQuery()) : Optional.empty();
+ }
+
+ public Optional getQueryTime(String queryId)
+ {
+ QueryCreatedEvent event = activeQueries.get(queryId);
+
+ return event != null ? Optional.of(event.getCreateTime()) : Optional.empty();
+ }
+
+ public Optional getClientType(String queryId)
+ {
+ QueryCreatedEvent event = activeQueries.get(queryId);
+ QueryContext context = event != null ? event.getContext() : null;
+
+ return context != null && context.getUserAgent().isPresent() ? Optional.of(context.getUserAgent().get()) : Optional.empty();
+ }
+
+ @Override
+ public void queryCreated(QueryCreatedEvent event)
+ {
+ activeQueries.put(event.getMetadata().getQueryId(), event);
+ }
+
+ @Override
+ public void queryCompleted(QueryCompletedEvent event)
+ {
+ activeQueries.remove(event.getMetadata().getQueryId());
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java
new file mode 100644
index 000000000000..6a3d98366636
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+class RangerTrinoResource
+ extends RangerAccessResourceImpl
+{
+ public static final String KEY_CATALOG = "catalog";
+ public static final String KEY_SCHEMA = "schema";
+ public static final String KEY_TABLE = "table";
+ public static final String KEY_COLUMN = "column";
+ public static final String KEY_USER = "trinouser";
+ public static final String KEY_PROCEDURE = "procedure";
+ public static final String KEY_SYSTEM_PROPERTY = "systemproperty";
+ public static final String KEY_SESSION_PROPERTY = "sessionproperty";
+ public static final String KEY_SCHEMA_FUNCTION = "schemafunction";
+ public static final String KEY_ROLE = "role";
+ public static final String KEY_QUERY_ID = "queryid";
+ public static final String KEY_SYSINFO = "sysinfo";
+
+ public static RangerTrinoResource forUser(String userName)
+ {
+ return new RangerTrinoResource(KEY_USER, userName);
+ }
+
+ public static RangerTrinoResource forRole(String roleName)
+ {
+ return new RangerTrinoResource(KEY_ROLE, roleName);
+ }
+
+ public static RangerTrinoResource forQueryId(String queryId)
+ {
+ return new RangerTrinoResource(KEY_QUERY_ID, queryId);
+ }
+
+ public static RangerTrinoResource forSystemProperty(String propertyName)
+ {
+ return new RangerTrinoResource(KEY_SYSTEM_PROPERTY, propertyName);
+ }
+
+ public static RangerTrinoResource forSystemInformation()
+ {
+ return new RangerTrinoResource(KEY_SYSINFO, "*");
+ }
+
+ public static RangerTrinoResource forCatalog(String catalogName)
+ {
+ return new RangerTrinoResource(KEY_CATALOG, catalogName);
+ }
+
+ public static RangerTrinoResource forSchema(String catalogName, String schema)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SCHEMA, schema);
+
+ return ret;
+ }
+
+ public static RangerTrinoResource forTable(String catalogName, String schema, String tableName)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SCHEMA, schema);
+ ret.setValue(KEY_TABLE, tableName);
+
+ return ret;
+ }
+
+ public static RangerTrinoResource forColumn(String catalogName, String schema, String tableName, String columnName)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SCHEMA, schema);
+ ret.setValue(KEY_TABLE, tableName);
+ ret.setValue(KEY_COLUMN, columnName);
+
+ return ret;
+ }
+
+ public static List forColumns(String catalogName, String schema, String tableName, Set columns)
+ {
+ if (columns.isEmpty()) {
+ return ImmutableList.of(forTable(catalogName, schema, tableName));
+ }
+
+ return columns.stream().map(columnName -> forColumn(catalogName, schema, tableName, columnName)).collect(Collectors.toList());
+ }
+
+ public static RangerTrinoResource forSchemaProcedure(String catalogName, String schema, String procedure)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SCHEMA, schema);
+ ret.setValue(KEY_PROCEDURE, procedure);
+
+ return ret;
+ }
+
+ public static RangerTrinoResource forSchemaFunction(String catalogName, String schema, String functionName)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SCHEMA, schema);
+ ret.setValue(KEY_SCHEMA_FUNCTION, functionName);
+
+ return ret;
+ }
+
+ public static RangerTrinoResource forSessionProperty(String catalogName, String propertyName)
+ {
+ RangerTrinoResource ret = new RangerTrinoResource();
+
+ ret.setValue(KEY_CATALOG, catalogName);
+ ret.setValue(KEY_SESSION_PROPERTY, propertyName);
+
+ return ret;
+ }
+
+ private RangerTrinoResource()
+ {
+ }
+
+ public RangerTrinoResource(String key, String value)
+ {
+ setValue(key, value);
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java b/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java
new file mode 100644
index 000000000000..8b54bd3f5cb3
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import org.apache.ranger.admin.client.AbstractRangerAdminClient;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
+import org.apache.ranger.plugin.util.ServicePolicies;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+
+public class RangerAdminClientImpl
+ extends AbstractRangerAdminClient
+{
+ private static final String policiesFilepath = "/src/test/resources/trino-policies.json";
+
+ @Override
+ public ServicePolicies getServicePoliciesIfUpdated(long lastKnownVersion, long lastActivationTimeInMillis)
+ throws Exception
+ {
+ String basedir = System.getProperty("basedir");
+
+ if (basedir == null) {
+ basedir = new File(".").getCanonicalPath();
+ }
+
+ byte[] policiesBytes = Files.readAllBytes(FileSystems.getDefault().getPath(basedir, policiesFilepath));
+
+ ServicePolicies ret = gson.fromJson(new String(policiesBytes, Charset.defaultCharset()), ServicePolicies.class);
+ RangerServiceDef serviceDef = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef("trino");
+ RangerServiceDef tagServiceDef = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef("tag");
+
+ ret.setServiceDef(serviceDef);
+
+ if (ret.getTagPolicies() == null) {
+ ret.setTagPolicies(new ServicePolicies.TagPolicies());
+ }
+
+ ret.getTagPolicies().setServiceDef(tagServiceDef);
+
+ return ret;
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/TestRangerSystemAccessControl.java b/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/TestRangerSystemAccessControl.java
new file mode 100644
index 000000000000..e784df23580e
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/test/java/io/trino/plugin/ranger/TestRangerSystemAccessControl.java
@@ -0,0 +1,327 @@
+/*
+ * 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.
+ */
+package io.trino.plugin.ranger;
+
+import com.google.common.collect.ImmutableSet;
+import io.trino.spi.QueryId;
+import io.trino.spi.connector.CatalogSchemaName;
+import io.trino.spi.connector.CatalogSchemaRoutineName;
+import io.trino.spi.connector.CatalogSchemaTableName;
+import io.trino.spi.connector.SchemaTableName;
+import io.trino.spi.function.SchemaFunctionName;
+import io.trino.spi.security.AccessDeniedException;
+import io.trino.spi.security.BasicPrincipal;
+import io.trino.spi.security.Identity;
+import io.trino.spi.security.SystemSecurityContext;
+import io.trino.spi.security.TrinoPrincipal;
+import io.trino.spi.security.ViewExpression;
+import io.trino.spi.type.VarcharType;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static io.trino.spi.security.PrincipalType.USER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class TestRangerSystemAccessControl
+{
+ private static RangerSystemAccessControl accessControlManager;
+
+ private static final Identity ALICE = new Identity.Builder("alice").withPrincipal(new BasicPrincipal("alice")).build();
+ private static final Identity ADMIN = new Identity.Builder("admin").withPrincipal(new BasicPrincipal("admin")).build();
+ private static final Identity KERBEROS_INVALID_ALICE = Identity.from(ALICE).withPrincipal(new KerberosPrincipal("mallory/example.com@EXAMPLE.COM")).build();
+ private static final Identity BOB = Identity.ofUser("bob");
+
+ private static final Set ALL_CATALOGS = ImmutableSet.of("open-to-all", "all-allowed", "alice-catalog", "user-catalog");
+ private static final Set QUERY_OWNERS = ImmutableSet.of(Identity.ofUser("bob"), Identity.ofUser("alice"), Identity.ofUser("frank"));
+ private static final String CATALOG_ALICE = "alice-catalog";
+ private static final String CATALOG_USER_HOME = "user-catalog";
+ private static final CatalogSchemaName SCHEMA_ALICE_SCH1 = new CatalogSchemaName(CATALOG_ALICE, "sch1");
+ private static final CatalogSchemaTableName TABLE_ALICE_SCH1_TBL1 = new CatalogSchemaTableName(CATALOG_ALICE, "sch1", "tbl1");
+ private static final CatalogSchemaTableName VIEW_ALICE_SCH1_VW1 = new CatalogSchemaTableName(CATALOG_ALICE, "sch1", "vw1");
+ private static final CatalogSchemaRoutineName PROC_ALICE_SCH1_PROC1 = new CatalogSchemaRoutineName(CATALOG_ALICE, "sch1", "proc1");
+ private static final CatalogSchemaRoutineName FUNC_ALICE_SCH1_FUNC1 = new CatalogSchemaRoutineName(CATALOG_ALICE, "sch1", "func1");
+ private static final CatalogSchemaName SCHEMA_USER_BOB = new CatalogSchemaName(CATALOG_USER_HOME, "bob_schema");
+ private static final CatalogSchemaTableName TABLE_USER_BOB_TBL1 = new CatalogSchemaTableName(CATALOG_USER_HOME, SCHEMA_USER_BOB.getSchemaName(), "tbl1");
+
+ @BeforeAll
+ public static void setUpBeforeClass()
+ throws Exception
+ {
+ RangerConfig config = new RangerConfig();
+
+ config.setServiceName("dev_trino");
+
+ accessControlManager = new RangerSystemAccessControl(config);
+ }
+
+ @Test
+ public void testCanSetUserOperations()
+ {
+ accessControlManager.checkCanSetUser(ADMIN.getPrincipal(), BOB.getUser());
+ accessControlManager.checkCanImpersonateUser(ADMIN, BOB.getUser());
+ accessControlManager.checkCanViewQueryOwnedBy(ADMIN, BOB);
+ accessControlManager.checkCanKillQueryOwnedBy(ADMIN, BOB);
+ assertThat(accessControlManager.filterViewQueryOwnedBy(ALICE, QUERY_OWNERS)).isEmpty();
+
+ assertThatThrownBy(() -> accessControlManager.checkCanSetUser(ALICE.getPrincipal(), BOB.getUser())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanImpersonateUser(ALICE, BOB.getUser())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanViewQueryOwnedBy(ALICE, BOB)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanKillQueryOwnedBy(ALICE, BOB)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanImpersonateUser(KERBEROS_INVALID_ALICE, BOB.getUser())).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testSystemInformationOperations()
+ {
+ accessControlManager.checkCanReadSystemInformation(ADMIN);
+ accessControlManager.checkCanWriteSystemInformation(ADMIN);
+
+ assertThatThrownBy(() -> accessControlManager.checkCanReadSystemInformation(ALICE)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanWriteSystemInformation(ALICE)).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testSystemSessionPropertyOperations()
+ {
+ accessControlManager.checkCanSetSystemSessionProperty(ADMIN, new QueryId("q1"), "test-property");
+
+ assertThatThrownBy(() -> accessControlManager.checkCanSetSystemSessionProperty(ALICE, new QueryId("q1"), "test-property")).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testQueryOperations()
+ {
+ accessControlManager.checkCanExecuteQuery(ADMIN, new QueryId("1"));
+
+ assertThatThrownBy(() -> accessControlManager.checkCanExecuteQuery(ALICE, new QueryId("1"))).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testCatalogOperations()
+ {
+ accessControlManager.canAccessCatalog(context(ALICE), CATALOG_ALICE);
+ accessControlManager.checkCanCreateCatalog(context(ALICE), CATALOG_ALICE);
+ accessControlManager.checkCanDropCatalog(context(ALICE), CATALOG_ALICE);
+ accessControlManager.checkCanSetCatalogSessionProperty(context(ALICE), CATALOG_ALICE, "property");
+ assertThat(accessControlManager.filterCatalogs(context(ALICE), ALL_CATALOGS)).isEqualTo(ALL_CATALOGS);
+ assertThat(accessControlManager.filterCatalogs(context(BOB), ALL_CATALOGS)).isEqualTo(ImmutableSet.of("open-to-all", "all-allowed", "user-catalog"));
+
+ assertThat(accessControlManager.canAccessCatalog(context(BOB), CATALOG_ALICE)).isFalse();
+
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateCatalog(context(BOB), CATALOG_ALICE)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropCatalog(context(BOB), CATALOG_ALICE)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetCatalogSessionProperty(context(BOB), CATALOG_ALICE, "property")).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testSchemaOperations()
+ {
+ accessControlManager.checkCanCreateSchema(context(ALICE), SCHEMA_ALICE_SCH1, null);
+ accessControlManager.checkCanDropSchema(context(ALICE), SCHEMA_ALICE_SCH1);
+ accessControlManager.checkCanRenameSchema(context(ALICE), SCHEMA_ALICE_SCH1, "new-schema");
+ accessControlManager.checkCanShowSchemas(context(ALICE), CATALOG_ALICE);
+ accessControlManager.checkCanSetSchemaAuthorization(context(ALICE), SCHEMA_ALICE_SCH1, new TrinoPrincipal(USER, "principal"));
+ accessControlManager.checkCanShowCreateSchema(context(ALICE), SCHEMA_ALICE_SCH1);
+
+ Set aliceSchemas = ImmutableSet.of("sch1");
+ assertThat(accessControlManager.filterSchemas(context(ALICE), CATALOG_ALICE, aliceSchemas)).isEqualTo(aliceSchemas);
+ assertThat(accessControlManager.filterSchemas(context(BOB), "alice-catalog", aliceSchemas)).isEmpty();
+
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateSchema(context(BOB), SCHEMA_ALICE_SCH1, null)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropSchema(context(BOB), SCHEMA_ALICE_SCH1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRenameSchema(context(BOB), SCHEMA_ALICE_SCH1, "new-schema")).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanShowSchemas(context(BOB), SCHEMA_ALICE_SCH1.getCatalogName())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetSchemaAuthorization(context(BOB), SCHEMA_ALICE_SCH1, new TrinoPrincipal(USER, "principal"))).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanShowCreateSchema(context(BOB), SCHEMA_ALICE_SCH1)).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testTableOperations()
+ {
+ CatalogSchemaTableName newTableName = new CatalogSchemaTableName("alice-catalog", "sch1", "new-table");
+
+ accessControlManager.checkCanCreateTable(context(ALICE), TABLE_ALICE_SCH1_TBL1, Map.of());
+ accessControlManager.checkCanDropTable(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanRenameTable(context(ALICE), TABLE_ALICE_SCH1_TBL1, newTableName);
+ accessControlManager.checkCanSetTableProperties(context(ALICE), TABLE_ALICE_SCH1_TBL1, Collections.emptyMap());
+ accessControlManager.checkCanSetTableComment(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanSetTableAuthorization(context(ALICE), TABLE_ALICE_SCH1_TBL1, new TrinoPrincipal(USER, "principal"));
+ accessControlManager.checkCanShowTables(context(ALICE), SCHEMA_ALICE_SCH1);
+ accessControlManager.checkCanShowCreateTable(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanInsertIntoTable(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanDeleteFromTable(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanTruncateTable(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanCreateTable(context(BOB), TABLE_USER_BOB_TBL1, Map.of());
+ accessControlManager.checkCanDropTable(context(BOB), TABLE_USER_BOB_TBL1);
+
+ Set aliceTables = ImmutableSet.of(new SchemaTableName("sch1", "tbl1"));
+ assertThat(accessControlManager.filterTables(context(ALICE), CATALOG_ALICE, aliceTables)).isEqualTo(aliceTables);
+ assertThat(accessControlManager.filterTables(context(BOB), "alice-catalog", aliceTables)).isEmpty();
+
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateTable(context(BOB), TABLE_ALICE_SCH1_TBL1, Map.of())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropTable(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRenameTable(context(BOB), TABLE_ALICE_SCH1_TBL1, newTableName)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetTableProperties(context(BOB), TABLE_ALICE_SCH1_TBL1, Collections.emptyMap())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetTableComment(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetTableAuthorization(context(BOB), TABLE_ALICE_SCH1_TBL1, new TrinoPrincipal(USER, "principal"))).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanShowTables(context(BOB), SCHEMA_ALICE_SCH1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanShowCreateTable(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanInsertIntoTable(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDeleteFromTable(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanTruncateTable(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testColumnOperations()
+ {
+ accessControlManager.checkCanAddColumn(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanAlterColumn(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanDropColumn(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanRenameColumn(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanSetColumnComment(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanShowColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ accessControlManager.checkCanSelectFromColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1, ImmutableSet.of());
+ accessControlManager.checkCanUpdateTableColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1, Collections.emptySet());
+ accessControlManager.checkCanAddColumn(context(BOB), TABLE_USER_BOB_TBL1);
+ accessControlManager.checkCanSelectFromColumns(context(BOB), TABLE_USER_BOB_TBL1, ImmutableSet.of());
+ accessControlManager.checkCanDropColumn(context(BOB), TABLE_USER_BOB_TBL1);
+
+ Set columns = Collections.singleton("column-1");
+ Map> tableColumns = Collections.singletonMap(TABLE_ALICE_SCH1_TBL1.getSchemaTableName(), columns);
+
+ assertThat(accessControlManager.filterColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1, columns)).isEqualTo(columns);
+ assertThat(accessControlManager.filterColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1.getCatalogName(), tableColumns)).isEqualTo(tableColumns);
+ assertThat(accessControlManager.filterColumns(context(BOB), TABLE_ALICE_SCH1_TBL1, columns)).isEmpty();
+ assertThat(accessControlManager.filterColumns(context(BOB), TABLE_ALICE_SCH1_TBL1.getCatalogName(), tableColumns)).isEqualTo(Collections.singletonMap(TABLE_ALICE_SCH1_TBL1.getSchemaTableName(), Collections.emptySet()));
+
+ assertThatThrownBy(() -> accessControlManager.checkCanAddColumn(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanAlterColumn(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropColumn(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRenameColumn(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetColumnComment(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanShowColumns(context(BOB), TABLE_ALICE_SCH1_TBL1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSelectFromColumns(context(BOB), TABLE_ALICE_SCH1_TBL1, ImmutableSet.of())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanUpdateTableColumns(context(BOB), TABLE_ALICE_SCH1_TBL1, Collections.emptySet())).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testViewOperations()
+ {
+ CatalogSchemaTableName newViewName = new CatalogSchemaTableName(VIEW_ALICE_SCH1_VW1.getCatalogName(), VIEW_ALICE_SCH1_VW1.getSchemaTableName().getSchemaName(), "new-view");
+
+ accessControlManager.checkCanCreateView(context(ALICE), VIEW_ALICE_SCH1_VW1);
+ accessControlManager.checkCanDropView(context(ALICE), VIEW_ALICE_SCH1_VW1);
+ accessControlManager.checkCanRenameView(context(ALICE), VIEW_ALICE_SCH1_VW1, newViewName);
+ accessControlManager.checkCanSetViewAuthorization(context(ALICE), VIEW_ALICE_SCH1_VW1, new TrinoPrincipal(USER, "user"));
+ accessControlManager.checkCanCreateViewWithSelectFromColumns(context(ALICE), TABLE_ALICE_SCH1_TBL1, ImmutableSet.of());
+ accessControlManager.checkCanSetViewAuthorization(context(ALICE), VIEW_ALICE_SCH1_VW1, new TrinoPrincipal(USER, "user"));
+ accessControlManager.checkCanSetViewComment(context(ALICE), VIEW_ALICE_SCH1_VW1);
+
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateView(context(BOB), VIEW_ALICE_SCH1_VW1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropView(context(BOB), VIEW_ALICE_SCH1_VW1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRenameView(context(BOB), VIEW_ALICE_SCH1_VW1, newViewName)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetViewAuthorization(context(BOB), VIEW_ALICE_SCH1_VW1, new TrinoPrincipal(USER, "user"))).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateViewWithSelectFromColumns(context(BOB), TABLE_ALICE_SCH1_TBL1, ImmutableSet.of())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetViewAuthorization(context(BOB), VIEW_ALICE_SCH1_VW1, new TrinoPrincipal(USER, "user"))).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetViewComment(context(BOB), VIEW_ALICE_SCH1_VW1)).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testMaterializedViewOperations()
+ {
+ CatalogSchemaTableName newViewName = new CatalogSchemaTableName(VIEW_ALICE_SCH1_VW1.getCatalogName(), VIEW_ALICE_SCH1_VW1.getSchemaTableName().getSchemaName(), "new-view");
+
+ accessControlManager.checkCanCreateMaterializedView(context(ALICE), VIEW_ALICE_SCH1_VW1, Collections.emptyMap());
+ accessControlManager.checkCanRefreshMaterializedView(context(ALICE), VIEW_ALICE_SCH1_VW1);
+ accessControlManager.checkCanSetMaterializedViewProperties(context(ALICE), VIEW_ALICE_SCH1_VW1, Collections.emptyMap());
+ accessControlManager.checkCanDropMaterializedView(context(ALICE), VIEW_ALICE_SCH1_VW1);
+ accessControlManager.checkCanRenameMaterializedView(context(ALICE), VIEW_ALICE_SCH1_VW1, newViewName);
+
+ assertThatThrownBy(() -> accessControlManager.checkCanCreateMaterializedView(context(BOB), VIEW_ALICE_SCH1_VW1, Collections.emptyMap())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRefreshMaterializedView(context(BOB), VIEW_ALICE_SCH1_VW1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanSetMaterializedViewProperties(context(BOB), VIEW_ALICE_SCH1_VW1, Collections.emptyMap())).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanDropMaterializedView(context(BOB), VIEW_ALICE_SCH1_VW1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanRenameMaterializedView(context(BOB), VIEW_ALICE_SCH1_VW1, newViewName)).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testProcedureOperations()
+ {
+ accessControlManager.checkCanExecuteProcedure(context(ALICE), PROC_ALICE_SCH1_PROC1);
+ accessControlManager.checkCanExecuteTableProcedure(context(ALICE), TABLE_ALICE_SCH1_TBL1, PROC_ALICE_SCH1_PROC1.getRoutineName());
+
+ assertThatThrownBy(() -> accessControlManager.checkCanExecuteProcedure(context(BOB), PROC_ALICE_SCH1_PROC1)).isInstanceOf(AccessDeniedException.class);
+ assertThatThrownBy(() -> accessControlManager.checkCanExecuteTableProcedure(context(BOB), TABLE_ALICE_SCH1_TBL1, PROC_ALICE_SCH1_PROC1.getRoutineName())).isInstanceOf(AccessDeniedException.class);
+ }
+
+ @Test
+ public void testFunctionOperations()
+ {
+ accessControlManager.checkCanCreateFunction(context(ALICE), FUNC_ALICE_SCH1_FUNC1);
+ accessControlManager.checkCanDropFunction(context(ALICE), FUNC_ALICE_SCH1_FUNC1);
+ accessControlManager.checkCanShowCreateFunction(context(ALICE), FUNC_ALICE_SCH1_FUNC1);
+ accessControlManager.checkCanShowFunctions(context(ALICE), SCHEMA_ALICE_SCH1);
+ accessControlManager.canCreateViewWithExecuteFunction(context(ALICE), FUNC_ALICE_SCH1_FUNC1);
+
+ assertThat(accessControlManager.canExecuteFunction(context(ALICE), FUNC_ALICE_SCH1_FUNC1)).isTrue();
+ assertThat(accessControlManager.canExecuteFunction(context(BOB), FUNC_ALICE_SCH1_FUNC1)).isFalse();
+
+ Set functionNames = Collections.singleton(new SchemaFunctionName(SCHEMA_ALICE_SCH1.getSchemaName(), FUNC_ALICE_SCH1_FUNC1.getRoutineName()));
+
+ assertThat(accessControlManager.filterFunctions(context(ALICE), CATALOG_ALICE, functionNames)).isEqualTo(functionNames);
+ assertThat(accessControlManager.filterFunctions(context(BOB), CATALOG_ALICE, functionNames)).isEmpty();
+ }
+
+ @Test
+ public void testColumnMask()
+ {
+ final VarcharType varcharType = VarcharType.createVarcharType(20);
+
+ // MASK_NONE
+ Optional ret = accessControlManager.getColumnMask(context(ALICE), TABLE_ALICE_SCH1_TBL1, "national_id", varcharType);
+ assertThat(ret.isPresent()).isFalse();
+
+ // MASK_SHOW_FIRST_4
+ ret = accessControlManager.getColumnMask(context(BOB), TABLE_ALICE_SCH1_TBL1, "national_id", varcharType);
+ assertThat(ret.isPresent()).isTrue();
+ assertThat(ret.get().getExpression()).isEqualTo("cast(regexp_replace(national_id, '(^.{4})(.*)', x -> x[1] || regexp_replace(x[2], '.', 'X')) as varchar(20))");
+ }
+
+ @Test
+ public void testRowFilters()
+ {
+ List retArray = accessControlManager.getRowFilters(context(ALICE), TABLE_ALICE_SCH1_TBL1);
+ assertThat(retArray).isEmpty();
+
+ retArray = accessControlManager.getRowFilters(context(BOB), TABLE_ALICE_SCH1_TBL1);
+ assertThat(retArray).isNotEmpty();
+ assertThat(retArray.size()).isEqualTo(1);
+ assertThat(retArray.get(0).getExpression()).isEqualTo("status = 'active'");
+ }
+
+ private SystemSecurityContext context(Identity id)
+ {
+ return new SystemSecurityContext(id, new QueryId("id_1"), Instant.now());
+ }
+}
diff --git a/plugin/trino-apache-ranger/src/test/resources/ranger-trino-security.xml b/plugin/trino-apache-ranger/src/test/resources/ranger-trino-security.xml
new file mode 100644
index 000000000000..89c2a0c75221
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/test/resources/ranger-trino-security.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ ranger.plugin.trino.service.name
+ cl1_trino
+
+ Name of the Ranger service containing policies for this SampleApp instance
+
+
+
+
+ ranger.plugin.trino.policy.source.impl
+ io.trino.plugin.ranger.RangerAdminClientImpl
+
+ Policy source.
+
+
+
+
+ ranger.plugin.trino.policy.pollIntervalMs
+ 30000
+
+ How often to poll for changes in policies?
+
+
+
+
+ ranger.plugin.trino.policy.cache.dir
+ ${project.build.directory}
+
+ Directory where Ranger policies are cached after successful retrieval from the source
+
+
+
+
diff --git a/plugin/trino-apache-ranger/src/test/resources/trino-policies.json b/plugin/trino-apache-ranger/src/test/resources/trino-policies.json
new file mode 100644
index 000000000000..351b933ebf2c
--- /dev/null
+++ b/plugin/trino-apache-ranger/src/test/resources/trino-policies.json
@@ -0,0 +1,305 @@
+{
+ "serviceName": "dev_trino",
+ "serviceId": 203,
+ "policyVersion": 5,
+ "policies": [
+ {
+ "id": 10,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "checkCanImpersonateUser",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "trinouser": { "values": [ "bob" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "impersonate" } ], "users": [ "admin" ] }
+ ]
+ },
+ {
+ "id": 11,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "SystemInformation",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "sysinfo": { "values": [ "*" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "read_sysinfo" }, { "type": "write_sysinfo" } ], "users": [ "admin" ] }
+ ]
+ },
+ {
+ "id": 12,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "SystemSessionProperty",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "systemproperty": { "values": [ "test-property" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "alter" } ], "users": [ "admin" ] }
+ ]
+ },
+ {
+ "id": 13,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "checkCanExecuteQuery",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "queryid": { "values": [ "*" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "execute" } ], "users": [ "admin" ] }
+ ]
+ },
+ {
+ "id": 14,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "checkFunction",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "function": { "values": [ "func1" ] } },
+ "policyItems": [
+ {
+ "accesses": [ { "type": "execute" }, { "type": "grant" } ],
+ "users": [ "alice" ]
+ }
+ ]
+ },
+ {
+ "id": 15,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-schema",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1", "new-schema" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "alter" }, { "type": "show" }, { "type": "grant" }, { "type": "revoke" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 16,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-catalog",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "use" }, { "type": "alter" }, { "type": "show" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 17,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-table",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "table": { "values": [ "tbl1", "new-table" ]
+ }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "delete" }, { "type": "alter" }, { "type": "grant" }, { "type": "revoke" }, { "type": "show" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 18,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-table",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "table": { "values": [ "tbl1" ] },
+ "column": { "values": [ "*" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "delete" }, { "type": "alter" }, { "type": "grant" }, { "type": "revoke" }, { "type": "show" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 19,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-procedure",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "procedure": { "values": [ "proc1" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "execute" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 20,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-view",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "table": { "values": [ "vw1", "new-view" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "create" }, { "type": "drop" }, { "type": "alter" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 21,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "alice-session-property",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "sessionproperty": { "values": [ "property" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "show" }, { "type": "alter" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 22,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "open-to-all",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "open-to-all", "all-allowed" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" } ], "users": [ "{USER}" ] }
+ ]
+ },
+ {
+ "id": 23,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "role",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "role": { "values": [ "*" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "create" }, { "type": "drop" }, { "type": "show" }, { "type": "grant" }, { "type": "revoke" } ], "users": [ "admin" ] }
+ ]
+ },
+ {
+ "id": 24,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "schema-function",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "schemafunction": { "values": [ "func1" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "create" }, { "type": "drop" }, { "type": "show" }, { "type": "execute" } ], "users": [ "alice" ] }
+ ]
+ },
+ {
+ "id": 25,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "test-column-mask",
+ "policyType": 1,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "table": { "values": [ "tbl1" ] },
+ "column": { "values": [ "national_id" ] }
+ },
+ "dataMaskPolicyItems": [
+ { "accesses": [ { "type": "select" } ], "users": [ "alice" ], "dataMaskInfo": { "dataMaskType": "MASK_NONE" } },
+ { "accesses": [ { "type": "select" } ], "users": [ "{USER}" ], "dataMaskInfo": { "dataMaskType": "MASK_SHOW_FIRST_4" } }
+ ]
+ },
+ {
+ "id": 26,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "test-row-filter",
+ "policyType": 2,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "alice-catalog" ] },
+ "schema": { "values": [ "sch1" ] },
+ "table": { "values": [ "tbl1" ] }
+ },
+ "rowFilterPolicyItems": [
+ { "rowFilterInfo": { "filterExpr": "" }, "accesses": [ { "type": "select" } ], "users": [ "alice" ] },
+ { "rowFilterInfo": { "filterExpr": "status = 'active'" }, "accesses": [ { "type": "select" } ], "users": [ "{USER}" ] }
+ ]
+ },
+ {
+ "id": 27,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "user's home schema in alice-catalog",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "user-catalog" ] },
+ "schema": { "values": [ "${{USER._name}}_schema" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "alter" }, { "type": "show" }, { "type": "grant" }, { "type": "revoke" } ], "users": [ "{USER}" ] }
+ ]
+ },
+ {
+ "id": 28,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "user's home schema in alice-catalog",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "user-catalog" ] },
+ "schema": { "values": [ "${{USER._name}}_schema" ] },
+ "table": { "values": [ "*" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "alter" }, { "type": "show" }, { "type": "grant" }, { "type": "revoke" } ], "users": [ "{USER}" ] }
+ ]
+ },
+ {
+ "id": 29,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "user's home schema in alice-catalog",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": {
+ "catalog": { "values": [ "user-catalog" ] },
+ "schema": { "values": [ "${{USER._name}}_schema" ] },
+ "table": { "values": [ "*" ] },
+ "column": { "values": [ "*" ] }
+ },
+ "policyItems": [
+ { "accesses": [ { "type": "select" }, { "type": "insert" }, { "type": "create" }, { "type": "drop" }, { "type": "alter" }, { "type": "show" }, { "type": "grant" }, { "type": "revoke" } ], "users": [ "{USER}" ] }
+ ]
+ }
+ ]
+}
diff --git a/pom.xml b/pom.xml
index bd282730530f..b7a64373733b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,7 @@
lib/trino-parquet
lib/trino-plugin-toolkit
lib/trino-record-decoder
+ plugin/trino-apache-ranger
plugin/trino-base-jdbc
plugin/trino-bigquery
plugin/trino-blackhole
@@ -958,6 +959,19 @@
${dep.swagger.version}
+
+ io.trino
+ trino-apache-ranger
+ ${project.version}
+
+
+
+ io.trino
+ trino-apache-ranger
+ ${project.version}
+ test-jar
+
+
io.trino
trino-array
diff --git a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
index 90939d93a921..ce6d7104bba2 100644
--- a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
+++ b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
@@ -88,6 +88,7 @@ public final class TestGroups
public static final String CLICKHOUSE = "clickhouse";
public static final String KUDU = "kudu";
public static final String MARIADB = "mariadb";
+ public static final String APACHE_RANGER = "apache-ranger";
public static final String SNOWFLAKE = "snowflake";
public static final String DELTA_LAKE_OSS = "delta-lake-oss";
public static final String DELTA_LAKE_HDFS = "delta-lake-hdfs";
diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeApacheRanger.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeApacheRanger.java
new file mode 100644
index 000000000000..4bca1170cdd2
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeApacheRanger.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+package io.trino.tests.product.launcher.env.environment;
+
+import com.google.inject.Inject;
+import io.trino.tests.product.launcher.docker.DockerFiles;
+import io.trino.tests.product.launcher.env.DockerContainer;
+import io.trino.tests.product.launcher.env.Environment;
+import io.trino.tests.product.launcher.env.EnvironmentProvider;
+import io.trino.tests.product.launcher.env.common.StandardMultinode;
+import io.trino.tests.product.launcher.env.common.TestsEnvironment;
+import io.trino.tests.product.launcher.testcontainers.PortBinder;
+import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;
+
+import static io.trino.tests.product.launcher.docker.ContainerUtil.forSelectedPorts;
+import static io.trino.tests.product.launcher.env.EnvironmentContainers.COORDINATOR;
+import static io.trino.tests.product.launcher.env.EnvironmentContainers.WORKER;
+import static io.trino.tests.product.launcher.env.common.Standard.CONTAINER_TRINO_ETC;
+import static java.util.Objects.requireNonNull;
+import static org.testcontainers.utility.MountableFile.forHostPath;
+
+/**
+ * Trino with Apache Ranger authorizer plugin
+ */
+@TestsEnvironment
+public class EnvMultinodeApacheRanger
+ extends EnvironmentProvider
+{
+ public static final int MARIADB_PORT = 23306;
+
+ private final DockerFiles dockerFiles;
+ private final PortBinder portBinder;
+
+ @Inject
+ public EnvMultinodeApacheRanger(StandardMultinode standardMultinode, DockerFiles dockerFiles, PortBinder portBinder)
+ {
+ super(standardMultinode);
+ this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null");
+ this.portBinder = requireNonNull(portBinder, "portBinder is null");
+ }
+
+ @Override
+ public void extendEnvironment(Environment.Builder builder)
+ {
+ DockerFiles.ResourceProvider configDir = dockerFiles.getDockerFilesHostDirectory("conf/environment/multinode-apache-ranger/");
+
+ builder.addConnector("mariadb", forHostPath(configDir.getPath("mariadb.properties")));
+ builder.addContainer(createMariaDb());
+
+ builder.configureContainer(COORDINATOR, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("access-control.properties")), CONTAINER_TRINO_ETC + "/access-control.properties"));
+ builder.configureContainer(COORDINATOR, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-trino-security.xml")), CONTAINER_TRINO_ETC + "/ranger-trino-security.xml"));
+ builder.configureContainer(COORDINATOR, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-trino-audit.xml")), CONTAINER_TRINO_ETC + "/ranger-trino-audit.xml"));
+ builder.configureContainer(COORDINATOR, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-policymgr-ssl.xml")), CONTAINER_TRINO_ETC + "/ranger-policymgr-ssl.xml"));
+ builder.configureContainer(COORDINATOR, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("trino-policies.json")), "/tmp/apache-ranger-policycache/trino_dev_trino.json"));
+
+ builder.configureContainer(WORKER, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("access-control.properties")), CONTAINER_TRINO_ETC + "/access-control.properties"));
+ builder.configureContainer(WORKER, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-trino-security.xml")), CONTAINER_TRINO_ETC + "/ranger-trino-security.xml"));
+ builder.configureContainer(WORKER, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-trino-audit.xml")), CONTAINER_TRINO_ETC + "/ranger-trino-audit.xml"));
+ builder.configureContainer(WORKER, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("ranger-policymgr-ssl.xml")), CONTAINER_TRINO_ETC + "/ranger-policymgr-ssl.xml"));
+ builder.configureContainer(WORKER, container -> container.withCopyFileToContainer(forHostPath(configDir.getPath("trino-policies.json")), "/tmp/apache-ranger-policycache/trino_dev_trino.json"));
+ }
+
+ private DockerContainer createMariaDb()
+ {
+ DockerContainer container = new DockerContainer("mariadb:10.7.1", "mariadb")
+ .withEnv("MYSQL_USER", "test")
+ .withEnv("MYSQL_PASSWORD", "test")
+ .withEnv("MYSQL_ROOT_PASSWORD", "test")
+ .withEnv("MYSQL_DATABASE", "test")
+ .withCommand("mysqld", "--port", Integer.toString(MARIADB_PORT), "--character-set-server", "utf8mb4")
+ .withStartupCheckStrategy(new IsRunningStartupCheckStrategy())
+ .waitingFor(forSelectedPorts(MARIADB_PORT));
+
+ portBinder.exposePort(container, MARIADB_PORT);
+
+ return container;
+ }
+}
diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteApacheRanger.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteApacheRanger.java
new file mode 100644
index 000000000000..b97312004666
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteApacheRanger.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+package io.trino.tests.product.launcher.suite.suites;
+
+import com.google.common.collect.ImmutableList;
+import io.trino.tests.product.launcher.env.EnvironmentConfig;
+import io.trino.tests.product.launcher.env.environment.EnvMultinodeApacheRanger;
+import io.trino.tests.product.launcher.suite.Suite;
+import io.trino.tests.product.launcher.suite.SuiteTestRun;
+
+import java.util.List;
+
+import static io.trino.tests.product.launcher.suite.SuiteTestRun.testOnEnvironment;
+
+public class SuiteApacheRanger
+ extends Suite
+{
+ @Override
+ public List getTestRuns(EnvironmentConfig config)
+ {
+ return ImmutableList.of(
+ testOnEnvironment(EnvMultinodeApacheRanger.class)
+ .withGroups("configured_features", "apache-ranger")
+ .build());
+ }
+}
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/access-control.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/access-control.properties
new file mode 100644
index 000000000000..1d9c791555a4
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/access-control.properties
@@ -0,0 +1,4 @@
+access-control.name=apache-ranger
+
+apache-ranger.service.name=dev_trino
+apache-ranger.plugin.config.resource=/docker/trino-product-tests/conf/trino/etc/ranger-trino-security.xml,/docker/trino-product-tests/conf/trino/etc/ranger-trino-audit.xml,/docker/trino-product-tests/conf/trino/etc/ranger-policymgr-ssl.xml
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/mariadb.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/mariadb.properties
new file mode 100644
index 000000000000..78530164025d
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/mariadb.properties
@@ -0,0 +1,4 @@
+connector.name=mariadb
+connection-url=jdbc:mariadb://mariadb:23306/
+connection-user=test
+connection-password=test
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-policymgr-ssl.xml b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-policymgr-ssl.xml
new file mode 100644
index 000000000000..03e30dd449b4
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-policymgr-ssl.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ xasecure.policymgr.clientssl.keystore
+ trinoservice-clientcert.jks
+ Java Keystore files
+
+
+
+ xasecure.policymgr.clientssl.truststore
+ cacerts-xasecure.jks
+ java truststore file
+
+
+
+ xasecure.policymgr.clientssl.keystore.credential.file
+ jceks://file/etc/trino/keystore-trinoservice-ssl.jceks
+ java keystore credential file
+
+
+
+ xasecure.policymgr.clientssl.truststore.credential.file
+ jceks://file/etc/trino/truststore-trinoservice-ssl.jceks
+ java truststore credential file
+
+
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-audit.xml b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-audit.xml
new file mode 100644
index 000000000000..cd7ddb136e85
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-audit.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ xasecure.audit.is.enabled
+ false
+
+
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-security.xml b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-security.xml
new file mode 100644
index 000000000000..9a9b09d76baf
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/ranger-trino-security.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+ ranger.plugin.trino.service.name
+ dev_trino
+
+ Name of the Ranger service containing policies for this SampleApp instance
+
+
+
+
+ ranger.plugin.trino.super.users
+ trino,hive
+ List of users with superuser privileges
+
+
+
+ ranger.plugin.trino.policy.rest.url
+ http://host.docker.internal:6080
+ URL to Ranger Admin
+
+
+
+ ranger.plugin.trino.policy.cache.dir
+ /tmp/apache-ranger-policycache
+
+ Directory where Ranger policies are cached after successful retrieval from the source
+
+
+
+
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/trino-policies.json b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/trino-policies.json
new file mode 100644
index 000000000000..134e4af5b324
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-apache-ranger/trino-policies.json
@@ -0,0 +1,189 @@
+{
+ "serviceName": "dev_trino",
+ "serviceId": 203,
+ "serviceDef": {
+ "id": 203,
+ "name": "trino",
+ "displayName": "trino",
+ "implClass": "org.apache.ranger.services.trino.RangerServiceTrino",
+ "label": "Trino",
+ "description": "Trino",
+ "resources": [
+ { "itemId": 1, "name": "catalog", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": true, "recursiveSupported": false, "excludesSupported": true, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino Catalog", "description": "Trino Catalog", "isValidLeaf": true },
+ { "itemId": 2, "name": "schema", "type": "string", "level": 20, "parent": "catalog", "mandatory": true, "lookupSupported": true, "recursiveSupported": false, "excludesSupported": true, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino Schema", "description": "Trino Schema", "isValidLeaf": true },
+ { "itemId": 3, "name": "table", "type": "string", "level": 30, "parent": "schema", "mandatory": true, "lookupSupported": true, "recursiveSupported": false, "excludesSupported": true, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino Table", "description": "Trino Table", "isValidLeaf": true },
+ { "itemId": 4, "name": "column", "type": "string", "level": 40, "parent": "table", "mandatory": true, "lookupSupported": true, "recursiveSupported": false, "excludesSupported": true, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino Column", "description": "Trino Column", "isValidLeaf": true },
+ { "itemId": 5, "name": "trinouser", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino User", "description": "Trino User", "accessTypeRestrictions": ["impersonate"] },
+ { "itemId": 6, "name": "systemproperty", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "System Property", "description": "Trino System Property", "accessTypeRestrictions": ["alter"] },
+ { "itemId": 7, "name": "sessionproperty", "type": "string", "level": 20, "parent": "catalog", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Catalog Session Property", "description": "Trino Catalog Session Property", "accessTypeRestrictions": ["alter"] },
+ { "itemId": 8, "name": "function", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Trino Function", "description": "Trino Function", "accessTypeRestrictions": ["execute", "grant"] },
+ { "itemId": 9, "name": "procedure", "type": "string", "level": 30, "parent": "schema", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Schema Procedure", "description": "Schema Procedure", "accessTypeRestrictions": ["execute", "grant"] },
+ { "itemId": 10, "name": "schemafunction", "type": "string", "level": 30, "parent": "schema", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Schema Function", "description": "Schema Function", "accessTypeRestrictions": [ "create", "drop", "show" ] },
+ { "itemId": 11, "name": "queryid", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Query ID", "description": "Query ID", "accessTypeRestrictions": [ "execute" ] },
+ { "itemId": 12, "name": "sysinfo", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "System Information", "description": "Trino System Information", "accessTypeRestrictions": [ "read_sysinfo", "write_sysinfo" ] },
+ { "itemId": 13, "name": "role", "type": "string", "level": 10, "parent": "", "mandatory": true, "lookupSupported": false, "recursiveSupported": false, "excludesSupported": false, "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Role", "description": "Trino Role", "accessTypeRestrictions": [ "create", "drop", "show", "grant", "revoke" ] }
+ ],
+ "accessTypes": [
+ { "itemId": 1, "name": "select", "label": "Select", "category": "READ" },
+ { "itemId": 2, "name": "insert", "label": "Insert", "category": "UPDATE" },
+ { "itemId": 3, "name": "create", "label": "Create", "category": "CREATE" },
+ { "itemId": 4, "name": "drop", "label": "Drop", "category": "DELETE" },
+ { "itemId": 5, "name": "delete", "label": "Delete", "category": "DELETE" },
+ { "itemId": 6, "name": "use", "label": "Use", "category": "READ" },
+ { "itemId": 7, "name": "alter", "label": "Alter", "category": "CREATE" },
+ { "itemId": 8, "name": "grant", "label": "Grant", "category": "MANAGE" },
+ { "itemId": 9, "name": "revoke", "label": "Revoke", "category": "MANAGE" },
+ { "itemId": 10, "name": "show", "label": "Show", "category": "READ" },
+ { "itemId": 11, "name": "impersonate", "label": "Impersonate", "category": "READ" },
+ {
+ "itemId": 12,
+ "name": "all",
+ "label": "All",
+ "impliedGrants": [
+ "select",
+ "insert",
+ "create",
+ "delete",
+ "drop",
+ "use",
+ "alter",
+ "grant",
+ "revoke",
+ "show",
+ "impersonate",
+ "execute",
+ "read_sysinfo",
+ "write_sysinfo"
+ ]
+ },
+ { "itemId": 13, "name": "execute", "label": "Execute", "category": "READ" },
+ { "itemId": 14, "name": "read_sysinfo", "label": "Read System Information", "category": "MANAGE" },
+ { "itemId": 15, "name": "write_sysinfo", "label": "Write System Information", "category": "MANAGE" }
+ ],
+ "configs": [
+ {
+ "itemId": 1,
+ "name": "username",
+ "type": "string",
+ "mandatory": true,
+ "label": "Username"
+ },
+ {
+ "itemId": 2,
+ "name": "password",
+ "type": "password",
+ "mandatory": false,
+ "label": "Password"
+ },
+ {
+ "itemId": 3,
+ "name": "jdbc.driverClassName",
+ "type": "string",
+ "mandatory": true,
+ "defaultValue": "io.trino.jdbc.TrinoDriver"
+ },
+ {
+ "itemId": 4,
+ "name": "jdbc.url",
+ "type": "string",
+ "mandatory": true
+ },
+ {
+ "itemId": 5,
+ "name": "ranger.plugin.audit.filters",
+ "type": "string",
+ "defaultValue": "[{'accessResult':'DENIED','isAudited':true},{'isAudited':false,'resources':{'queryid':{'values':['*']}},'accessTypes':['execute']},{'isAudited':false,'resources':{'trinouser':{'values':['{USER}']}},'accessTypes':['impersonate']}]"
+ },
+ {
+ "itemId": 6,
+ "name": "ranger.plugin.super.users",
+ "label": "Superusers",
+ "description": "Superusers will have full access to all resources in this Trino instance",
+ "type": "string",
+ "defaultValue": "trino"
+ },
+ {
+ "itemId": 7,
+ "name": "ranger.plugin.super.groups",
+ "label": "Superuser groups",
+ "description": "Users in superuser groups will have full access to all resources in this Trino instance",
+ "type": "string",
+ "defaultValue": "trino"
+ },
+ {
+ "itemId": 8,
+ "name": "service.admin.users",
+ "label": "Service admin users",
+ "description": "Service admin users can create policies for any resource in this Trino instance",
+ "type": "string",
+ "defaultValue": "trino"
+ },
+ {
+ "itemId": 9,
+ "name": "service.admin.groups",
+ "label": "Service admin usergroups",
+ "description": "Users in service admin usergroups can create policies for any resource in this Trino instance",
+ "type": "string",
+ "defaultValue": "trino"
+ }
+ ],
+ "dataMaskDef": {
+ "accessTypes": [
+ { "name": "select" }
+ ],
+ "resources": [
+ { "name": "catalog", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "uiHint":"{ \"singleValue\":true }" },
+ { "name": "schema", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "uiHint":"{ \"singleValue\":true }" },
+ { "name": "table", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "uiHint":"{ \"singleValue\":true }" },
+ { "name": "column", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "uiHint":"{ \"singleValue\":true }" }
+ ],
+ "maskTypes": [
+ { "itemId": 1, "name": "MASK", "label": "Redact", "description": "Replace lowercase with 'x', uppercase with 'X', digits with '0'", "transformer": "cast(regexp_replace(regexp_replace(regexp_replace({col},'([A-Z])', 'X'),'([a-z])','x'),'([0-9])','0') as {type})", "dataMaskOptions": { } },
+ { "itemId": 2, "name": "MASK_SHOW_LAST_4", "label": "Partial mask: show last 4", "description": "Show last 4 characters; replace rest with 'X'", "transformer": "cast(regexp_replace({col}, '(.*)(.{4}$)', x -> regexp_replace(x[1], '.', 'X') || x[2]) as {type})" },
+ { "itemId": 3, "name": "MASK_SHOW_FIRST_4", "label": "Partial mask: show first 4", "description": "Show first 4 characters; replace rest with 'x'", "transformer": "cast(regexp_replace({col}, '(^.{4})(.*)', x -> x[1] || regexp_replace(x[2], '.', 'X')) as {type})" },
+ { "itemId": 4, "name": "MASK_HASH", "label": "Hash", "description": "Hash the value of a varchar with sha256", "transformer": "cast(to_hex(sha256(to_utf8({col}))) as {type})" },
+ { "itemId": 5, "name": "MASK_NULL", "label": "Nullify", "description": "Replace with NULL" },
+ { "itemId": 6, "name": "MASK_NONE", "label": "Unmasked (retain original value)", "description": "No masking" },
+ { "itemId": 12, "name": "MASK_DATE_SHOW_YEAR", "label": "Date: show only year", "description": "Date: show only year", "transformer": "date_trunc('year', {col})" },
+ { "itemId": 13, "name": "CUSTOM", "label": "Custom", "description": "Custom" }
+ ]
+ },
+ "rowFilterDef": {
+ "accessTypes": [
+ { "name": "select" }
+ ],
+ "resources": [
+ { "name": "catalog", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "mandatory": true, "uiHint": "{ \"singleValue\":true }" },
+ { "name": "schema", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "mandatory": true, "uiHint": "{ \"singleValue\":true }" },
+ { "name": "table", "matcherOptions": { "wildCard": "true" }, "lookupSupported": true, "mandatory": true, "uiHint": "{ \"singleValue\":true }" }
+ ]
+ }
+ },
+ "policyVersion": 5,
+ "policies": [
+ {
+ "id": 10,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "checkCanImpersonateUser",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "trinouser": { "values": [ "{USER}" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "impersonate" } ], "users": [ "{USER}" ] }
+ ]
+ },
+ {
+ "id": 11,
+ "service": "dev_trino",
+ "serviceType": "trino",
+ "name": "checkCanExecuteQuery",
+ "policyType": 0,
+ "policyPriority": 0,
+ "resources": { "queryid": { "values": [ "*" ] } },
+ "policyItems": [
+ { "accesses": [ { "type": "execute" } ], "users": [ "{USER}" ] }
+ ]
+ }
+ ]
+}
diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/ranger/TestApacheRanger.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/ranger/TestApacheRanger.java
new file mode 100644
index 000000000000..7bde7104d0a9
--- /dev/null
+++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/ranger/TestApacheRanger.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+package io.trino.tests.product.ranger;
+
+import io.trino.tempto.ProductTest;
+import io.trino.tempto.query.QueryExecutionException;
+import io.trino.tempto.query.QueryExecutor;
+import org.testng.annotations.Test;
+
+import static io.trino.tempto.assertions.QueryAssert.Row.row;
+import static io.trino.testing.TestingNames.randomNameSuffix;
+import static io.trino.tests.product.TestGroups.APACHE_RANGER;
+import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS;
+import static io.trino.tests.product.utils.QueryExecutors.connectToTrino;
+import static io.trino.tests.product.utils.QueryExecutors.onTrino;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class TestApacheRanger
+ extends ProductTest
+{
+ @Test(groups = {APACHE_RANGER, PROFILE_SPECIFIC_TESTS})
+ public void testCreateTableAsSelect()
+ {
+ String tableName = "mariadb.test.nation_" + randomNameSuffix();
+
+ // onTrino() is mapped to user hive. Ranger plugin is configured with hive as a superuser, so all queries from hive should succeed.
+ try (QueryExecutor trino = onTrino()) {
+ try {
+ trino.executeQuery("DROP TABLE IF EXISTS " + tableName);
+ assertThat(trino.executeQuery("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation")).updatedRowsCountIsEqualTo(25);
+ assertThat(trino.executeQuery("SELECT COUNT(*) FROM " + tableName)).containsOnly(row(25));
+ assertThat(trino.executeQuery("TRUNCATE TABLE " + tableName)).updatedRowsCountIsEqualTo(0);
+ assertThat(trino.executeQuery("INSERT INTO " + tableName + " SELECT * FROM tpch.tiny.nation")).updatedRowsCountIsEqualTo(25);
+ assertThat(trino.executeQuery("UPDATE " + tableName + " SET comment = 'updated comment'")).updatedRowsCountIsEqualTo(25);
+ assertThat(trino.executeQuery("DELETE FROM " + tableName)).updatedRowsCountIsEqualTo(25);
+
+ // config 'alice@trino' is mapped to user alice. This user doesn't have any permissions in Ranger, so all queries should fail.
+ try (QueryExecutor userAlice = connectToTrino("alice@trino")) {
+ assertThatThrownBy(() -> userAlice.executeQuery("SELECT COUNT(*) FROM " + tableName)).isInstanceOf(QueryExecutionException.class);
+ assertThatThrownBy(() -> userAlice.executeQuery("TRUNCATE TABLE " + tableName)).isInstanceOf(QueryExecutionException.class);
+ assertThatThrownBy(() -> userAlice.executeQuery("INSERT INTO " + tableName + " SELECT * FROM tpch.tiny.nation")).isInstanceOf(QueryExecutionException.class);
+ assertThatThrownBy(() -> userAlice.executeQuery("UPDATE " + tableName + " SET comment = 'updated comment'")).isInstanceOf(QueryExecutionException.class);
+ assertThatThrownBy(() -> userAlice.executeQuery("DELETE FROM " + tableName)).isInstanceOf(QueryExecutionException.class);
+ }
+ }
+ finally {
+ trino.executeQuery("DROP TABLE IF EXISTS " + tableName);
+ }
+ }
+ }
+}