Skip to content

Commit

Permalink
Add syntax and APIs generalizing privilege grants
Browse files Browse the repository at this point in the history
These changes generalize privilege granting so that
the entity kinds to which the grants apply and the
privileges for those grants can be extended.

This commit also adds APIs to grant, deny, revoke and
list grants on arbitrary entity kinds.  Entity kinds
TABLE and SCHEMA are processed as before, but new
entity kinds can be supported by implementing the
new methods on SystemAccessControl and
SystemSecurityMetadata.
  • Loading branch information
djsstarburst authored and martint committed Feb 7, 2024
1 parent 45f80e6 commit b5988bf
Show file tree
Hide file tree
Showing 34 changed files with 1,004 additions and 277 deletions.
46 changes: 29 additions & 17 deletions core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4
Original file line number Diff line number Diff line change
Expand Up @@ -122,34 +122,34 @@ statement
(IN catalog=identifier)? #createRole
| DROP ROLE name=identifier (IN catalog=identifier)? #dropRole
| GRANT
roles
privilegeOrRole (',' privilegeOrRole)*
TO principal (',' principal)*
(WITH ADMIN OPTION)?
(GRANTED BY grantor)?
(IN catalog=identifier)? #grantRoles
| GRANT
((privilegeOrRole (',' privilegeOrRole)*) | ALL PRIVILEGES)
ON grantObject
TO principal
(WITH GRANT OPTION)? #grantPrivileges
| REVOKE
(ADMIN OPTION FOR)?
roles
privilegeOrRole (',' privilegeOrRole)*
FROM principal (',' principal)*
(GRANTED BY grantor)?
(IN catalog=identifier)? #revokeRoles
| SET ROLE (ALL | NONE | role=identifier)
(IN catalog=identifier)? #setRole
| GRANT
(privilege (',' privilege)* | ALL PRIVILEGES)
ON (SCHEMA | TABLE)? qualifiedName
TO grantee=principal
(WITH GRANT OPTION)? #grant
| DENY
(privilege (',' privilege)* | ALL PRIVILEGES)
ON (SCHEMA | TABLE)? qualifiedName
TO grantee=principal #deny
| REVOKE
(GRANT OPTION FOR)?
((privilegeOrRole (',' privilegeOrRole)*) | ALL PRIVILEGES)
ON grantObject
FROM grantee=principal #revokePrivileges
| DENY
(privilege (',' privilege)* | ALL PRIVILEGES)
ON (SCHEMA | TABLE)? qualifiedName
FROM grantee=principal #revoke
| SHOW GRANTS (ON TABLE? qualifiedName)? #showGrants
ON grantObject
TO grantee=principal #deny
| SET ROLE (ALL | NONE | role=identifier)
(IN catalog=identifier)? #setRole
| SHOW GRANTS (ON grantObject)? #showGrants
| EXPLAIN ('(' explainOption (',' explainOption)* ')')? statement #explain
| EXPLAIN ANALYZE VERBOSE? statement #explainAnalyze
| SHOW CREATE TABLE qualifiedName #showCreateTable
Expand Down Expand Up @@ -918,7 +918,15 @@ sqlStatementList
;

privilege
: CREATE | SELECT | DELETE | INSERT | UPDATE
: CREATE | SELECT | DELETE | INSERT | UPDATE | identifier
;

entityKind
: TABLE | SCHEMA | identifier
;

grantObject
: entityKind? qualifiedName
;

qualifiedName
Expand Down Expand Up @@ -950,6 +958,10 @@ roles
: identifier (',' identifier)*
;

privilegeOrRole
: CREATE | SELECT | DELETE | INSERT | UPDATE | identifier
;

identifier
: IDENTIFIER #unquotedIdentifier
| QUOTED_IDENTIFIER #quotedIdentifier
Expand Down
29 changes: 24 additions & 5 deletions core/trino-main/src/main/java/io/trino/execution/DenyTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@
import io.trino.metadata.RedirectionAwareTableHandle;
import io.trino.security.AccessControl;
import io.trino.spi.connector.CatalogSchemaName;
import io.trino.spi.connector.EntityKindAndName;
import io.trino.spi.connector.EntityPrivilege;
import io.trino.spi.security.Privilege;
import io.trino.sql.tree.Deny;
import io.trino.sql.tree.Expression;
import io.trino.sql.tree.GrantOnType;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static io.trino.execution.PrivilegeUtilities.fetchEntityKindPrivileges;
import static io.trino.execution.PrivilegeUtilities.parseStatementPrivileges;
import static io.trino.metadata.MetadataUtil.createCatalogSchemaName;
import static io.trino.metadata.MetadataUtil.createEntityKindAndName;
import static io.trino.metadata.MetadataUtil.createPrincipal;
import static io.trino.metadata.MetadataUtil.createQualifiedObjectName;
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
Expand Down Expand Up @@ -64,18 +67,22 @@ public String getName()
@Override
public ListenableFuture<Void> execute(Deny statement, QueryStateMachine stateMachine, List<Expression> parameters, WarningCollector warningCollector)
{
if (statement.getType().filter(GrantOnType.SCHEMA::equals).isPresent()) {
String entityKind = statement.getGrantObject().getEntityKind().orElse("TABLE");
if (entityKind.equalsIgnoreCase("TABLE")) {
executeDenyOnTable(stateMachine.getSession(), statement, metadata, accessControl);
}
else if (entityKind.equalsIgnoreCase("SCHEMA")) {
executeDenyOnSchema(stateMachine.getSession(), statement, metadata, accessControl);
}
else {
executeDenyOnTable(stateMachine.getSession(), statement, metadata, accessControl);
executeDenyOnEntity(stateMachine.getSession(), statement, metadata, entityKind, accessControl);
}
return immediateVoidFuture();
}

private static void executeDenyOnSchema(Session session, Deny statement, Metadata metadata, AccessControl accessControl)
{
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getName()));
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getGrantObject().getName()));

if (!metadata.schemaExists(session, schemaName)) {
throw semanticException(SCHEMA_NOT_FOUND, statement, "Schema '%s' does not exist", schemaName);
Expand All @@ -91,7 +98,7 @@ private static void executeDenyOnSchema(Session session, Deny statement, Metadat

private static void executeDenyOnTable(Session session, Deny statement, Metadata metadata, AccessControl accessControl)
{
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName());
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getGrantObject().getName());
RedirectionAwareTableHandle redirection = metadata.getRedirectionAwareTableHandle(session, tableName);
if (redirection.tableHandle().isEmpty()) {
throw semanticException(TABLE_NOT_FOUND, statement, "Table '%s' does not exist", tableName);
Expand All @@ -108,4 +115,16 @@ private static void executeDenyOnTable(Session session, Deny statement, Metadata

metadata.denyTablePrivileges(session, tableName, privileges, createPrincipal(statement.getGrantee()));
}

private static void executeDenyOnEntity(Session session, Deny statement, Metadata metadata, String entityKind, AccessControl accessControl)
{
EntityKindAndName entity = createEntityKindAndName(entityKind, statement.getGrantObject().getName());
Set<EntityPrivilege> privileges = fetchEntityKindPrivileges(entityKind, metadata, statement.getPrivileges());

for (EntityPrivilege privilege : privileges) {
accessControl.checkCanDenyEntityPrivilege(session.toSecurityContext(), privilege, entity, createPrincipal(statement.getGrantee()));
}

metadata.denyEntityPrivileges(session, entity, privileges, createPrincipal(statement.getGrantee()));
}
}
29 changes: 24 additions & 5 deletions core/trino-main/src/main/java/io/trino/execution/GrantTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@
import io.trino.metadata.RedirectionAwareTableHandle;
import io.trino.security.AccessControl;
import io.trino.spi.connector.CatalogSchemaName;
import io.trino.spi.connector.EntityKindAndName;
import io.trino.spi.connector.EntityPrivilege;
import io.trino.spi.security.Privilege;
import io.trino.sql.tree.Expression;
import io.trino.sql.tree.Grant;
import io.trino.sql.tree.GrantOnType;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static io.trino.execution.PrivilegeUtilities.fetchEntityKindPrivileges;
import static io.trino.execution.PrivilegeUtilities.parseStatementPrivileges;
import static io.trino.metadata.MetadataUtil.createCatalogSchemaName;
import static io.trino.metadata.MetadataUtil.createEntityKindAndName;
import static io.trino.metadata.MetadataUtil.createPrincipal;
import static io.trino.metadata.MetadataUtil.createQualifiedObjectName;
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
Expand Down Expand Up @@ -68,18 +71,22 @@ public ListenableFuture<Void> execute(
List<Expression> parameters,
WarningCollector warningCollector)
{
if (statement.getType().filter(GrantOnType.SCHEMA::equals).isPresent()) {
String entityKind = statement.getGrantObject().getEntityKind().orElse("TABLE");
if (entityKind.equalsIgnoreCase("TABLE")) {
executeGrantOnTable(stateMachine.getSession(), statement);
}
else if (entityKind.equalsIgnoreCase("SCHEMA")) {
executeGrantOnSchema(stateMachine.getSession(), statement);
}
else {
executeGrantOnTable(stateMachine.getSession(), statement);
executeGrantOnEntity(stateMachine.getSession(), entityKind, metadata, statement);
}
return immediateVoidFuture();
}

private void executeGrantOnSchema(Session session, Grant statement)
{
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getName()));
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getGrantObject().getName()));

if (!metadata.schemaExists(session, schemaName)) {
throw semanticException(SCHEMA_NOT_FOUND, statement, "Schema '%s' does not exist", schemaName);
Expand All @@ -95,7 +102,7 @@ private void executeGrantOnSchema(Session session, Grant statement)

private void executeGrantOnTable(Session session, Grant statement)
{
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName());
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getGrantObject().getName());
RedirectionAwareTableHandle redirection = metadata.getRedirectionAwareTableHandle(session, tableName);
if (redirection.tableHandle().isEmpty()) {
throw semanticException(TABLE_NOT_FOUND, statement, "Table '%s' does not exist", tableName);
Expand All @@ -112,4 +119,16 @@ private void executeGrantOnTable(Session session, Grant statement)

metadata.grantTablePrivileges(session, tableName, privileges, createPrincipal(statement.getGrantee()), statement.isWithGrantOption());
}

private void executeGrantOnEntity(Session session, String entityKind, Metadata metadata, Grant statement)
{
EntityKindAndName entity = createEntityKindAndName(entityKind, statement.getGrantObject().getName());
Set<EntityPrivilege> privileges = fetchEntityKindPrivileges(entityKind, metadata, statement.getPrivileges());

for (EntityPrivilege privilege : privileges) {
accessControl.checkCanGrantEntityPrivilege(session.toSecurityContext(), privilege, entity, createPrincipal(statement.getGrantee()), statement.isWithGrantOption());
}

metadata.grantEntityPrivileges(session, entity, privileges, createPrincipal(statement.getGrantee()), statement.isWithGrantOption());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
*/
package io.trino.execution;

import io.trino.metadata.Metadata;
import io.trino.spi.TrinoException;
import io.trino.spi.connector.EntityPrivilege;
import io.trino.spi.security.Privilege;
import io.trino.sql.tree.Node;

import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

Expand All @@ -44,6 +48,24 @@ public static Set<Privilege> parseStatementPrivileges(Node statement, Optional<L
return privileges;
}

public static Set<EntityPrivilege> fetchEntityKindPrivileges(String entityKind, Metadata metadata, Optional<List<String>> privileges)
{
Set<EntityPrivilege> allPrivileges = metadata.getAllEntityKindPrivileges(entityKind);
if (privileges.isPresent()) {
return privileges.get().stream()
.map(privilege -> {
EntityPrivilege entityPrivilege = new EntityPrivilege(privilege.toUpperCase(Locale.ENGLISH));
if (!allPrivileges.contains(entityPrivilege)) {
throw new TrinoException(INVALID_PRIVILEGE, "Privilege %s is not supported for entity kind %s".formatted(privilege, entityKind));
}
return entityPrivilege;
}).collect(toImmutableSet());
}
else {
return allPrivileges;
}
}

private static Privilege parsePrivilege(Node statement, String privilegeString)
{
for (Privilege privilege : Privilege.values()) {
Expand Down
30 changes: 25 additions & 5 deletions core/trino-main/src/main/java/io/trino/execution/RevokeTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@
import io.trino.metadata.RedirectionAwareTableHandle;
import io.trino.security.AccessControl;
import io.trino.spi.connector.CatalogSchemaName;
import io.trino.spi.connector.EntityKindAndName;
import io.trino.spi.connector.EntityPrivilege;
import io.trino.spi.security.Privilege;
import io.trino.sql.tree.Expression;
import io.trino.sql.tree.GrantOnType;
import io.trino.sql.tree.Revoke;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static io.trino.execution.PrivilegeUtilities.fetchEntityKindPrivileges;
import static io.trino.execution.PrivilegeUtilities.parseStatementPrivileges;
import static io.trino.metadata.MetadataUtil.createCatalogSchemaName;
import static io.trino.metadata.MetadataUtil.createEntityKindAndName;
import static io.trino.metadata.MetadataUtil.createPrincipal;
import static io.trino.metadata.MetadataUtil.createQualifiedObjectName;
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
Expand Down Expand Up @@ -68,18 +71,23 @@ public ListenableFuture<Void> execute(
List<Expression> parameters,
WarningCollector warningCollector)
{
if (statement.getType().filter(GrantOnType.SCHEMA::equals).isPresent()) {
String entityKind = statement.getGrantObject().getEntityKind().orElse("TABLE");
if (entityKind.equalsIgnoreCase("TABLE")) {
executeRevokeOnTable(stateMachine.getSession(), statement);
}
else if (entityKind.equalsIgnoreCase("SCHEMA")) {
executeRevokeOnSchema(stateMachine.getSession(), statement);
}
else {
executeRevokeOnTable(stateMachine.getSession(), statement);
executeRevokeOnEntity(stateMachine.getSession(), entityKind, metadata, statement);
}

return immediateVoidFuture();
}

private void executeRevokeOnSchema(Session session, Revoke statement)
{
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getName()));
CatalogSchemaName schemaName = createCatalogSchemaName(session, statement, Optional.of(statement.getGrantObject().getName()));

if (!metadata.schemaExists(session, schemaName)) {
throw semanticException(SCHEMA_NOT_FOUND, statement, "Schema '%s' does not exist", schemaName);
Expand All @@ -95,7 +103,7 @@ private void executeRevokeOnSchema(Session session, Revoke statement)

private void executeRevokeOnTable(Session session, Revoke statement)
{
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName());
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getGrantObject().getName());
RedirectionAwareTableHandle redirection = metadata.getRedirectionAwareTableHandle(session, tableName);
if (redirection.tableHandle().isEmpty()) {
throw semanticException(TABLE_NOT_FOUND, statement, "Table '%s' does not exist", tableName);
Expand All @@ -111,4 +119,16 @@ private void executeRevokeOnTable(Session session, Revoke statement)

metadata.revokeTablePrivileges(session, tableName, privileges, createPrincipal(statement.getGrantee()), statement.isGrantOptionFor());
}

private void executeRevokeOnEntity(Session session, String entityKind, Metadata metadata, Revoke statement)
{
EntityKindAndName entity = createEntityKindAndName(entityKind, statement.getGrantObject().getName());
Set<EntityPrivilege> privileges = fetchEntityKindPrivileges(entityKind, metadata, statement.getPrivileges());

for (EntityPrivilege privilege : privileges) {
accessControl.checkCanRevokeEntityPrivilege(session.toSecurityContext(), privilege, entity, createPrincipal(statement.getGrantee()), statement.isGrantOptionFor());
}

metadata.revokeEntityPrivileges(session, entity, privileges, createPrincipal(statement.getGrantee()), statement.isGrantOptionFor());
}
}
Loading

0 comments on commit b5988bf

Please sign in to comment.