This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 8a7348963b [#12480] improvement(server): Add table-like existence
probe privilege (#12482)
8a7348963b is described below
commit 8a7348963b3b031791220c121cbe440c58cabf5d
Author: jarred0214 <[email protected]>
AuthorDate: Mon Aug 24 09:45:35 2026 +0800
[#12480] improvement(server): Add table-like existence probe privilege
(#12482)
### What changes were proposed in this pull request?
This PR updates the Flink connector to preserve Gravitino table
authorization failures when loading tables.
- `BaseCatalog#getTable` now wraps `ForbiddenException` as
`CatalogException` instead of `TableNotExistException`.
- `GravitinoHiveCatalog#getTable` applies the same behavior for Hive,
which overrides the base implementation.
- Added regression tests for both the common `BaseCatalog` path and the
Hive-specific path.
### Why are the changes needed?
When Gravitino server returns `403 Forbidden` for `loadTable`, the Flink
connector currently converts it to `TableNotExistException`. Flink then
reports a misleading error saying the table cannot be found in any
catalog.
The table exists; the real problem is missing permission.
Fix: #12480
### Does this PR introduce _any_ user-facing change?
Yes. Flink users will now see a permission-related catalog error when
table authorization fails, instead of a misleading table-not-found
error.
### How was this patch tested?
Added unit tests:
- `TestBaseCatalog#testGetTableThrowsCatalogExceptionWhenForbidden`
-
`TestGravitinoHiveCatalog#testGetTableThrowsCatalogExceptionWhenForbidden`
I could not run Gradle locally because this machine only has Java 8
installed, while the project requires JDK 17.
---
.../apache/gravitino/authorization/Privilege.java | 4 +-
.../apache/gravitino/authorization/Privileges.java | 42 ++++++
.../test/authorization/TableAuthorizationIT.java | 4 +-
.../authorization/AuthorizationUtils.java | 5 +-
docs/open-api/roles.yaml | 1 +
docs/security/access-control.md | 47 +++---
.../flink/connector/catalog/BaseCatalog.java | 7 +-
.../flink/connector/hive/GravitinoHiveCatalog.java | 2 +-
.../flink/connector/catalog/TestBaseCatalog.java | 38 +++++
.../connector/hive/TestGravitinoHiveCatalog.java | 84 +++++++++++
.../AuthorizationExpressionConstants.java | 15 ++
.../AuthorizationExpressionConverter.java | 5 +
.../web/filter/GravitinoInterceptionService.java | 3 +-
.../authorization/AuthorizeExecutorFactory.java | 6 +-
.../LoadTableAuthorizationExecutor.java | 28 +++-
.../gravitino/server/web/rest/TableOperations.java | 4 +-
.../filter/TestGravitinoInterceptionService.java | 167 +++++++++++++++++++++
.../TestLoadTableAuthorizationExecutor.java | 3 +-
.../TestTableAuthorizationExpression.java | 98 ++++++++----
19 files changed, 499 insertions(+), 64 deletions(-)
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
index 69e1392d69..9122a8d25b 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
@@ -151,7 +151,9 @@ public interface Privilege {
/** The privilege to execute (invoke) a function. */
EXECUTE_FUNCTION(0L, 1L << 31),
/** The privilege to alter a function's metadata. */
- MODIFY_FUNCTION(0L, 1L << 32);
+ MODIFY_FUNCTION(0L, 1L << 32),
+ /** The privilege to probe whether a table-like object exists. */
+ PROBE_TABLE_LIKE(0L, 1L << 33);
private final long highBits;
private final long lowBits;
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
index 393252cf15..5c217bc5cd 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
@@ -32,6 +32,13 @@ public class Privileges {
MetadataObject.Type.CATALOG,
MetadataObject.Type.SCHEMA,
MetadataObject.Type.TABLE);
+ private static final Set<MetadataObject.Type> TABLE_LIKE_SUPPORTED_TYPES =
+ Sets.immutableEnumSet(
+ MetadataObject.Type.METALAKE,
+ MetadataObject.Type.CATALOG,
+ MetadataObject.Type.SCHEMA,
+ MetadataObject.Type.TABLE,
+ MetadataObject.Type.VIEW);
private static final Set<MetadataObject.Type> MODEL_SUPPORTED_TYPES =
Sets.immutableEnumSet(
@@ -127,6 +134,8 @@ public class Privileges {
return ModifyTable.allow();
case SELECT_TABLE:
return SelectTable.allow();
+ case PROBE_TABLE_LIKE:
+ return ProbeTableLike.allow();
// Fileset
case CREATE_FILESET:
@@ -249,6 +258,8 @@ public class Privileges {
return ModifyTable.deny();
case SELECT_TABLE:
return SelectTable.deny();
+ case PROBE_TABLE_LIKE:
+ return ProbeTableLike.deny();
// Fileset
case CREATE_FILESET:
@@ -598,6 +609,37 @@ public class Privileges {
}
}
+ /** The privilege to probe whether a table-like object exists. */
+ public static class ProbeTableLike extends GenericPrivilege<ProbeTableLike> {
+ private static final ProbeTableLike ALLOW_INSTANCE =
+ new ProbeTableLike(Condition.ALLOW, Name.PROBE_TABLE_LIKE);
+ private static final ProbeTableLike DENY_INSTANCE =
+ new ProbeTableLike(Condition.DENY, Name.PROBE_TABLE_LIKE);
+
+ private ProbeTableLike(Condition condition, Name name) {
+ super(condition, name);
+ }
+
+ /**
+ * @return The instance with allow condition of the privilege.
+ */
+ public static ProbeTableLike allow() {
+ return ALLOW_INSTANCE;
+ }
+
+ /**
+ * @return The instance with deny condition of the privilege.
+ */
+ public static ProbeTableLike deny() {
+ return DENY_INSTANCE;
+ }
+
+ @Override
+ public boolean canBindTo(MetadataObject.Type type) {
+ return TABLE_LIKE_SUPPORTED_TYPES.contains(type);
+ }
+ }
+
/** The privilege to create a fileset. */
public static class CreateFileset extends GenericPrivilege<CreateFileset> {
private static final CreateFileset ALLOW_INSTANCE =
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TableAuthorizationIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TableAuthorizationIT.java
index af12862609..b244fad6eb 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TableAuthorizationIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TableAuthorizationIT.java
@@ -219,7 +219,9 @@ public class TableAuthorizationIT extends
BaseRestApiAuthorizationIT {
NameIdentifier[] tablesListNormalUser =
tableCatalogNormalUser.listTables(Namespace.of(SCHEMA));
assertArrayEquals(
new NameIdentifier[] {
- NameIdentifier.of(SCHEMA, "table2"), NameIdentifier.of(SCHEMA,
"table3")
+ NameIdentifier.of(SCHEMA, "table1"),
+ NameIdentifier.of(SCHEMA, "table2"),
+ NameIdentifier.of(SCHEMA, "table3")
},
tablesListNormalUser);
}
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index 76f77770e8..8ca88fb5da 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -111,7 +111,10 @@ public class AuthorizationUtils {
Privilege.Name.CREATE_FILESET, Privilege.Name.WRITE_FILESET,
Privilege.Name.READ_FILESET);
private static final Set<Privilege.Name> TABLE_PRIVILEGES =
Sets.immutableEnumSet(
- Privilege.Name.CREATE_TABLE, Privilege.Name.MODIFY_TABLE,
Privilege.Name.SELECT_TABLE);
+ Privilege.Name.CREATE_TABLE,
+ Privilege.Name.MODIFY_TABLE,
+ Privilege.Name.SELECT_TABLE,
+ Privilege.Name.PROBE_TABLE_LIKE);
private static final Set<Privilege.Name> TOPIC_PRIVILEGES =
Sets.immutableEnumSet(
Privilege.Name.CREATE_TOPIC, Privilege.Name.PRODUCE_TOPIC,
Privilege.Name.CONSUME_TOPIC);
diff --git a/docs/open-api/roles.yaml b/docs/open-api/roles.yaml
index 046b8071c9..888dc966ab 100644
--- a/docs/open-api/roles.yaml
+++ b/docs/open-api/roles.yaml
@@ -199,6 +199,7 @@ components:
- CREATE_SCHEMA
- USE_SCHEMA
- CREATE_TABLE
+ - PROBE_TABLE_LIKE
- MODIFY_TABLE
- SELECT_TABLE
- CREATE_FILESET
diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index 969d8a359c..dc6ddad78c 100755
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -165,29 +165,30 @@ sets the scope of the grant. Binding a privilege to a
type not listed for it is
### Data Object Privileges
-| Privilege | Grantable On | What It Allows
|
-|----------------------|-------------------------------------|--------------------------------------------------------------------|
-| `CREATE_CATALOG` | Metalake | Create catalogs
|
-| `USE_CATALOG` | Metalake, Catalog | Use any catalog
in scope, and reach the objects inside it |
-| `CREATE_SCHEMA` | Metalake, Catalog, Schema | Create schemas
or nested schemas in scope |
-| `USE_SCHEMA` | Metalake, Catalog, Schema | Use any schema
in scope, and reach the objects inside it |
-| `CREATE_TABLE` | Metalake, Catalog, Schema | Create tables
in any schema in scope |
-| `SELECT_TABLE` | Metalake, Catalog, Schema, Table | Read any table
in scope |
-| `MODIFY_TABLE` | Metalake, Catalog, Schema, Table | Read and write
to, and alter the schema of, any table in scope |
-| `CREATE_VIEW` | Metalake, Catalog, Schema | Create views in
any schema in scope |
-| `SELECT_VIEW` | Metalake, Catalog, Schema, View | Read any view
in scope |
-| `CREATE_TOPIC` | Metalake, Catalog, Schema | Create topics
in any schema in scope |
-| `CONSUME_TOPIC` | Metalake, Catalog, Schema, Topic | Consume from
any topic in scope |
-| `PRODUCE_TOPIC` | Metalake, Catalog, Schema, Topic | Consume from,
produce to, and alter any topic in scope |
-| `CREATE_FILESET` | Metalake, Catalog, Schema | Create filesets
in any schema in scope |
-| `READ_FILESET` | Metalake, Catalog, Schema, Fileset | Read any
fileset in scope |
-| `WRITE_FILESET` | Metalake, Catalog, Schema, Fileset | Read, write,
and alter any fileset in scope |
-| `REGISTER_MODEL` | Metalake, Catalog, Schema | Register models
in any schema in scope |
-| `LINK_MODEL_VERSION` | Metalake, Catalog, Schema, Model | Link versions
to any model in scope |
-| `USE_MODEL` | Metalake, Catalog, Schema, Model | Read the
metadata of, and download versions of, any model in scope |
-| `REGISTER_FUNCTION` | Metalake, Catalog, Schema | Register
functions in any schema in scope |
-| `EXECUTE_FUNCTION` | Metalake, Catalog, Schema, Function | Read the
metadata of, and execute, any function in scope |
-| `MODIFY_FUNCTION` | Metalake, Catalog, Schema, Function | Alter or drop
any function in scope |
+| Privilege | Grantable On | What It
Allows |
+|----------------------|----------------------------------------|--------------------------------------------------------------------|
+| `CREATE_CATALOG` | Metalake | Create
catalogs |
+| `USE_CATALOG` | Metalake, Catalog | Use any
catalog in scope, and reach the objects inside it |
+| `CREATE_SCHEMA` | Metalake, Catalog, Schema | Create
schemas or nested schemas in scope |
+| `USE_SCHEMA` | Metalake, Catalog, Schema | Use any
schema in scope, and reach the objects inside it |
+| `CREATE_TABLE` | Metalake, Catalog, Schema | Create
tables in any schema in scope |
+| `PROBE_TABLE_LIKE` | Metalake, Catalog, Schema, Table, View | Probe
whether a table-like object exists without reading its data |
+| `SELECT_TABLE` | Metalake, Catalog, Schema, Table | Read any
table in scope |
+| `MODIFY_TABLE` | Metalake, Catalog, Schema, Table | Read and
write to, and alter the schema of, any table in scope |
+| `CREATE_VIEW` | Metalake, Catalog, Schema | Create views
in any schema in scope |
+| `SELECT_VIEW` | Metalake, Catalog, Schema, View | Read any
view in scope |
+| `CREATE_TOPIC` | Metalake, Catalog, Schema | Create
topics in any schema in scope |
+| `CONSUME_TOPIC` | Metalake, Catalog, Schema, Topic | Consume from
any topic in scope |
+| `PRODUCE_TOPIC` | Metalake, Catalog, Schema, Topic | Consume
from, produce to, and alter any topic in scope |
+| `CREATE_FILESET` | Metalake, Catalog, Schema | Create
filesets in any schema in scope |
+| `READ_FILESET` | Metalake, Catalog, Schema, Fileset | Read any
fileset in scope |
+| `WRITE_FILESET` | Metalake, Catalog, Schema, Fileset | Read, write,
and alter any fileset in scope |
+| `REGISTER_MODEL` | Metalake, Catalog, Schema | Register
models in any schema in scope |
+| `LINK_MODEL_VERSION` | Metalake, Catalog, Schema, Model | Link
versions to any model in scope |
+| `USE_MODEL` | Metalake, Catalog, Schema, Model | Read the
metadata of, and download versions of, any model in scope |
+| `REGISTER_FUNCTION` | Metalake, Catalog, Schema | Register
functions in any schema in scope |
+| `EXECUTE_FUNCTION` | Metalake, Catalog, Schema, Function | Read the
metadata of, and execute, any function in scope |
+| `MODIFY_FUNCTION` | Metalake, Catalog, Schema, Function | Alter or
drop any function in scope |
Either `SELECT_TABLE` or `MODIFY_TABLE` is enough to load a table's metadata,
and the same pairing
holds for views, topics, and filesets.
diff --git
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java
index b116e93c69..bc9d87eac6 100644
---
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java
+++
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java
@@ -271,10 +271,7 @@ public abstract class BaseCatalog extends AbstractCatalog {
} catch (NoSuchTableException e) {
// Fall through to check views.
} catch (ForbiddenException e) {
- // Flink/Calcite speculatively probes tables during multi-part
identifier resolution.
- // Treat authorization failure as table-not-exist to allow Calcite to
fall back to
- // alternative resolution paths (e.g., treating the name as a schema).
- throw new TableNotExistException(catalogName(), tablePath, e);
+ throw new CatalogException(e);
} catch (CatalogException e) {
throw e;
} catch (Exception e) {
@@ -294,7 +291,7 @@ public abstract class BaseCatalog extends AbstractCatalog {
return true;
}
} catch (ForbiddenException e) {
- return false;
+ throw new CatalogException(e);
} catch (Exception e) {
throw new CatalogException(e);
}
diff --git
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java
index feb6362fae..5a3b9971f4 100644
---
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java
+++
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java
@@ -207,7 +207,7 @@ public class GravitinoHiveCatalog extends BaseCatalog {
} catch (NoSuchTableException e) {
// Fall through to check views.
} catch (ForbiddenException e) {
- throw new TableNotExistException(catalogName(), tablePath, e);
+ throw new CatalogException(e);
} catch (Exception e) {
LOG.warn("Failed to load table {} from catalog {}", tablePath,
catalogName(), e);
throw new CatalogException(e);
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java
index 5b3e44d90b..96e9215ba5 100644
---
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java
@@ -33,20 +33,24 @@ import org.apache.flink.table.catalog.CatalogDatabase;
import org.apache.flink.table.catalog.CatalogDatabaseImpl;
import org.apache.flink.table.catalog.CatalogView;
import org.apache.flink.table.catalog.Column;
+import org.apache.flink.table.catalog.ObjectPath;
import org.apache.flink.table.catalog.ResolvedCatalogView;
import org.apache.flink.table.catalog.ResolvedSchema;
import org.apache.flink.table.catalog.TableChange;
+import org.apache.flink.table.catalog.exceptions.CatalogException;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.SchemaChange;
import org.apache.gravitino.catalog.lakehouse.paimon.PaimonConstants;
+import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.flink.connector.PartitionConverter;
import org.apache.gravitino.flink.connector.SchemaAndTablePropertiesConverter;
import org.apache.gravitino.flink.connector.utils.DefaultCatalogCompat;
import org.apache.gravitino.rel.Dialects;
import org.apache.gravitino.rel.Representation;
import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.TableCatalog;
import org.apache.gravitino.rel.ViewCatalog;
import org.apache.gravitino.rel.ViewChange;
import org.apache.gravitino.rel.expressions.distributions.Distributions;
@@ -179,6 +183,40 @@ public class TestBaseCatalog {
Assertions.assertEquals(ImmutableList.of("v1", "v2"), views);
}
+ @Test
+ public void testGetTableThrowsCatalogExceptionWhenForbidden() throws
Exception {
+ Catalog gravitinoCatalog = Mockito.mock(Catalog.class);
+ TableCatalog tableCatalog = Mockito.mock(TableCatalog.class);
+ ForbiddenException forbiddenException = new ForbiddenException("denied");
+ Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog);
+
Mockito.when(tableCatalog.loadTable(Mockito.any())).thenThrow(forbiddenException);
+ BaseCatalog catalog =
+ new TestableBaseCatalog(Mockito.mock(AbstractCatalog.class),
gravitinoCatalog);
+
+ CatalogException catalogException =
+ Assertions.assertThrows(
+ CatalogException.class, () -> catalog.getTable(new
ObjectPath("db", "tbl")));
+
+ Assertions.assertSame(forbiddenException, catalogException.getCause());
+ }
+
+ @Test
+ public void testTableExistsThrowsCatalogExceptionWhenForbidden() throws
Exception {
+ Catalog gravitinoCatalog = Mockito.mock(Catalog.class);
+ TableCatalog tableCatalog = Mockito.mock(TableCatalog.class);
+ ForbiddenException forbiddenException = new ForbiddenException("denied");
+ Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog);
+
Mockito.when(tableCatalog.tableExists(Mockito.any())).thenThrow(forbiddenException);
+ BaseCatalog catalog =
+ new TestableBaseCatalog(Mockito.mock(AbstractCatalog.class),
gravitinoCatalog);
+
+ CatalogException catalogException =
+ Assertions.assertThrows(
+ CatalogException.class, () -> catalog.tableExists(new
ObjectPath("db", "tbl")));
+
+ Assertions.assertSame(forbiddenException, catalogException.getCause());
+ }
+
@Test
public void testGetGravitinoViewChangesSetAndRemoveProperty() {
List<TableChange> tableChanges =
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java
new file mode 100644
index 0000000000..64cd45641b
--- /dev/null
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.flink.connector.hive;
+
+import java.util.Collections;
+import org.apache.flink.table.catalog.AbstractCatalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.exceptions.CatalogException;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.flink.connector.PartitionConverter;
+import org.apache.gravitino.flink.connector.SchemaAndTablePropertiesConverter;
+import org.apache.gravitino.rel.TableCatalog;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestGravitinoHiveCatalog {
+
+ @Test
+ public void testGetTableThrowsCatalogExceptionWhenForbidden() throws
Exception {
+ Catalog gravitinoCatalog = Mockito.mock(Catalog.class);
+ TableCatalog tableCatalog = Mockito.mock(TableCatalog.class);
+ ForbiddenException forbiddenException = new ForbiddenException("denied");
+ Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog);
+
Mockito.when(tableCatalog.loadTable(Mockito.any())).thenThrow(forbiddenException);
+ TestableGravitinoHiveCatalog catalog = new
TestableGravitinoHiveCatalog(gravitinoCatalog);
+
+ CatalogException catalogException =
+ Assertions.assertThrows(
+ CatalogException.class, () -> catalog.getTable(new
ObjectPath("db", "tbl")));
+
+ Assertions.assertSame(forbiddenException, catalogException.getCause());
+ }
+
+ private static class TestableGravitinoHiveCatalog extends
GravitinoHiveCatalog {
+ private final Catalog gravitinoCatalog;
+
+ TestableGravitinoHiveCatalog(Catalog gravitinoCatalog) {
+ super(
+ "test",
+ "default",
+ Collections.emptyMap(),
+ Mockito.mock(SchemaAndTablePropertiesConverter.class),
+ Mockito.mock(PartitionConverter.class),
+ hiveConf(),
+ null);
+ this.gravitinoCatalog = gravitinoCatalog;
+ }
+
+ @Override
+ protected AbstractCatalog realCatalog() {
+ return Mockito.mock(AbstractCatalog.class);
+ }
+
+ @Override
+ protected Catalog catalog() {
+ return gravitinoCatalog;
+ }
+
+ private static HiveConf hiveConf() {
+ HiveConf hiveConf = new HiveConf();
+ hiveConf.set("hive.metastore.uris", "thrift://localhost:9083");
+ return hiveConf;
+ }
+ }
+}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
index c8fd3d07d1..7086f7e125 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
@@ -40,6 +40,21 @@ public class AuthorizationExpressionConstants {
ANY_USE_CATALOG && ANY_USE_SCHEMA && (TABLE::OWNER ||
ANY_SELECT_TABLE || ANY_MODIFY_TABLE)
""";
+ public static final String PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION =
+ """
+ ANY_USE_CATALOG && ANY_USE_SCHEMA &&
+ (ANY_PROBE_TABLE_LIKE || ANY_SELECT_TABLE ||
ANY_MODIFY_TABLE ||
+ ANY_CREATE_TABLE || ANY_CREATE_VIEW)
+ """;
+
+ public static final String LIST_TABLE_LIKE_AUTHORIZATION_EXPRESSION =
+ """
+ ANY(OWNER, METALAKE, CATALOG, SCHEMA, TABLE) ||
+ ANY_USE_CATALOG && ANY_USE_SCHEMA &&
+ (ANY_PROBE_TABLE_LIKE || ANY_SELECT_TABLE ||
ANY_MODIFY_TABLE ||
+ ANY_CREATE_TABLE || ANY_CREATE_VIEW)
+ """;
+
// Adding ANY_CREATE_TABLE here as Spark calls tableExists before creating
a table.
public static final String ICEBERG_LOAD_TABLE_AUTHORIZATION_EXPRESSION =
"""
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
index f98ea1ea02..968f40bbf2 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
@@ -261,6 +261,11 @@ public class AuthorizationExpressionConverter {
"ANY_CREATE_TABLE",
"((ANY(CREATE_TABLE, METALAKE, CATALOG, SCHEMA, TABLE)) "
+ "&& !(ANY(DENY_CREATE_TABLE, METALAKE, CATALOG, SCHEMA,
TABLE)))");
+ expression =
+ expression.replaceAll(
+ "ANY_PROBE_TABLE_LIKE",
+ "((ANY(PROBE_TABLE_LIKE, METALAKE, CATALOG, SCHEMA, TABLE)) "
+ + "&& !(ANY(DENY_PROBE_TABLE_LIKE, METALAKE, CATALOG, SCHEMA,
TABLE)))");
expression =
expression.replaceAll(
"ANY_SELECT_VIEW",
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
index c23818cc17..ae67980587 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
@@ -234,7 +234,8 @@ public class GravitinoInterceptionService implements
InterceptionService {
parameters,
args,
secondaryExpression,
- secondaryExpressionCondition);
+ secondaryExpressionCondition,
+ expressionAnnotation.allowCheckExistence());
boolean authorizeResult =
executor.execute(authorizationRequestContext);
if (!authorizeResult) {
MetadataObject.Type type =
expressionAnnotation.accessMetadataType();
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
index 4d2f4c63aa..4b9f43d599 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
@@ -36,7 +36,8 @@ public class AuthorizeExecutorFactory {
Parameter[] parameters,
Object[] args,
String secondaryExpression,
- ExpressionCondition secondaryExpressionCondition) {
+ ExpressionCondition secondaryExpressionCondition,
+ String allowCheckExistenceExpression) {
return switch (requestType) {
case COMMON -> new CommonAuthorizerExecutor(
expression, metadataContext, pathParams, entityType);
@@ -54,7 +55,8 @@ public class AuthorizeExecutorFactory {
pathParams,
entityType,
secondaryExpression,
- secondaryExpressionCondition);
+ secondaryExpressionCondition,
+ allowCheckExistenceExpression);
case CREATE_SCHEMA -> new CreateSchemaAuthorizationExecutor(
parameters, args, expression, metadataContext, pathParams,
entityType);
};
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
index 5b87979518..a0a99b94e4 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
@@ -22,7 +22,9 @@ import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.Privilege;
import
org.apache.gravitino.server.authorization.annotations.ExpressionCondition;
import
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
@@ -42,6 +44,8 @@ import org.apache.gravitino.server.web.filter.ParameterUtil;
* Legacy clients without the `privileges` parameter will use default
authorization.
*/
public class LoadTableAuthorizationExecutor extends CommonAuthorizerExecutor {
+ private final AuthorizationExpressionEvaluator allowCheckExistenceEvaluator;
+
public LoadTableAuthorizationExecutor(
Parameter[] parameters,
Object[] args,
@@ -50,8 +54,13 @@ public class LoadTableAuthorizationExecutor extends
CommonAuthorizerExecutor {
Map<String, Object> pathParams,
Optional<String> entityType,
String secondaryExpression,
- ExpressionCondition secondaryExpressionCondition) {
+ ExpressionCondition secondaryExpressionCondition,
+ String allowCheckExistenceExpression) {
super(expression, metadataContext, pathParams, entityType);
+ this.allowCheckExistenceEvaluator =
+ StringUtils.isBlank(allowCheckExistenceExpression)
+ ? null
+ : new
AuthorizationExpressionEvaluator(allowCheckExistenceExpression);
if (!shouldCheckModifyTablePrivilege(secondaryExpression,
secondaryExpressionCondition)) {
return;
@@ -65,6 +74,23 @@ public class LoadTableAuthorizationExecutor extends
CommonAuthorizerExecutor {
}
}
+ @Override
+ public boolean execute(AuthorizationRequestContext
authorizationRequestContext) throws Exception {
+ if (super.execute(authorizationRequestContext)) {
+ return true;
+ }
+
+ if (allowCheckExistenceEvaluator == null
+ || !allowCheckExistenceEvaluator.evaluate(
+ metadataContext, pathParams, authorizationRequestContext,
entityType)) {
+ return false;
+ }
+
+ NameIdentifier tableIdentifier =
metadataContext.get(Entity.EntityType.TABLE);
+ return tableIdentifier != null
+ &&
!GravitinoEnv.getInstance().tableDispatcher().tableExists(tableIdentifier);
+ }
+
private static boolean shouldCheckModifyTablePrivilege(
String secondaryExpression, ExpressionCondition
secondaryExpressionCondition) {
return StringUtils.isNotBlank(secondaryExpression)
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
index fa89ee0259..b44f7b1408 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
@@ -99,7 +99,7 @@ public class TableOperations {
idents =
MetadataAuthzHelper.filterByExpression(
metalake,
-
AuthorizationExpressionConstants.FILTER_TABLE_AUTHORIZATION_EXPRESSION,
+
AuthorizationExpressionConstants.LIST_TABLE_LIKE_AUTHORIZATION_EXPRESSION,
Entity.EntityType.TABLE,
idents);
Response response = Utils.ok(new EntityListResponse(idents));
@@ -167,6 +167,8 @@ public class TableOperations {
@ResponseMetered(name = "load-table", absolute = true)
@AuthorizationExpression(
expression =
AuthorizationExpressionConstants.LOAD_TABLE_AUTHORIZATION_EXPRESSION,
+ allowCheckExistence =
+
AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION,
secondaryExpression =
AuthorizationExpressionConstants.MODIFY_TABLE_AUTHORIZATION_EXPRESSION,
secondaryExpressionCondition =
ExpressionCondition.REQUIRED_MODIFY_PRIVILEGES,
accessMetadataType = MetadataObject.Type.TABLE)
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
index 5e9f60ac36..e3548d857c 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
@@ -18,6 +18,8 @@
package org.apache.gravitino.server.web.filter;
import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.CAN_ACCESS_METADATA_AND_TAG;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.LOAD_TABLE_AUTHORIZATION_EXPRESSION;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -48,6 +50,7 @@ import
org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.catalog.TableDispatcher;
import org.apache.gravitino.dto.requests.TagValuesAssociateRequest;
import org.apache.gravitino.dto.responses.ErrorResponse;
import org.apache.gravitino.exceptions.ForbiddenException;
@@ -515,6 +518,99 @@ public class TestGravitinoInterceptionService {
}
}
+ @Test
+ public void
testLoadTableAuthorizationProceedsWhenProbeAllowedTableDoesNotExist()
+ throws Throwable {
+ try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
+ MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
+ mockStatic(GravitinoAuthorizerProvider.class);
+ MockedStatic<AuthorizationUtils> authUtilsMocked =
mockStatic(AuthorizationUtils.class);
+ MockedStatic<GravitinoEnv> envMocked = mockStatic(GravitinoEnv.class))
{
+ principalUtilsMocked
+ .when(PrincipalUtils::getCurrentPrincipal)
+ .thenReturn(new UserPrincipal("tester"));
+
principalUtilsMocked.when(PrincipalUtils::getCurrentUserName).thenReturn("tester");
+
+ authUtilsMocked
+ .when(
+ () ->
+ AuthorizationUtils.checkCurrentUser(
+ ArgumentMatchers.any(), ArgumentMatchers.any(),
ArgumentMatchers.any()))
+ .thenAnswer(invocation -> null);
+
+ GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
+ GravitinoAuthorizer authorizer = tableProbeAuthorizer();
+
authorizerMocked.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+ when(mockedProvider.getGravitinoAuthorizer()).thenReturn(authorizer);
+
+ GravitinoEnv mockEnv = mock(GravitinoEnv.class);
+ TableDispatcher tableDispatcher = mock(TableDispatcher.class);
+ EventBus mockEventBus = mock(EventBus.class);
+ envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
+ when(mockEnv.tableDispatcher()).thenReturn(tableDispatcher);
+ when(mockEnv.eventBus()).thenReturn(mockEventBus);
+
when(tableDispatcher.tableExists(ArgumentMatchers.any())).thenReturn(false);
+
+ MethodInterceptor interceptor = tableLoadInterceptor();
+ MethodInvocation invocation = tableLoadInvocation();
+ Response notFound = Utils.notFound("NoSuchTableException", "Table does
not exist");
+ when(invocation.proceed()).thenReturn(notFound);
+
+ Response response = (Response) interceptor.invoke(invocation);
+
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response.getStatus());
+ verify(invocation).proceed();
+ verify(tableDispatcher).tableExists(ArgumentMatchers.any());
+ verify(mockEventBus, never()).dispatchEvent(ArgumentMatchers.any());
+ }
+ }
+
+ @Test
+ public void
testLoadTableAuthorizationReturnsForbiddenWhenProbeAllowedTableExists()
+ throws Throwable {
+ try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
+ MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
+ mockStatic(GravitinoAuthorizerProvider.class);
+ MockedStatic<AuthorizationUtils> authUtilsMocked =
mockStatic(AuthorizationUtils.class);
+ MockedStatic<GravitinoEnv> envMocked = mockStatic(GravitinoEnv.class))
{
+ principalUtilsMocked
+ .when(PrincipalUtils::getCurrentPrincipal)
+ .thenReturn(new UserPrincipal("tester"));
+
principalUtilsMocked.when(PrincipalUtils::getCurrentUserName).thenReturn("tester");
+
+ authUtilsMocked
+ .when(
+ () ->
+ AuthorizationUtils.checkCurrentUser(
+ ArgumentMatchers.any(), ArgumentMatchers.any(),
ArgumentMatchers.any()))
+ .thenAnswer(invocation -> null);
+
+ GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
+ GravitinoAuthorizer authorizer = tableProbeAuthorizer();
+
authorizerMocked.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+ when(mockedProvider.getGravitinoAuthorizer()).thenReturn(authorizer);
+
+ GravitinoEnv mockEnv = mock(GravitinoEnv.class);
+ TableDispatcher tableDispatcher = mock(TableDispatcher.class);
+ EventBus mockEventBus = spy(new EventBus(Collections.emptyList()));
+ envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
+ when(mockEnv.tableDispatcher()).thenReturn(tableDispatcher);
+ when(mockEnv.eventBus()).thenReturn(mockEventBus);
+
when(tableDispatcher.tableExists(ArgumentMatchers.any())).thenReturn(true);
+
+ MethodInterceptor interceptor = tableLoadInterceptor();
+ MethodInvocation invocation = tableLoadInvocation();
+
+ Response response = (Response) interceptor.invoke(invocation);
+
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(),
response.getStatus());
+ verify(invocation, never()).proceed();
+ verify(tableDispatcher).tableExists(ArgumentMatchers.any());
+ verify(mockEventBus)
+
.dispatchEvent(ArgumentMatchers.any(AuthorizationDenialFailureEvent.class));
+ }
+ }
+
/**
* When {@code checkCurrentUser} throws {@link NoSuchMetalakeException}, the
403 is a
* resource-not-found masquerading as forbidden — no {@link
AuthorizationDenialFailureEvent} is
@@ -582,6 +678,23 @@ public class TestGravitinoInterceptionService {
}
}
+ public static class TestTableLoadOperations {
+
+ @AuthorizationExpression(
+ expression = LOAD_TABLE_AUTHORIZATION_EXPRESSION,
+ allowCheckExistence = PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION,
+ accessMetadataType = MetadataObject.Type.TABLE)
+ public Response loadTable(
+ @AuthorizationMetadata(type = Entity.EntityType.METALAKE) String
metalake,
+ @AuthorizationMetadata(type = Entity.EntityType.CATALOG) String
catalog,
+ @AuthorizationMetadata(type = Entity.EntityType.SCHEMA) String schema,
+ @AuthorizationMetadata(type = Entity.EntityType.TABLE) String table,
+ @AuthorizationRequest(type =
AuthorizationRequest.RequestType.LOAD_TABLE)
+ String requiredPrivileges) {
+ return Utils.ok("unused");
+ }
+ }
+
public static class TestOperations {
@AuthorizationExpression(
@@ -602,6 +715,60 @@ public class TestGravitinoInterceptionService {
}
}
+ private MethodInterceptor tableLoadInterceptor() throws
NoSuchMethodException {
+ Method method =
+ TestTableLoadOperations.class.getMethod(
+ "loadTable", String.class, String.class, String.class,
String.class, String.class);
+ return new
GravitinoInterceptionService().getMethodInterceptors(method).get(0);
+ }
+
+ private MethodInvocation tableLoadInvocation() throws NoSuchMethodException {
+ Method method =
+ TestTableLoadOperations.class.getMethod(
+ "loadTable", String.class, String.class, String.class,
String.class, String.class);
+ MethodInvocation invocation = mock(MethodInvocation.class);
+ when(invocation.getMethod()).thenReturn(method);
+ when(invocation.getArguments())
+ .thenReturn(
+ new Object[] {"testMetalake", "testCatalog", "testSchema",
"missingTable", null});
+ return invocation;
+ }
+
+ private GravitinoAuthorizer tableProbeAuthorizer() {
+ GravitinoAuthorizer authorizer = mock(GravitinoAuthorizer.class);
+ when(authorizer.authorize(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.eq("testMetalake"),
+ ArgumentMatchers.argThat(
+ metadataObject ->
+ metadataObject.type() == MetadataObject.Type.CATALOG
+ && "testCatalog".equals(metadataObject.name())),
+ ArgumentMatchers.eq(Privilege.Name.USE_CATALOG),
+ ArgumentMatchers.any()))
+ .thenReturn(true);
+ when(authorizer.authorize(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.eq("testMetalake"),
+ ArgumentMatchers.argThat(
+ metadataObject ->
+ metadataObject.type() == MetadataObject.Type.SCHEMA
+ && "testSchema".equals(metadataObject.name())),
+ ArgumentMatchers.eq(Privilege.Name.USE_SCHEMA),
+ ArgumentMatchers.any()))
+ .thenReturn(true);
+ when(authorizer.authorize(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.eq("testMetalake"),
+ ArgumentMatchers.argThat(
+ metadataObject ->
+ metadataObject.type() == MetadataObject.Type.METALAKE
+ && "testMetalake".equals(metadataObject.name())),
+ ArgumentMatchers.eq(Privilege.Name.PROBE_TABLE_LIKE),
+ ArgumentMatchers.any()))
+ .thenReturn(true);
+ return authorizer;
+ }
+
private static class MockGravitinoAuthorizer implements GravitinoAuthorizer {
@Override
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
index fcd0f273e7..15bc76ce8f 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
@@ -74,7 +74,8 @@ public class TestLoadTableAuthorizationExecutor {
Collections.emptyMap(),
Optional.empty(),
SECONDARY_EXPRESSION,
- condition);
+ condition,
+ "");
}
private static String expression(LoadTableAuthorizationExecutor executor)
throws Exception {
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestTableAuthorizationExpression.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestTableAuthorizationExpression.java
index f115695416..779fe6062f 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestTableAuthorizationExpression.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestTableAuthorizationExpression.java
@@ -82,47 +82,59 @@ public class TestTableAuthorizationExpression {
}
@Test
- public void testListTable() throws IllegalAccessException, OgnlException,
NoSuchFieldException {
- Field loadTableAuthorizationExpressionField =
+ public void testListTable() throws NoSuchMethodException, OgnlException {
+ Method method =
+ TableOperations.class.getMethod("listTables", String.class,
String.class, String.class);
+ AuthorizationExpression authorizationExpressionAnnotation =
+ method.getAnnotation(AuthorizationExpression.class);
+ String expression = authorizationExpressionAnnotation.expression();
+ MockAuthorizationExpressionEvaluator mockEvaluator =
+ new MockAuthorizationExpressionEvaluator(expression);
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of()));
+ assertTrue(mockEvaluator.getResult(ImmutableSet.of("METALAKE::OWNER")));
+ assertTrue(mockEvaluator.getResult(ImmutableSet.of("CATALOG::OWNER")));
+ assertTrue(
+ mockEvaluator.getResult(ImmutableSet.of("SCHEMA::USE_SCHEMA",
"CATALOG::USE_CATALOG")));
+ }
+
+ @Test
+ public void testListTableFilter()
+ throws IllegalAccessException, OgnlException, NoSuchFieldException {
+ Field listTableLikeAuthorizationExpressionField =
AuthorizationExpressionConstants.class.getDeclaredField(
- "LOAD_TABLE_AUTHORIZATION_EXPRESSION");
- loadTableAuthorizationExpressionField.setAccessible(true);
- String loadTableAuthExpression = (String)
loadTableAuthorizationExpressionField.get(null);
+ "LIST_TABLE_LIKE_AUTHORIZATION_EXPRESSION");
+ listTableLikeAuthorizationExpressionField.setAccessible(true);
+ String expression = (String)
listTableLikeAuthorizationExpressionField.get(null);
MockAuthorizationExpressionEvaluator mockEvaluator =
- new MockAuthorizationExpressionEvaluator(loadTableAuthExpression);
+ new MockAuthorizationExpressionEvaluator(expression);
assertFalse(mockEvaluator.getResult(ImmutableSet.of()));
assertTrue(mockEvaluator.getResult(ImmutableSet.of("METALAKE::OWNER")));
assertTrue(mockEvaluator.getResult(ImmutableSet.of("CATALOG::OWNER")));
- assertFalse(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::OWNER")));
- assertTrue(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::OWNER",
"CATALOG::USE_CATALOG")));
- assertTrue(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::OWNER",
"METALAKE::USE_CATALOG")));
-
assertFalse(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::CREATE_TABLE")));
- assertFalse(
- mockEvaluator.getResult(ImmutableSet.of("SCHEMA::CREATE_TABLE",
"SCHEMA::USE_SCHEMA")));
+ assertTrue(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::OWNER")));
+ assertTrue(mockEvaluator.getResult(ImmutableSet.of("TABLE::OWNER")));
assertFalse(
mockEvaluator.getResult(
- ImmutableSet.of("SCHEMA::CREATE_TABLE", "SCHEMA::USE_SCHEMA",
"CATALOG::USE_CATALOG")));
-
assertFalse(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::SELECT_TABLE")));
- assertFalse(
- mockEvaluator.getResult(ImmutableSet.of("SCHEMA::SELECT_TABLE",
"SCHEMA::USE_SCHEMA")));
+ ImmutableSet.of(
+ "METALAKE::PROBE_TABLE_LIKE",
+ "CATALOG::USE_CATALOG",
+ "SCHEMA::USE_SCHEMA",
+ "CATALOG::DENY_PROBE_TABLE_LIKE")));
+ assertTrue(
+ mockEvaluator.getResult(
+ ImmutableSet.of(
+ "METALAKE::PROBE_TABLE_LIKE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
assertTrue(
mockEvaluator.getResult(
ImmutableSet.of("SCHEMA::SELECT_TABLE", "SCHEMA::USE_SCHEMA",
"CATALOG::USE_CATALOG")));
assertTrue(
mockEvaluator.getResult(
- ImmutableSet.of(
- "CATALOG::SELECT_TABLE", "CATALOG::USE_SCHEMA",
"CATALOG::USE_CATALOG")));
+ ImmutableSet.of("SCHEMA::MODIFY_TABLE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
assertTrue(
mockEvaluator.getResult(
- ImmutableSet.of(
- "METALAKE::SELECT_TABLE", "METALAKE::USE_SCHEMA",
"METALAKE::USE_CATALOG")));
- assertFalse(
+ ImmutableSet.of("SCHEMA::CREATE_TABLE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
+ assertTrue(
mockEvaluator.getResult(
- ImmutableSet.of(
- "METALAKE::SELECT_TABLE",
- "CATALOG::DENY_SELECT_TABLE",
- "METALAKE::USE_SCHEMA",
- "METALAKE::USE_CATALOG")));
+ ImmutableSet.of("SCHEMA::CREATE_VIEW", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
}
@Test
@@ -170,6 +182,40 @@ public class TestTableAuthorizationExpression {
"METALAKE::USE_CATALOG")));
}
+ @Test
+ public void testLoadTableExistenceProbe() throws NoSuchMethodException,
OgnlException {
+ Method method =
+ TableOperations.class.getMethod(
+ "loadTable", String.class, String.class, String.class,
String.class, String.class);
+ AuthorizationExpression authorizationExpressionAnnotation =
+ method.getAnnotation(AuthorizationExpression.class);
+ String expression =
authorizationExpressionAnnotation.allowCheckExistence();
+ MockAuthorizationExpressionEvaluator mockEvaluator =
+ new MockAuthorizationExpressionEvaluator(expression);
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of()));
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of("METALAKE::OWNER")));
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of("CATALOG::OWNER")));
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of("SCHEMA::OWNER")));
+ assertFalse(mockEvaluator.getResult(ImmutableSet.of("TABLE::OWNER")));
+ assertFalse(
+ mockEvaluator.getResult(
+ ImmutableSet.of(
+ "METALAKE::PROBE_TABLE_LIKE",
+ "CATALOG::USE_CATALOG",
+ "SCHEMA::USE_SCHEMA",
+ "CATALOG::DENY_PROBE_TABLE_LIKE")));
+ assertTrue(
+ mockEvaluator.getResult(
+ ImmutableSet.of(
+ "METALAKE::PROBE_TABLE_LIKE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
+ assertTrue(
+ mockEvaluator.getResult(
+ ImmutableSet.of("SCHEMA::SELECT_TABLE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
+ assertTrue(
+ mockEvaluator.getResult(
+ ImmutableSet.of("SCHEMA::MODIFY_TABLE", "CATALOG::USE_CATALOG",
"SCHEMA::USE_SCHEMA")));
+ }
+
@Test
public void testAlterTable() throws NoSuchMethodException, OgnlException {
Method method =