Skip to content

Commit ff01dae

Browse files
committed
Add definer and invoker rights access control to materialized views
1 parent 1ea14b5 commit ff01dae

File tree

27 files changed

+899
-88
lines changed

27 files changed

+899
-88
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-main-base/src/main/java/com/facebook/presto/SystemSessionProperties.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
import com.facebook.presto.sql.analyzer.FeaturesConfig.SingleStreamSpillerChoice;
5050
import com.facebook.presto.sql.analyzer.FunctionsConfig;
5151
import com.facebook.presto.sql.planner.CompilerConfig;
52-
import com.facebook.presto.sql.tree.CreateView;
52+
import com.facebook.presto.sql.tree.ViewSecurity;
5353
import com.facebook.presto.tracing.TracingConfig;
5454
import com.google.common.base.Splitter;
5555
import com.google.common.collect.ImmutableList;
@@ -1901,15 +1901,15 @@ public SystemSessionProperties(
19011901
new PropertyMetadata<>(
19021902
DEFAULT_VIEW_SECURITY_MODE,
19031903
format("Set default view security mode. Options are: %s",
1904-
Stream.of(CreateView.Security.values())
1905-
.map(CreateView.Security::name)
1904+
Stream.of(ViewSecurity.values())
1905+
.map(ViewSecurity::name)
19061906
.collect(joining(","))),
19071907
VARCHAR,
1908-
CreateView.Security.class,
1908+
ViewSecurity.class,
19091909
featuresConfig.getDefaultViewSecurityMode(),
19101910
false,
1911-
value -> CreateView.Security.valueOf(((String) value).toUpperCase()),
1912-
CreateView.Security::name),
1911+
value -> ViewSecurity.valueOf(((String) value).toUpperCase()),
1912+
ViewSecurity::name),
19131913
booleanProperty(
19141914
JOIN_PREFILTER_BUILD_SIDE,
19151915
"Prefiltering the build/inner side of a join with keys from the other side",
@@ -3303,9 +3303,9 @@ public static boolean isEagerPlanValidationEnabled(Session session)
33033303
return session.getSystemProperty(EAGER_PLAN_VALIDATION_ENABLED, Boolean.class);
33043304
}
33053305

3306-
public static CreateView.Security getDefaultViewSecurityMode(Session session)
3306+
public static ViewSecurity getDefaultViewSecurityMode(Session session)
33073307
{
3308-
return session.getSystemProperty(DEFAULT_VIEW_SECURITY_MODE, CreateView.Security.class);
3308+
return session.getSystemProperty(DEFAULT_VIEW_SECURITY_MODE, ViewSecurity.class);
33093309
}
33103310

33113311
public static boolean isJoinPrefilterEnabled(Session session)

presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.facebook.presto.sql.tree.Expression;
3434
import com.facebook.presto.sql.tree.NodeRef;
3535
import com.facebook.presto.sql.tree.Parameter;
36+
import com.facebook.presto.sql.tree.ViewSecurity;
3637
import com.facebook.presto.transaction.TransactionManager;
3738
import com.google.common.util.concurrent.ListenableFuture;
3839
import jakarta.inject.Inject;
@@ -41,6 +42,8 @@
4142
import java.util.Map;
4243
import java.util.Optional;
4344

45+
import static com.facebook.presto.SystemSessionProperties.getDefaultViewSecurityMode;
46+
import static com.facebook.presto.SystemSessionProperties.isLegacyMaterializedViews;
4447
import static com.facebook.presto.metadata.MetadataUtil.createQualifiedObjectName;
4548
import static com.facebook.presto.metadata.MetadataUtil.getConnectorIdOrThrow;
4649
import static com.facebook.presto.metadata.MetadataUtil.toSchemaTableName;
@@ -50,6 +53,9 @@
5053
import static com.facebook.presto.sql.analyzer.SemanticErrorCode.MATERIALIZED_VIEW_ALREADY_EXISTS;
5154
import static com.facebook.presto.sql.analyzer.SemanticErrorCode.NOT_SUPPORTED;
5255
import static com.facebook.presto.sql.analyzer.utils.ParameterUtils.parameterExtractor;
56+
import static com.facebook.presto.sql.tree.ViewSecurity.DEFINER;
57+
import static com.facebook.presto.sql.tree.ViewSecurity.INVOKER;
58+
import static com.google.common.base.Preconditions.checkState;
5359
import static com.google.common.collect.ImmutableList.toImmutableList;
5460
import static com.google.common.util.concurrent.Futures.immediateFuture;
5561
import static java.util.Objects.requireNonNull;
@@ -132,12 +138,33 @@ public ListenableFuture<?> execute(CreateMaterializedView statement, Transaction
132138
.collect(toImmutableList());
133139

134140
MaterializedViewColumnMappingExtractor extractor = new MaterializedViewColumnMappingExtractor(analysis, session, metadata);
141+
142+
if (isLegacyMaterializedViews(session) && statement.getSecurity().isPresent()) {
143+
throw new SemanticException(
144+
NOT_SUPPORTED,
145+
statement,
146+
"SECURITY clause is not supported when legacy_materialized_views is enabled");
147+
}
148+
149+
// Security is determined by presence of owner. For definer rights, we store the owner's identity, and for invoker rights,
150+
// we store nothing (absence indicates invoker rights). The legacy behavior is to always store the owner's identity.
151+
ViewSecurity defaultViewSecurityMode = getDefaultViewSecurityMode(session);
152+
Optional<String> owner = Optional.of(session.getUser());
153+
if (!isLegacyMaterializedViews(session) && statement.getSecurity().orElse(defaultViewSecurityMode) == INVOKER) {
154+
owner = Optional.empty();
155+
}
156+
else {
157+
checkState(isLegacyMaterializedViews(session) || statement.getSecurity().orElse(defaultViewSecurityMode) == DEFINER,
158+
"Unexpected view security: %s",
159+
statement.getSecurity());
160+
}
161+
135162
MaterializedViewDefinition viewDefinition = new MaterializedViewDefinition(
136163
sql,
137164
viewName.getSchemaName(),
138165
viewName.getObjectName(),
139166
baseTables,
140-
Optional.of(session.getUser()),
167+
owner,
141168
extractor.getMaterializedViewColumnMappings(),
142169
extractor.getMaterializedViewDirectColumnMappings(),
143170
extractor.getBaseTablesOnOuterJoinSide(),

presto-main-base/src/main/java/com/facebook/presto/execution/CreateViewTask.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.facebook.presto.sql.tree.CreateView;
3030
import com.facebook.presto.sql.tree.Expression;
3131
import com.facebook.presto.sql.tree.Statement;
32+
import com.facebook.presto.sql.tree.ViewSecurity;
3233
import com.facebook.presto.transaction.TransactionManager;
3334
import com.google.common.util.concurrent.ListenableFuture;
3435
import jakarta.inject.Inject;
@@ -42,7 +43,7 @@
4243
import static com.facebook.presto.spi.analyzer.ViewDefinition.ViewColumn;
4344
import static com.facebook.presto.sql.SqlFormatterUtil.getFormattedSql;
4445
import static com.facebook.presto.sql.analyzer.utils.ParameterUtils.parameterExtractor;
45-
import static com.facebook.presto.sql.tree.CreateView.Security.INVOKER;
46+
import static com.facebook.presto.sql.tree.ViewSecurity.INVOKER;
4647
import static com.google.common.collect.ImmutableList.toImmutableList;
4748
import static com.google.common.util.concurrent.Futures.immediateFuture;
4849
import static java.util.Objects.requireNonNull;
@@ -101,7 +102,7 @@ public ListenableFuture<?> execute(CreateView statement, TransactionManager tran
101102

102103
ConnectorTableMetadata viewMetadata = new ConnectorTableMetadata(toSchemaTableName(name), columnMetadata);
103104

104-
CreateView.Security defaultViewSecurityMode = getDefaultViewSecurityMode(session);
105+
ViewSecurity defaultViewSecurityMode = getDefaultViewSecurityMode(session);
105106
Optional<String> owner = Optional.of(session.getUser());
106107
if (statement.getSecurity().orElse(defaultViewSecurityMode) == INVOKER) {
107108
owner = Optional.empty();

presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/Analyzer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import static com.facebook.presto.SystemSessionProperties.isCheckAccessControlOnUtilizedColumnsOnly;
3939
import static com.facebook.presto.SystemSessionProperties.isCheckAccessControlWithSubfields;
40+
import static com.facebook.presto.SystemSessionProperties.isLegacyMaterializedViews;
4041
import static com.facebook.presto.sql.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
4142
import static com.facebook.presto.sql.analyzer.ExpressionTreeUtils.extractExpressions;
4243
import static com.facebook.presto.sql.analyzer.ExpressionTreeUtils.extractExternalFunctions;
@@ -124,7 +125,7 @@ public Analysis analyzeSemantic(Statement statement, boolean isDescribe)
124125
StatementAnalyzer analyzer = new StatementAnalyzer(analysis, metadata, sqlParser, accessControl, session, warningCollector);
125126
analyzer.analyze(rewrittenStatement, Optional.empty());
126127
analyzeForUtilizedColumns(analysis, analysis.getStatement(), warningCollector);
127-
analysis.populateTableColumnAndSubfieldReferencesForAccessControl(isCheckAccessControlOnUtilizedColumnsOnly(session), isCheckAccessControlWithSubfields(session));
128+
analysis.populateTableColumnAndSubfieldReferencesForAccessControl(isCheckAccessControlOnUtilizedColumnsOnly(session), isCheckAccessControlWithSubfields(session), isLegacyMaterializedViews(session));
128129
return analysis;
129130
}
130131

presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/FeaturesConfig.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import com.facebook.presto.common.resourceGroups.QueryType;
2626
import com.facebook.presto.spi.PrestoException;
2727
import com.facebook.presto.spi.function.FunctionMetadata;
28-
import com.facebook.presto.sql.tree.CreateView;
28+
import com.facebook.presto.sql.tree.ViewSecurity;
2929
import com.google.common.annotations.VisibleForTesting;
3030
import com.google.common.base.Splitter;
3131
import com.google.common.collect.ImmutableList;
@@ -47,7 +47,7 @@
4747
import static com.facebook.presto.sql.analyzer.FeaturesConfig.JoinNotNullInferenceStrategy.NONE;
4848
import static com.facebook.presto.sql.analyzer.FeaturesConfig.TaskSpillingStrategy.ORDER_BY_CREATE_TIME;
4949
import static com.facebook.presto.sql.expressions.ExpressionOptimizerManager.DEFAULT_EXPRESSION_OPTIMIZER_NAME;
50-
import static com.facebook.presto.sql.tree.CreateView.Security.DEFINER;
50+
import static com.facebook.presto.sql.tree.ViewSecurity.DEFINER;
5151
import static com.google.common.collect.ImmutableList.toImmutableList;
5252
import static java.lang.String.format;
5353
import static java.util.Objects.requireNonNull;
@@ -296,7 +296,7 @@ public class FeaturesConfig
296296
private boolean generateDomainFilters;
297297
private boolean printEstimatedStatsFromCache;
298298
private boolean removeCrossJoinWithSingleConstantRow = true;
299-
private CreateView.Security defaultViewSecurityMode = DEFINER;
299+
private ViewSecurity defaultViewSecurityMode = DEFINER;
300300
private boolean useHistograms;
301301

302302
private boolean isInlineProjectionsOnValuesEnabled;
@@ -2929,14 +2929,14 @@ public FeaturesConfig setOptimizeConditionalApproxDistinct(boolean optimizeCondi
29292929
return this;
29302930
}
29312931

2932-
public CreateView.Security getDefaultViewSecurityMode()
2932+
public ViewSecurity getDefaultViewSecurityMode()
29332933
{
29342934
return this.defaultViewSecurityMode;
29352935
}
29362936

29372937
@Config("default-view-security-mode")
29382938
@ConfigDescription("Sets the default security mode for view creation. The options are definer/invoker.")
2939-
public FeaturesConfig setDefaultViewSecurityMode(CreateView.Security securityMode)
2939+
public FeaturesConfig setDefaultViewSecurityMode(ViewSecurity securityMode)
29402940
{
29412941
this.defaultViewSecurityMode = securityMode;
29422942
return this;

presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ protected Scope visitInsert(Insert insert, Optional<Scope> scope)
528528
Column::new);
529529

530530
analysis.setUpdatedSourceColumns(Optional.of(Streams.zip(
531-
columnStream, queryScope.getRelationType().getVisibleFields().stream(), (column, field) -> new OutputColumnMetadata(column.getName(), column.getType(), analysis.getSourceColumns(field)))
531+
columnStream, queryScope.getRelationType().getVisibleFields().stream(), (column, field) -> new OutputColumnMetadata(column.getName(), column.getType(), analysis.getSourceColumns(field)))
532532
.collect(toImmutableList())));
533533

534534
return createAndAssignScope(insert, scope, Field.newUnqualified(insert.getLocation(), "rows", BIGINT));
@@ -873,13 +873,25 @@ protected Scope visitRefreshMaterializedView(RefreshMaterializedView node, Optio
873873
Query refreshQuery = tablePredicates.containsKey(toSchemaTableName(viewName)) ?
874874
buildSubqueryWithPredicate(viewQuery, tablePredicates.get(toSchemaTableName(viewName)))
875875
: viewQuery;
876-
// Check if the owner has SELECT permission on the base tables
876+
877+
// Security is determined by presence of owner, like Views. For definer rights, we store the owner's identity, and for invoker rights,
878+
// we store nothing (absence indicates invoker rights). The legacy behavior is to always store the owner's identity.
879+
Session querySession;
880+
if (!isLegacyMaterializedViews(session) && !view.getOwner().isPresent()) {
881+
querySession = session;
882+
}
883+
else {
884+
checkState(isLegacyMaterializedViews(session) || view.getOwner().isPresent(),
885+
"Expected materialized view to have owner (DEFINER security) when legacy materialized views are disabled");
886+
querySession = buildOwnerSession(session, view.getOwner(), metadata.getSessionPropertyManager(), viewName.getCatalogName(), view.getSchema());
887+
}
888+
877889
StatementAnalyzer queryAnalyzer = new StatementAnalyzer(
878890
analysis,
879891
metadata,
880892
sqlParser,
881893
accessControl,
882-
buildOwnerSession(session, view.getOwner(), metadata.getSessionPropertyManager(), viewName.getCatalogName(), view.getSchema()),
894+
querySession,
883895
warningCollector);
884896
queryAnalyzer.analyze(refreshQuery, Scope.create());
885897

@@ -1667,7 +1679,7 @@ private ArgumentsAnalysis mapTableFunctionArgsByPosition(List<ArgumentSpecificat
16671679
private ArgumentAnalysis analyzeArgument(ArgumentSpecification argumentSpecification, TableFunctionArgument argument, Optional<Scope> scope)
16681680
{
16691681
String actualType = getArgumentTypeString(argument);
1670-
switch (argumentSpecification.getArgumentType()){
1682+
switch (argumentSpecification.getArgumentType()) {
16711683
case TableArgumentSpecification.argumentType:
16721684
return analyzeTableArgument(argument, (TableArgumentSpecification) argumentSpecification, scope, actualType);
16731685
case DescriptorArgumentSpecification.argumentType:
@@ -2343,8 +2355,38 @@ private Scope processMaterializedView(
23432355
materializedViewDefinition);
23442356
analysis.setMaterializedViewInfo(materializedView, mvInfo);
23452357

2346-
// Process the view query to analyze base tables for access control
2347-
process(viewQuery, scope);
2358+
// Security is determined by presence of owner. Definer rights if owner is present, invoker rights otherwise.
2359+
Optional<String> owner = materializedViewDefinition.getOwner();
2360+
Identity queryIdentity;
2361+
AccessControl queryAccessControl;
2362+
2363+
// ViewAccessControl is only assigned when owner is present (definer rights).
2364+
// For definer rights, we wrap the access control to allow access to the view definition itself
2365+
// while still enforcing access control on the underlying base tables using the owner's identity.
2366+
// For invoker rights (owner absent), we use the session's identity and standard access control,
2367+
// ensuring the current user's permissions are checked on all tables including the view itself.
2368+
if (owner.isPresent()) {
2369+
queryIdentity = new Identity(owner.get(), Optional.empty(), session.getIdentity().getExtraCredentials());
2370+
queryAccessControl = new ViewAccessControl(accessControl);
2371+
}
2372+
else {
2373+
queryIdentity = session.getIdentity();
2374+
queryAccessControl = accessControl;
2375+
}
2376+
2377+
Session mvQuerySession = createViewSession(
2378+
Optional.of(materializedViewName.getCatalogName()),
2379+
Optional.of(materializedViewDefinition.getSchema()),
2380+
queryIdentity);
2381+
2382+
StatementAnalyzer mvAnalyzer = new StatementAnalyzer(
2383+
analysis,
2384+
metadata,
2385+
sqlParser,
2386+
queryAccessControl,
2387+
mvQuerySession,
2388+
warningCollector);
2389+
mvAnalyzer.analyze(viewQuery, scope);
23482390

23492391
Scope queryScope = process(dataTable, scope);
23502392
RelationType relationType = queryScope.getRelationType().withOnlyVisibleFields().withAlias(materializedViewName.getObjectName(), null);

0 commit comments

Comments
 (0)