Skip to content

Commit 4c57c73

Browse files
authored
feat(analyzer): Add invoker and definer rights access control to materialized views (#26521)
1 parent 1aa91b0 commit 4c57c73

File tree

40 files changed

+1562
-129
lines changed

40 files changed

+1562
-129
lines changed

presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/Analysis.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -931,12 +931,12 @@ public Map<AccessControlInfo, Map<QualifiedObjectName, Set<String>>> getUtilized
931931
return ImmutableMap.copyOf(utilizedTableColumnReferences);
932932
}
933933

934-
public void populateTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields)
934+
public void populateTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields, boolean isLegacyMaterializedViews)
935935
{
936-
accessControlReferences.addTableColumnAndSubfieldReferencesForAccessControl(getTableColumnAndSubfieldReferencesForAccessControl(checkAccessControlOnUtilizedColumnsOnly, checkAccessControlWithSubfields));
936+
accessControlReferences.addTableColumnAndSubfieldReferencesForAccessControl(getTableColumnAndSubfieldReferencesForAccessControl(checkAccessControlOnUtilizedColumnsOnly, checkAccessControlWithSubfields, isLegacyMaterializedViews));
937937
}
938938

939-
private Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> getTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields)
939+
private Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> getTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields, boolean isLegacyMaterializedViews)
940940
{
941941
Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> references;
942942
if (!checkAccessControlWithSubfields) {
@@ -968,19 +968,26 @@ else if (!checkAccessControlOnUtilizedColumnsOnly) {
968968
})
969969
.collect(toImmutableSet())))));
970970
}
971-
return buildMaterializedViewAccessControl(references);
971+
return buildMaterializedViewAccessControl(references, isLegacyMaterializedViews);
972972
}
973973

974974
/**
975-
* For a query on materialized view, only check the actual required access controls for its base tables. For the materialized view,
976-
* will not check access control by replacing with AllowAllAccessControl.
975+
* For a query on materialized view:
976+
* - When legacy_materialized_views=true: Only check access controls for base tables, bypass access control
977+
* for the materialized view itself by replacing with AllowAllAccessControl.
978+
* - When legacy_materialized_views=false: Check access control for both the materialized view itself
979+
* and all base tables referenced in the view query.
977980
**/
978-
private Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> buildMaterializedViewAccessControl(Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> tableColumnReferences)
981+
private Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> buildMaterializedViewAccessControl(Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> tableColumnReferences, boolean isLegacyMaterializedViews)
979982
{
980983
if (!(getStatement() instanceof Query) || materializedViews.isEmpty()) {
981984
return tableColumnReferences;
982985
}
983986

987+
if (!isLegacyMaterializedViews) {
988+
return tableColumnReferences;
989+
}
990+
984991
Map<AccessControlInfo, Map<QualifiedObjectName, Set<Subfield>>> newTableColumnReferences = new LinkedHashMap<>();
985992

986993
tableColumnReferences.forEach((accessControlInfo, references) -> {

presto-docs/src/main/sphinx/admin/materialized-views.rst

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,59 @@ subsets of large tables.
1818
Use at your own risk in production environments.
1919

2020
To enable materialized views, set :ref:`admin/properties:\`\`experimental.legacy-materialized-views\`\`` = ``false``
21-
in your configuration properties, or use ``SET SESSION legacy_materialized_views = false``.
21+
in your configuration properties.
22+
23+
Alternatively, you can use ``SET SESSION legacy_materialized_views = false`` to enable them for a session,
24+
but only if :ref:`admin/properties:\`\`experimental.allow-legacy-materialized-views-toggle\`\`` = ``true``
25+
is set in the server configuration. The toggle option should only be used in non-production environments
26+
for testing and migration purposes.
27+
28+
Security Modes
29+
--------------
30+
31+
When ``legacy_materialized_views=false``, materialized views support SQL standard security modes
32+
that control whose permissions are used when querying the view:
33+
34+
**SECURITY DEFINER**
35+
The materialized view executes with the permissions of the user who created it. This is the
36+
default mode and matches the behavior of most SQL systems. When using DEFINER mode, column
37+
masks and row filters on base tables are permitted.
38+
39+
**SECURITY INVOKER**
40+
The materialized view executes with the permissions of the user querying it. Each user must
41+
have appropriate permissions on the underlying base tables. When using INVOKER mode and there
42+
are column masks or row filters on the base tables, the materialized view is treated as stale,
43+
since the data would vary by user.
44+
45+
The default security mode can be configured using the ``default_view_security_mode`` session
46+
property. When the ``SECURITY`` clause is not specified in ``CREATE MATERIALIZED VIEW``, this
47+
default is used.
48+
49+
.. note::
50+
51+
The ``REFRESH`` operation always uses DEFINER rights regardless of the view's security mode.
52+
53+
Required Permissions
54+
--------------------
55+
56+
The following permissions are required for materialized view operations when
57+
``legacy_materialized_views=false``:
58+
59+
**CREATE MATERIALIZED VIEW**
60+
* ``CREATE TABLE`` permission
61+
* ``CREATE VIEW`` permission
62+
63+
**REFRESH MATERIALIZED VIEW**
64+
* ``INSERT`` permission (to write new data)
65+
* ``DELETE`` permission (to remove old data)
66+
67+
**DROP MATERIALIZED VIEW**
68+
* ``DROP TABLE`` permission
69+
* ``DROP VIEW`` permission
70+
71+
**Querying a materialized view**
72+
* For DEFINER mode: User needs ``SELECT`` permission on the view itself
73+
* For INVOKER mode: User needs ``SELECT`` permission on all underlying base tables
2274

2375
See Also
2476
--------

presto-docs/src/main/sphinx/admin/properties-session.rst

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,24 @@ Use to configure how long a query can be queued before it is terminated.
529529

530530
The corresponding configuration property is :ref:`admin/properties:\`\`query.max-queued-time\`\``.
531531

532-
Materialized View Properties
533-
----------------------------
532+
View and Materialized View Properties
533+
--------------------------------------
534+
535+
``default_view_security_mode``
536+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
537+
538+
* **Type:** ``string``
539+
* **Allowed values:** ``DEFINER``, ``INVOKER``
540+
* **Default value:** ``DEFINER``
541+
542+
Sets the default security mode for views and materialized views when the ``SECURITY``
543+
clause is not explicitly specified in ``CREATE VIEW`` or ``CREATE MATERIALIZED VIEW``
544+
statements.
545+
546+
* ``DEFINER``: Views execute with the permissions of the user who created them
547+
* ``INVOKER``: Views execute with the permissions of the user querying them
548+
549+
The corresponding configuration property is :ref:`admin/properties:\`\`default-view-security-mode\`\``.
534550

535551
``legacy_materialized_views``
536552
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -539,7 +555,13 @@ Materialized View Properties
539555
* **Default value:** ``true``
540556

541557
Use legacy materialized views implementation. Set to ``false`` to enable the new materialized
542-
views implementation.
558+
views implementation with security modes (DEFINER and INVOKER), automatic query rewriting, and
559+
freshness tracking.
560+
561+
By default, this session property is locked to the server configuration value and cannot be
562+
changed. To allow runtime toggling of this property (for testing/migration purposes only),
563+
set :ref:`admin/properties:\`\`experimental.allow-legacy-materialized-views-toggle\`\`` = ``true``
564+
in the server configuration.
543565

544566
The corresponding configuration property is :ref:`admin/properties:\`\`experimental.legacy-materialized-views\`\``.
545567

presto-docs/src/main/sphinx/admin/properties.rst

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,8 +1244,24 @@ fails with one of these error codes, it can be automatically retried on a backup
12441244
cluster if a retry URL is provided. Available error codes include standard Presto
12451245
error codes such as ``REMOTE_TASK_ERROR``, ``CLUSTER_OUT_OF_MEMORY``, etc.
12461246

1247-
Materialized View Properties
1248-
----------------------------
1247+
View and Materialized View Properties
1248+
-------------------------------------
1249+
1250+
``default-view-security-mode``
1251+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1252+
1253+
* **Type:** ``string``
1254+
* **Allowed values:** ``DEFINER``, ``INVOKER``
1255+
* **Default value:** ``DEFINER``
1256+
1257+
Sets the default security mode for views and materialized views when the ``SECURITY``
1258+
clause is not explicitly specified in ``CREATE VIEW`` or ``CREATE MATERIALIZED VIEW``
1259+
statements.
1260+
1261+
* ``DEFINER``: Views execute with the permissions of the user who created them
1262+
* ``INVOKER``: Views execute with the permissions of the user querying them
1263+
1264+
The corresponding session property is :ref:`admin/properties-session:\`\`default_view_security_mode\`\``.
12491265

12501266
``experimental.legacy-materialized-views``
12511267
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1262,3 +1278,21 @@ The corresponding session property is :ref:`admin/properties-session:\`\`legacy_
12621278
.. warning::
12631279

12641280
Materialized views are experimental. The SPI and behavior may change in future releases.
1281+
1282+
``experimental.allow-legacy-materialized-views-toggle``
1283+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1284+
1285+
* **Type:** ``boolean``
1286+
* **Default value:** ``false``
1287+
1288+
Allow the ``legacy_materialized_views`` session property to be changed at runtime.
1289+
By default, the session property value is locked to the server configuration value
1290+
and cannot be changed per-session.
1291+
1292+
Set this to ``true`` to allow users to toggle between legacy and new materialized
1293+
views implementations using the session property. This is intended for testing and
1294+
migration purposes only.
1295+
1296+
.. warning::
1297+
1298+
This should only be enabled in non-production environments.

presto-docs/src/main/sphinx/sql/create-materialized-view.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Synopsis
1616
1717
CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] view_name
1818
[ COMMENT 'string' ]
19+
[ SECURITY { DEFINER | INVOKER } ]
1920
[ WITH ( property_name = expression [, ...] ) ]
2021
AS query
2122
@@ -31,6 +32,15 @@ not already exist.
3132

3233
The optional ``COMMENT`` clause stores a description of the materialized view in the metastore.
3334

35+
The optional ``SECURITY`` clause specifies the security mode for the materialized view. When
36+
``legacy_materialized_views=false``:
37+
38+
* ``SECURITY DEFINER``: The view executes with the permissions of the user who created it. This is the default mode if ``SECURITY`` is not specified and matches the behavior of most SQL systems.
39+
* ``SECURITY INVOKER``: The view executes with the permissions of the user querying it. Each user must have appropriate permissions on the underlying base tables.
40+
41+
When ``legacy_materialized_views=true``, the ``SECURITY`` clause is not supported and will
42+
cause an error if used.
43+
3444
The optional ``WITH`` clause specifies connector-specific properties. Connector properties vary by
3545
connector implementation. Consult connector documentation for supported properties.
3646

@@ -47,6 +57,26 @@ Create a materialized view with daily aggregations::
4757
FROM orders
4858
GROUP BY date_trunc('day', order_date), region
4959

60+
Create a materialized view with DEFINER security mode::
61+
62+
CREATE MATERIALIZED VIEW daily_sales
63+
SECURITY DEFINER
64+
AS
65+
SELECT date_trunc('day', order_date) AS day,
66+
region,
67+
SUM(amount) AS total_sales
68+
FROM orders
69+
GROUP BY date_trunc('day', order_date), region
70+
71+
Create a materialized view with INVOKER security mode::
72+
73+
CREATE MATERIALIZED VIEW user_specific_sales
74+
SECURITY INVOKER
75+
AS
76+
SELECT date_trunc('day', order_date) AS day,
77+
SUM(amount) AS total_sales
78+
FROM orders
79+
GROUP BY date_trunc('day', order_date)
5080

5181
Create a materialized view with connector properties::
5282

presto-hive/src/main/java/com/facebook/presto/hive/HiveMetadata.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2525,6 +2525,7 @@ public void createMaterializedView(ConnectorSession session, ConnectorTableMetad
25252525
viewDefinition.getTable(),
25262526
viewDefinition.getBaseTables(),
25272527
viewDefinition.getOwner(),
2528+
viewDefinition.getSecurityMode(),
25282529
viewDefinition.getColumnMappings(),
25292530
viewDefinition.getBaseTablesOnOuterJoinSide(),
25302531
Optional.of(getPartitionedBy(viewMetadata.getProperties())));

presto-hive/src/test/java/com/facebook/presto/hive/TestHiveMaterializedViewLogicalPlanner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ protected QueryRunner createQueryRunner()
101101
{
102102
return HiveQueryRunner.createQueryRunner(
103103
ImmutableList.of(ORDERS, LINE_ITEM, CUSTOMER, NATION, SUPPLIER),
104-
ImmutableMap.of(),
104+
ImmutableMap.of("experimental.allow-legacy-materialized-views-toggle", "true"),
105105
Optional.empty());
106106
}
107107

presto-hive/src/test/java/com/facebook/presto/hive/TestHiveMaterializedViewUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,6 @@ private static MaterializedViewDefinition getConnectorMaterializedViewDefinition
659659
List<SchemaTableName> tables,
660660
Map<String, Map<SchemaTableName, String>> originalColumnMapping)
661661
{
662-
return new MaterializedViewDefinition(SQL, SCHEMA_NAME, TABLE_NAME, tables, Optional.empty(), originalColumnMapping, originalColumnMapping, ImmutableList.of(), Optional.empty());
662+
return new MaterializedViewDefinition(SQL, SCHEMA_NAME, TABLE_NAME, tables, Optional.empty(), Optional.empty(), originalColumnMapping, originalColumnMapping, ImmutableList.of(), Optional.empty());
663663
}
664664
}

presto-main-base/src/main/java/com/facebook/presto/SystemSessionProperties.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.facebook.presto.memory.NodeMemoryConfig;
3030
import com.facebook.presto.spi.PrestoException;
3131
import com.facebook.presto.spi.eventlistener.CTEInformation;
32+
import com.facebook.presto.spi.security.ViewSecurity;
3233
import com.facebook.presto.spi.session.PropertyMetadata;
3334
import com.facebook.presto.spiller.NodeSpillConfig;
3435
import com.facebook.presto.sql.analyzer.FeaturesConfig;
@@ -49,7 +50,6 @@
4950
import com.facebook.presto.sql.analyzer.FeaturesConfig.SingleStreamSpillerChoice;
5051
import com.facebook.presto.sql.analyzer.FunctionsConfig;
5152
import com.facebook.presto.sql.planner.CompilerConfig;
52-
import com.facebook.presto.sql.tree.CreateView;
5353
import com.facebook.presto.tracing.TracingConfig;
5454
import com.google.common.base.Splitter;
5555
import com.google.common.collect.ImmutableList;
@@ -1357,12 +1357,24 @@ public SystemSessionProperties(
13571357
"Enable query optimization with materialized view",
13581358
featuresConfig.isQueryOptimizationWithMaterializedViewEnabled(),
13591359
true),
1360-
booleanProperty(
1360+
new PropertyMetadata<>(
13611361
LEGACY_MATERIALIZED_VIEWS,
1362-
"Experimental: Use legacy materialized views. This feature is under active development and may change" +
1363-
"or be removed at any time. Do not disable in production environments.",
1362+
"Experimental: Use legacy materialized views. This feature is under active development and may change " +
1363+
"or be removed at any time. Do not disable in production environments. " +
1364+
"To allow toggling this property via session, set experimental.allow-legacy-materialized-views-toggle=true in config.",
1365+
BOOLEAN,
1366+
Boolean.class,
13641367
featuresConfig.isLegacyMaterializedViews(),
1365-
true),
1368+
true,
1369+
value -> {
1370+
if (!featuresConfig.isAllowLegacyMaterializedViewsToggle()) {
1371+
throw new PrestoException(INVALID_SESSION_PROPERTY,
1372+
"Cannot toggle legacy_materialized_views session property. " +
1373+
"Set experimental.allow-legacy-materialized-views-toggle=true in config to allow changing this setting.");
1374+
}
1375+
return (Boolean) value;
1376+
},
1377+
object -> object),
13661378
booleanProperty(
13671379
MATERIALIZED_VIEW_ALLOW_FULL_REFRESH_ENABLED,
13681380
"Allow full refresh of MV when it's empty - potentially high cost.",
@@ -1913,15 +1925,15 @@ public SystemSessionProperties(
19131925
new PropertyMetadata<>(
19141926
DEFAULT_VIEW_SECURITY_MODE,
19151927
format("Set default view security mode. Options are: %s",
1916-
Stream.of(CreateView.Security.values())
1917-
.map(CreateView.Security::name)
1928+
Stream.of(ViewSecurity.values())
1929+
.map(ViewSecurity::name)
19181930
.collect(joining(","))),
19191931
VARCHAR,
1920-
CreateView.Security.class,
1932+
ViewSecurity.class,
19211933
featuresConfig.getDefaultViewSecurityMode(),
19221934
false,
1923-
value -> CreateView.Security.valueOf(((String) value).toUpperCase()),
1924-
CreateView.Security::name),
1935+
value -> ViewSecurity.valueOf(((String) value).toUpperCase()),
1936+
ViewSecurity::name),
19251937
booleanProperty(
19261938
JOIN_PREFILTER_BUILD_SIDE,
19271939
"Prefiltering the build/inner side of a join with keys from the other side",
@@ -3320,9 +3332,9 @@ public static boolean isEagerPlanValidationEnabled(Session session)
33203332
return session.getSystemProperty(EAGER_PLAN_VALIDATION_ENABLED, Boolean.class);
33213333
}
33223334

3323-
public static CreateView.Security getDefaultViewSecurityMode(Session session)
3335+
public static ViewSecurity getDefaultViewSecurityMode(Session session)
33243336
{
3325-
return session.getSystemProperty(DEFAULT_VIEW_SECURITY_MODE, CreateView.Security.class);
3337+
return session.getSystemProperty(DEFAULT_VIEW_SECURITY_MODE, ViewSecurity.class);
33263338
}
33273339

33283340
public static boolean isJoinPrefilterEnabled(Session session)

0 commit comments

Comments
 (0)