From 00f8e8388b61753b796bc87fdb22fa36ebc51d42 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 14 Dec 2018 21:44:03 -0800 Subject: [PATCH] Add current user security mode for views --- .../src/main/sphinx/sql/create-view.rst | 21 ++++- .../prestosql/execution/CreateViewTask.java | 9 ++- .../prestosql/metadata/MetadataManager.java | 5 +- .../io/prestosql/metadata/ViewDefinition.java | 16 +++- .../sql/rewrite/ShowQueriesRewrite.java | 2 +- .../TestInformationSchemaMetadata.java | 2 +- .../metadata/TestViewDefinition.java | 81 +++++++++++++++++++ .../prestosql/sql/analyzer/TestAnalyzer.java | 15 ++-- .../antlr4/io/prestosql/sql/parser/SqlBase.g4 | 12 ++- .../java/io/prestosql/sql/SqlFormatter.java | 9 ++- .../io/prestosql/sql/parser/AstBuilder.java | 11 ++- .../io/prestosql/sql/tree/CreateView.java | 28 +++++-- .../prestosql/sql/parser/TestSqlParser.java | 13 +-- .../tests/AbstractTestDistributedQueries.java | 14 ++++ 14 files changed, 206 insertions(+), 32 deletions(-) create mode 100644 presto-main/src/test/java/io/prestosql/metadata/TestViewDefinition.java diff --git a/presto-docs/src/main/sphinx/sql/create-view.rst b/presto-docs/src/main/sphinx/sql/create-view.rst index 171374c1c592..1b2520070054 100644 --- a/presto-docs/src/main/sphinx/sql/create-view.rst +++ b/presto-docs/src/main/sphinx/sql/create-view.rst @@ -7,7 +7,9 @@ Synopsis .. code-block:: none - CREATE [ OR REPLACE ] VIEW view_name AS query + CREATE [ OR REPLACE ] VIEW view_name + [ SECURITY { DEFINER | INVOKER } ] + AS query Description ----------- @@ -20,6 +22,23 @@ referenced by another query. The optional ``OR REPLACE`` clause causes the view to be replaced if it already exists rather than raising an error. +Security +-------- + +In the default ``DEFINER`` security mode, tables referenced in the view +are accessed using the permissions of the view owner (the *creator* or +*definer* of the view) rather than the user executing the query. This +allows providing restricted access to the underlying tables, for which +the user may not be allowed to access directly. + +In the ``INVOKER`` security mode, tables referenced in the view are accessed +using the permissions of the user executing the query (the *invoker* of the view). +A view created in this mode is simply a stored query. + +Regardless of the security mode, the ``current_user`` function will +always return the user executing the query and thus may be used +within views to filter out rows or otherwise restrict access. + Examples -------- diff --git a/presto-main/src/main/java/io/prestosql/execution/CreateViewTask.java b/presto-main/src/main/java/io/prestosql/execution/CreateViewTask.java index fcbad0634144..b0fac827d9a4 100644 --- a/presto-main/src/main/java/io/prestosql/execution/CreateViewTask.java +++ b/presto-main/src/main/java/io/prestosql/execution/CreateViewTask.java @@ -40,6 +40,7 @@ import static io.prestosql.metadata.MetadataUtil.createQualifiedObjectName; import static io.prestosql.metadata.ViewDefinition.ViewColumn; import static io.prestosql.sql.SqlFormatterUtil.getFormattedSql; +import static io.prestosql.sql.tree.CreateView.Security.INVOKER; import static java.util.Objects.requireNonNull; public class CreateViewTask @@ -88,7 +89,13 @@ public ListenableFuture execute(CreateView statement, TransactionManager tran .map(field -> new ViewColumn(field.getName().get(), field.getType())) .collect(toImmutableList()); - String data = codec.toJson(new ViewDefinition(sql, session.getCatalog(), session.getSchema(), columns, Optional.of(session.getUser()))); + // use DEFINER security by default + Optional owner = Optional.of(session.getUser()); + if (statement.getSecurity().orElse(null) == INVOKER) { + owner = Optional.empty(); + } + + String data = codec.toJson(new ViewDefinition(sql, session.getCatalog(), session.getSchema(), columns, owner, !owner.isPresent())); metadata.createView(session, name, data, statement.isReplace()); diff --git a/presto-main/src/main/java/io/prestosql/metadata/MetadataManager.java b/presto-main/src/main/java/io/prestosql/metadata/MetadataManager.java index 5c75a34d69fe..c45390163aeb 100644 --- a/presto-main/src/main/java/io/prestosql/metadata/MetadataManager.java +++ b/presto-main/src/main/java/io/prestosql/metadata/MetadataManager.java @@ -836,7 +836,7 @@ public Optional getView(Session session, QualifiedObjectName vie ConnectorViewDefinition view = views.get(viewName.asSchemaTableName()); if (view != null) { ViewDefinition definition = deserializeView(view.getViewData()); - if (view.getOwner().isPresent()) { + if (view.getOwner().isPresent() && !definition.isRunAsInvoker()) { definition = definition.withOwner(view.getOwner().get()); } return Optional.of(definition); @@ -1007,7 +1007,8 @@ private ConnectorMetadata getMetadataForWrite(Session session, ConnectorId conne return getCatalogMetadataForWrite(session, connectorId).getMetadata(); } - private static JsonCodec createTestingViewCodec() + @VisibleForTesting + static JsonCodec createTestingViewCodec() { ObjectMapperProvider provider = new ObjectMapperProvider(); provider.setJsonDeserializers(ImmutableMap.of(Type.class, new TypeDeserializer(new TypeRegistry()))); diff --git a/presto-main/src/main/java/io/prestosql/metadata/ViewDefinition.java b/presto-main/src/main/java/io/prestosql/metadata/ViewDefinition.java index 93d499b61677..3b394b082b26 100644 --- a/presto-main/src/main/java/io/prestosql/metadata/ViewDefinition.java +++ b/presto-main/src/main/java/io/prestosql/metadata/ViewDefinition.java @@ -22,6 +22,7 @@ import java.util.Optional; import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; public final class ViewDefinition @@ -31,6 +32,7 @@ public final class ViewDefinition private final Optional schema; private final List columns; private final Optional owner; + private final boolean runAsInvoker; @JsonCreator public ViewDefinition( @@ -38,13 +40,16 @@ public ViewDefinition( @JsonProperty("catalog") Optional catalog, @JsonProperty("schema") Optional schema, @JsonProperty("columns") List columns, - @JsonProperty("owner") Optional owner) + @JsonProperty("owner") Optional owner, + @JsonProperty("runAsInvoker") boolean runAsInvoker) { this.originalSql = requireNonNull(originalSql, "originalSql is null"); this.catalog = requireNonNull(catalog, "catalog is null"); this.schema = requireNonNull(schema, "schema is null"); this.columns = ImmutableList.copyOf(requireNonNull(columns, "columns is null")); this.owner = requireNonNull(owner, "owner is null"); + this.runAsInvoker = runAsInvoker; + checkArgument(!runAsInvoker || !owner.isPresent(), "owner cannot be present with runAsInvoker"); } @JsonProperty @@ -77,6 +82,12 @@ public Optional getOwner() return owner; } + @JsonProperty + public boolean isRunAsInvoker() + { + return runAsInvoker; + } + @Override public String toString() { @@ -86,13 +97,14 @@ public String toString() .add("schema", schema.orElse(null)) .add("columns", columns) .add("owner", owner.orElse(null)) + .add("runAsInvoker", runAsInvoker) .omitNullValues() .toString(); } public ViewDefinition withOwner(String owner) { - return new ViewDefinition(originalSql, catalog, schema, columns, Optional.of(owner)); + return new ViewDefinition(originalSql, catalog, schema, columns, Optional.of(owner), runAsInvoker); } public static final class ViewColumn diff --git a/presto-main/src/main/java/io/prestosql/sql/rewrite/ShowQueriesRewrite.java b/presto-main/src/main/java/io/prestosql/sql/rewrite/ShowQueriesRewrite.java index 9de2fb6ae50e..e054f5fde5fb 100644 --- a/presto-main/src/main/java/io/prestosql/sql/rewrite/ShowQueriesRewrite.java +++ b/presto-main/src/main/java/io/prestosql/sql/rewrite/ShowQueriesRewrite.java @@ -373,7 +373,7 @@ protected Node visitShowCreate(ShowCreate node, Void context) } Query query = parseView(viewDefinition.get().getOriginalSql(), objectName, node); - String sql = formatSql(new CreateView(createQualifiedName(objectName), query, false), Optional.of(parameters)).trim(); + String sql = formatSql(new CreateView(createQualifiedName(objectName), query, false, Optional.empty()), Optional.of(parameters)).trim(); return singleValueQuery("Create View", sql); } diff --git a/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java b/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java index 81e60d654610..2e9a2b5cb58b 100644 --- a/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java +++ b/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java @@ -74,7 +74,7 @@ public TestInformationSchemaMetadata() new SchemaTableName("test_schema", "test_view"), new SchemaTableName("test_schema", "another_table"))) .withGetViews((connectorSession, prefix) -> { - String viewJson = VIEW_DEFINITION_JSON_CODEC.toJson(new ViewDefinition("select 1", Optional.of("test_catalog"), Optional.of("test_schema"), ImmutableList.of(), Optional.empty())); + String viewJson = VIEW_DEFINITION_JSON_CODEC.toJson(new ViewDefinition("select 1", Optional.of("test_catalog"), Optional.of("test_schema"), ImmutableList.of(), Optional.empty(), false)); SchemaTableName viewName = new SchemaTableName("test_schema", "test_view"); return ImmutableMap.of(viewName, new ConnectorViewDefinition(viewName, Optional.empty(), viewJson)); }).build(); diff --git a/presto-main/src/test/java/io/prestosql/metadata/TestViewDefinition.java b/presto-main/src/test/java/io/prestosql/metadata/TestViewDefinition.java new file mode 100644 index 000000000000..9c10e1848a44 --- /dev/null +++ b/presto-main/src/test/java/io/prestosql/metadata/TestViewDefinition.java @@ -0,0 +1,81 @@ +/* + * 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.prestosql.metadata; + +import io.airlift.json.JsonCodec; +import io.prestosql.metadata.ViewDefinition.ViewColumn; +import io.prestosql.spi.type.IntegerType; +import org.testng.annotations.Test; + +import java.util.Optional; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static io.prestosql.metadata.MetadataManager.createTestingViewCodec; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class TestViewDefinition +{ + private static final JsonCodec CODEC = createTestingViewCodec(); + private static final String BASE_JSON = "" + + "\"originalSql\": \"SELECT 42 x\", " + + "\"columns\": [{\"name\": \"x\", \"type\": \"integer\"}]"; + + @Test + public void testLegacyViewWithoutOwner() + { + // very old view before owner was added + ViewDefinition view = CODEC.fromJson("{" + BASE_JSON + "}"); + assertBaseView(view); + assertFalse(view.getOwner().isPresent()); + } + + @Test + public void testViewWithOwner() + { + // old view before invoker security was added + ViewDefinition view = CODEC.fromJson("{" + BASE_JSON + ", \"owner\": \"abc\"}"); + assertBaseView(view); + assertEquals(view.getOwner(), Optional.of("abc")); + assertFalse(view.isRunAsInvoker()); + } + + @Test + public void testViewSecurityDefiner() + { + ViewDefinition view = CODEC.fromJson("{" + BASE_JSON + ", \"owner\": \"abc\", \"runAsInvoker\": false}"); + assertBaseView(view); + assertEquals(view.getOwner(), Optional.of("abc")); + assertFalse(view.isRunAsInvoker()); + } + + @Test + public void testViewSecurityInvoker() + { + ViewDefinition view = CODEC.fromJson("{" + BASE_JSON + ", \"runAsInvoker\": true}"); + assertBaseView(view); + assertFalse(view.getOwner().isPresent()); + assertTrue(view.isRunAsInvoker()); + } + + private static void assertBaseView(ViewDefinition view) + { + assertEquals(view.getOriginalSql(), "SELECT 42 x"); + assertEquals(view.getColumns().size(), 1); + ViewColumn column = getOnlyElement(view.getColumns()); + assertEquals(column.getName(), "x"); + assertEquals(column.getType(), IntegerType.INTEGER); + } +} diff --git a/presto-main/src/test/java/io/prestosql/sql/analyzer/TestAnalyzer.java b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestAnalyzer.java index aa27e4b02b07..0c8f4734ce4e 100644 --- a/presto-main/src/test/java/io/prestosql/sql/analyzer/TestAnalyzer.java +++ b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestAnalyzer.java @@ -1574,7 +1574,8 @@ public void setup() Optional.of(TPCH_CATALOG), Optional.of("s1"), ImmutableList.of(new ViewColumn("a", BIGINT)), - Optional.of("user"))); + Optional.of("user"), + false)); inSetupTransaction(session -> metadata.createView(session, new QualifiedObjectName(TPCH_CATALOG, "s1", "v1"), viewData1, false)); // stale view (different column type) @@ -1584,7 +1585,8 @@ public void setup() Optional.of(TPCH_CATALOG), Optional.of("s1"), ImmutableList.of(new ViewColumn("a", VARCHAR)), - Optional.of("user"))); + Optional.of("user"), + false)); inSetupTransaction(session -> metadata.createView(session, new QualifiedObjectName(TPCH_CATALOG, "s1", "v2"), viewData2, false)); // view referencing table in different schema from itself and session @@ -1594,7 +1596,8 @@ public void setup() Optional.of(SECOND_CATALOG), Optional.of("s2"), ImmutableList.of(new ViewColumn("a", BIGINT)), - Optional.of("owner"))); + Optional.of("owner"), + false)); inSetupTransaction(session -> metadata.createView(session, new QualifiedObjectName(THIRD_CATALOG, "s3", "v3"), viewData3, false)); // valid view with uppercase column name @@ -1604,7 +1607,8 @@ public void setup() Optional.of("tpch"), Optional.of("s1"), ImmutableList.of(new ViewColumn("a", BIGINT)), - Optional.of("user"))); + Optional.of("user"), + false)); inSetupTransaction(session -> metadata.createView(session, new QualifiedObjectName("tpch", "s1", "v4"), viewData4, false)); // recursive view referencing to itself @@ -1614,7 +1618,8 @@ public void setup() Optional.of(TPCH_CATALOG), Optional.of("s1"), ImmutableList.of(new ViewColumn("a", BIGINT)), - Optional.of("user"))); + Optional.of("user"), + false)); inSetupTransaction(session -> metadata.createView(session, new QualifiedObjectName(TPCH_CATALOG, "s1", "v5"), viewData5, false)); } diff --git a/presto-parser/src/main/antlr4/io/prestosql/sql/parser/SqlBase.g4 b/presto-parser/src/main/antlr4/io/prestosql/sql/parser/SqlBase.g4 index 62655f5ad36d..926abcf20974 100644 --- a/presto-parser/src/main/antlr4/io/prestosql/sql/parser/SqlBase.g4 +++ b/presto-parser/src/main/antlr4/io/prestosql/sql/parser/SqlBase.g4 @@ -56,7 +56,8 @@ statement DROP COLUMN column=qualifiedName #dropColumn | ALTER TABLE tableName=qualifiedName ADD COLUMN column=columnDefinition #addColumn - | CREATE (OR REPLACE)? VIEW qualifiedName AS query #createView + | CREATE (OR REPLACE)? VIEW qualifiedName + (SECURITY (DEFINER | INVOKER))? AS query #createView | DROP VIEW (IF EXISTS)? qualifiedName #dropView | CALL qualifiedName '(' (callArgument (',' callArgument)*)? ')' #call | GRANT @@ -455,12 +456,12 @@ nonReserved : ADD | ALL | ANALYZE | ANY | ARRAY | ASC | AT | BERNOULLI | CALL | CASCADE | CATALOGS | COLUMN | COLUMNS | COMMENT | COMMIT | COMMITTED | CURRENT - | DATA | DATE | DAY | DESC | DISTRIBUTED + | DATA | DATE | DAY | DEFINER | DESC | DISTRIBUTED | EXCLUDING | EXPLAIN | FILTER | FIRST | FOLLOWING | FORMAT | FUNCTIONS | GRANT | GRANTS | GRAPHVIZ | HOUR - | IF | INCLUDING | INPUT | INTERVAL | IO | ISOLATION + | IF | INCLUDING | INPUT | INTERVAL | INVOKER | IO | ISOLATION | JSON | LAST | LATERAL | LEVEL | LIMIT | LOGICAL | MAP | MINUTE | MONTH @@ -468,7 +469,7 @@ nonReserved | ONLY | OPTION | ORDINALITY | OUTPUT | OVER | PARTITION | PARTITIONS | PATH | POSITION | PRECEDING | PRIVILEGES | PROPERTIES | RANGE | READ | RENAME | REPEATABLE | REPLACE | RESET | RESTRICT | REVOKE | ROLLBACK | ROW | ROWS - | SCHEMA | SCHEMAS | SECOND | SERIALIZABLE | SESSION | SET | SETS + | SCHEMA | SCHEMAS | SECOND | SECURITY | SERIALIZABLE | SESSION | SET | SETS | SHOW | SOME | START | STATS | SUBSTRING | SYSTEM | TABLES | TABLESAMPLE | TEXT | TIME | TIMESTAMP | TO | TRANSACTION | TRY_CAST | TYPE | UNBOUNDED | UNCOMMITTED | USE @@ -515,6 +516,7 @@ DATA: 'DATA'; DATE: 'DATE'; DAY: 'DAY'; DEALLOCATE: 'DEALLOCATE'; +DEFINER: 'DEFINER'; DELETE: 'DELETE'; DESC: 'DESC'; DESCRIBE: 'DESCRIBE'; @@ -555,6 +557,7 @@ INSERT: 'INSERT'; INTERSECT: 'INTERSECT'; INTERVAL: 'INTERVAL'; INTO: 'INTO'; +INVOKER: 'INVOKER'; IO: 'IO'; IS: 'IS'; ISOLATION: 'ISOLATION'; @@ -617,6 +620,7 @@ ROWS: 'ROWS'; SCHEMA: 'SCHEMA'; SCHEMAS: 'SCHEMAS'; SECOND: 'SECOND'; +SECURITY: 'SECURITY'; SELECT: 'SELECT'; SERIALIZABLE: 'SERIALIZABLE'; SESSION: 'SESSION'; diff --git a/presto-parser/src/main/java/io/prestosql/sql/SqlFormatter.java b/presto-parser/src/main/java/io/prestosql/sql/SqlFormatter.java index 2ea5a33f5a7a..91d5593ac1e9 100644 --- a/presto-parser/src/main/java/io/prestosql/sql/SqlFormatter.java +++ b/presto-parser/src/main/java/io/prestosql/sql/SqlFormatter.java @@ -531,8 +531,13 @@ protected Void visitCreateView(CreateView node, Integer indent) builder.append("OR REPLACE "); } builder.append("VIEW ") - .append(formatName(node.getName())) - .append(" AS\n"); + .append(formatName(node.getName())); + + node.getSecurity().ifPresent(security -> + builder.append(" SECURITY ") + .append(security.toString())); + + builder.append(" AS\n"); process(node.getQuery(), indent); diff --git a/presto-parser/src/main/java/io/prestosql/sql/parser/AstBuilder.java b/presto-parser/src/main/java/io/prestosql/sql/parser/AstBuilder.java index 273d3951ac4a..a560a6a0dc14 100644 --- a/presto-parser/src/main/java/io/prestosql/sql/parser/AstBuilder.java +++ b/presto-parser/src/main/java/io/prestosql/sql/parser/AstBuilder.java @@ -369,11 +369,20 @@ public Node visitDropColumn(SqlBaseParser.DropColumnContext context) @Override public Node visitCreateView(SqlBaseParser.CreateViewContext context) { + Optional security = Optional.empty(); + if (context.DEFINER() != null) { + security = Optional.of(CreateView.Security.DEFINER); + } + else if (context.INVOKER() != null) { + security = Optional.of(CreateView.Security.INVOKER); + } + return new CreateView( getLocation(context), getQualifiedName(context.qualifiedName()), (Query) visit(context.query()), - context.REPLACE() != null); + context.REPLACE() != null, + security); } @Override diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/CreateView.java b/presto-parser/src/main/java/io/prestosql/sql/tree/CreateView.java index 17ed9578e227..49c06d1dfea7 100644 --- a/presto-parser/src/main/java/io/prestosql/sql/tree/CreateView.java +++ b/presto-parser/src/main/java/io/prestosql/sql/tree/CreateView.java @@ -25,26 +25,33 @@ public class CreateView extends Statement { + public enum Security + { + INVOKER, DEFINER + } + private final QualifiedName name; private final Query query; private final boolean replace; + private final Optional security; - public CreateView(QualifiedName name, Query query, boolean replace) + public CreateView(QualifiedName name, Query query, boolean replace, Optional security) { - this(Optional.empty(), name, query, replace); + this(Optional.empty(), name, query, replace, security); } - public CreateView(NodeLocation location, QualifiedName name, Query query, boolean replace) + public CreateView(NodeLocation location, QualifiedName name, Query query, boolean replace, Optional security) { - this(Optional.of(location), name, query, replace); + this(Optional.of(location), name, query, replace, security); } - private CreateView(Optional location, QualifiedName name, Query query, boolean replace) + private CreateView(Optional location, QualifiedName name, Query query, boolean replace, Optional security) { super(location); this.name = requireNonNull(name, "name is null"); this.query = requireNonNull(query, "query is null"); this.replace = replace; + this.security = requireNonNull(security, "security is null"); } public QualifiedName getName() @@ -62,6 +69,11 @@ public boolean isReplace() return replace; } + public Optional getSecurity() + { + return security; + } + @Override public R accept(AstVisitor visitor, C context) { @@ -77,7 +89,7 @@ public List getChildren() @Override public int hashCode() { - return Objects.hash(name, query, replace); + return Objects.hash(name, query, replace, security); } @Override @@ -92,7 +104,8 @@ public boolean equals(Object obj) CreateView o = (CreateView) obj; return Objects.equals(name, o.name) && Objects.equals(query, o.query) - && Objects.equals(replace, o.replace); + && Objects.equals(replace, o.replace) + && Objects.equals(security, o.security); } @Override @@ -102,6 +115,7 @@ public String toString() .add("name", name) .add("query", query) .add("replace", replace) + .add("security", security) .toString(); } } diff --git a/presto-parser/src/test/java/io/prestosql/sql/parser/TestSqlParser.java b/presto-parser/src/test/java/io/prestosql/sql/parser/TestSqlParser.java index 0107da95ecbd..5a6f9d900469 100644 --- a/presto-parser/src/test/java/io/prestosql/sql/parser/TestSqlParser.java +++ b/presto-parser/src/test/java/io/prestosql/sql/parser/TestSqlParser.java @@ -1347,12 +1347,15 @@ public void testCreateView() { Query query = simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("t"))); - assertStatement("CREATE VIEW a AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, false)); - assertStatement("CREATE OR REPLACE VIEW a AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, true)); + assertStatement("CREATE VIEW a AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, false, Optional.empty())); + assertStatement("CREATE OR REPLACE VIEW a AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, true, Optional.empty())); - assertStatement("CREATE VIEW bar.foo AS SELECT * FROM t", new CreateView(QualifiedName.of("bar", "foo"), query, false)); - assertStatement("CREATE VIEW \"awesome view\" AS SELECT * FROM t", new CreateView(QualifiedName.of("awesome view"), query, false)); - assertStatement("CREATE VIEW \"awesome schema\".\"awesome view\" AS SELECT * FROM t", new CreateView(QualifiedName.of("awesome schema", "awesome view"), query, false)); + assertStatement("CREATE VIEW a SECURITY DEFINER AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, false, Optional.of(CreateView.Security.DEFINER))); + assertStatement("CREATE VIEW a SECURITY INVOKER AS SELECT * FROM t", new CreateView(QualifiedName.of("a"), query, false, Optional.of(CreateView.Security.INVOKER))); + + assertStatement("CREATE VIEW bar.foo AS SELECT * FROM t", new CreateView(QualifiedName.of("bar", "foo"), query, false, Optional.empty())); + assertStatement("CREATE VIEW \"awesome view\" AS SELECT * FROM t", new CreateView(QualifiedName.of("awesome view"), query, false, Optional.empty())); + assertStatement("CREATE VIEW \"awesome schema\".\"awesome view\" AS SELECT * FROM t", new CreateView(QualifiedName.of("awesome schema", "awesome view"), query, false, Optional.empty())); } @Test diff --git a/presto-tests/src/main/java/io/prestosql/tests/AbstractTestDistributedQueries.java b/presto-tests/src/main/java/io/prestosql/tests/AbstractTestDistributedQueries.java index 08ca1c067109..3f3f39038296 100644 --- a/presto-tests/src/main/java/io/prestosql/tests/AbstractTestDistributedQueries.java +++ b/presto-tests/src/main/java/io/prestosql/tests/AbstractTestDistributedQueries.java @@ -942,8 +942,22 @@ public void testViewAccessControl() "SELECT * FROM test_nested_view_access", privilege(getSession().getUser(), "test_view_access", SELECT_COLUMN)); + // verify that INVOKER security runs as session user + assertAccessAllowed( + viewOwnerSession, + "CREATE VIEW test_invoker_view_access SECURITY INVOKER AS SELECT * FROM orders", + privilege("orders", CREATE_VIEW_WITH_SELECT_COLUMNS)); + assertAccessAllowed( + "SELECT * FROM test_invoker_view_access", + privilege(viewOwnerSession.getUser(), "orders", SELECT_COLUMN)); + assertAccessDenied( + "SELECT * FROM test_invoker_view_access", + "Cannot select from columns \\[.*\\] in table .*.orders.*", + privilege(getSession().getUser(), "orders", SELECT_COLUMN)); + assertAccessAllowed(nestedViewOwnerSession, "DROP VIEW test_nested_view_access"); assertAccessAllowed(viewOwnerSession, "DROP VIEW test_view_access"); + assertAccessAllowed(viewOwnerSession, "DROP VIEW test_invoker_view_access"); } @Test