Skip to content

Commit 27e7e3d

Browse files
committed
[SC-4110] SQL ACL
## What changes were proposed in this pull request? This is a port of all SQL ACL related patches to DB Spark 2.1: - 8824d9d [SC-4817] DB Spark ACL extensions - c60f734 [SC-4816] Add rule to resolve commands. - 0ddb5db [SC-4862] Modify ACL enforcement for Explain - 0ee858d [SC-4974] Make specifying securable non-optional for SHOW GRANT - 4febf33 [SC-4732] CheckPermissions rule for commands and logical plans - 61cb6df [SC-4815] SQL ACL synchronization for Acl Client and Metastore - bdaf5f8 [SC-4770] SQL ACL Client Databricks connector - 8244c7f [SC-4797] Reduced action set for SQL ACLs - d4ccace [SC-4713] SQL ACL command parser - 432a51d [SC-4110] Ownership chaining logic for Select requests on views - 86a2b89 Modified interface for AclClient - 539134e SQL ACL Interfaces ## How was this patch tested? Various test suites. Author: Herman van Hovell <[email protected]> Author: Srinath Shankar <[email protected]> Author: Shixiong Zhu <[email protected]> Author: Sameer Agarwal <[email protected]> Author: Sameer Agarwal <[email protected]> Closes apache#125 from hvanhovell/SC-4110-branch-2.1.
1 parent 72643d9 commit 27e7e3d

39 files changed

+4520
-23
lines changed

project/SparkBuild.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,10 @@ object Catalyst {
535535
}
536536

537537
object SQL {
538-
lazy val settings = Seq(
538+
lazy val settings = antlr4Settings ++ Seq(
539+
antlr4PackageName in Antlr4 := Some("com.databricks.sql.acl"),
540+
antlr4GenListener in Antlr4 := true,
541+
antlr4GenVisitor in Antlr4 := true,
539542
initialCommands in console :=
540543
"""
541544
|import org.apache.spark.SparkContext

sql/core/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@
177177
</execution>
178178
</executions>
179179
</plugin>
180+
<plugin>
181+
<groupId>org.antlr</groupId>
182+
<artifactId>antlr4-maven-plugin</artifactId>
183+
<executions>
184+
<execution>
185+
<goals>
186+
<goal>antlr4</goal>
187+
</goals>
188+
</execution>
189+
</executions>
190+
<configuration>
191+
<visitor>true</visitor>
192+
<sourceDirectory>../core/src/main/antlr4</sourceDirectory>
193+
</configuration>
194+
</plugin>
180195
</plugins>
181196
</build>
182197
</project>
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright © 2016 Databricks, Inc.
3+
*
4+
* Portions of this software incorporate or are derived from software contained within Apache Spark,
5+
* and this modified software differs from the Apache Spark software provided under the Apache
6+
* License, Version 2.0, a copy of which you may obtain at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*/
9+
grammar AclCommandBase;
10+
11+
tokens {
12+
DELIMITER
13+
}
14+
15+
singleStatement
16+
: statement EOF
17+
;
18+
19+
statement
20+
: managePermissions #managePermissionsAlt
21+
| ALTER securable OWNER TO identifier #alterOwner
22+
| MSCK REPAIR securable PRIVILEGES #repairPrivileges
23+
| SHOW GRANT identifier? ON (ALL| securable) #showPermissions
24+
| .*? #passThrough
25+
;
26+
27+
managePermissions
28+
: GRANT (actionTypes+=identifier (',' actionTypes+=identifier)* | ALL PRIVILEGES)
29+
ON securable TO grantee=identifier
30+
(WITH GRANT OPTION)?
31+
| REVOKE (GRANT OPTION FOR)?
32+
(actionTypes+=identifier (',' actionTypes+=identifier)* | ALL PRIVILEGES)
33+
ON securable FROM grantee=identifier
34+
;
35+
36+
securable
37+
: objectType=CATALOG
38+
| objectType=DATABASE identifier
39+
| objectType=VIEW qualifiedName
40+
| objectType=FUNCTION qualifiedName
41+
| ANONYMOUS objectType=FUNCTION
42+
| objectType=TABLE? qualifiedName
43+
;
44+
45+
qualifiedName
46+
: identifier ('.' identifier)*
47+
;
48+
49+
identifier
50+
: IDENTIFIER #unquotedIdentifier
51+
| quotedIdentifier #quotedIdentifierAlternative
52+
| nonReserved #unquotedIdentifier
53+
;
54+
55+
quotedIdentifier
56+
: BACKQUOTED_IDENTIFIER
57+
;
58+
59+
nonReserved
60+
: ALTER | OWNER | TO | MSCK | REPAIR | PRIVILEGES | SHOW | GRANT | ON | ALL | WITH | OPTION |
61+
REVOKE | FOR | FROM | CATALOG | DATABASE | TABLE | VIEW | FUNCTION | ANONYMOUS
62+
;
63+
64+
ALTER: 'ALTER';
65+
OWNER: 'OWNER';
66+
TO: 'TO';
67+
MSCK: 'MSCK';
68+
REPAIR: 'REPAIR';
69+
PRIVILEGES: 'PRIVILEGES';
70+
SHOW: 'SHOW';
71+
GRANT: 'GRANT';
72+
ON: 'ON';
73+
ALL: 'ALL';
74+
WITH: 'WITH';
75+
OPTION: 'OPTION';
76+
REVOKE: 'REVOKE';
77+
FOR: 'FOR';
78+
FROM: 'FROM';
79+
CATALOG: 'CATALOG';
80+
DATABASE: 'DATABASE';
81+
TABLE: 'TABLE';
82+
VIEW: 'VIEW';
83+
FUNCTION: 'FUNCTION';
84+
ANONYMOUS: 'ANONYMOUS';
85+
86+
STRING
87+
: '\'' ( ~('\''|'\\') | ('\\' .) )* '\''
88+
| '\"' ( ~('\"'|'\\') | ('\\' .) )* '\"'
89+
;
90+
91+
IDENTIFIER
92+
: (LETTER | DIGIT | '_')+
93+
;
94+
95+
BACKQUOTED_IDENTIFIER
96+
: '`' ( ~'`' | '``' )* '`'
97+
;
98+
99+
fragment DIGIT
100+
: [0-9]
101+
;
102+
103+
fragment LETTER
104+
: [A-Z]
105+
;
106+
107+
SIMPLE_COMMENT
108+
: '--' ~[\r\n]* '\r'? '\n'? -> channel(HIDDEN)
109+
;
110+
111+
BRACKETED_COMMENT
112+
: '/*' .*? '*/' -> channel(HIDDEN)
113+
;
114+
115+
WS : [ \r\n\t]+ -> channel(HIDDEN)
116+
;
117+
118+
// Catch-all for anything we can't recognize.
119+
// We use this to be able to ignore and recover all the text
120+
// when splitting statements with DelimiterLexer
121+
UNRECOGNIZED
122+
: .
123+
;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright © 2016 Databricks, Inc.
3+
*
4+
* Portions of this software incorporate or are derived from software contained within Apache Spark,
5+
* and this modified software differs from the Apache Spark software provided under the Apache
6+
* License, Version 2.0, a copy of which you may obtain at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*/
9+
package com.databricks.sql.acl
10+
11+
trait AclClient {
12+
/**
13+
* Given a set of actions on securables, returns the subset which the underlying principal
14+
* has permissions to execute.
15+
*
16+
* @param requests to check.
17+
*/
18+
def getValidPermissions(requests: Traversable[(Securable, Action)]): Set[(Securable, Action)]
19+
20+
/**
21+
* Retrieve the owners for the given securables.
22+
*/
23+
def getOwners(securables: Traversable[Securable]): Map[Securable, Principal]
24+
25+
/**
26+
* List the permission for an (optional) principal and an (optional) object securable. The
27+
* principal issuing this command must meet one of the following requirements:
28+
* 1. The principal must be equal to or an (indirect) member of the given principal.
29+
* 2. The principal must own the given securable or one of its hierarchical parents.
30+
* A [[SecurityException]] is thrown when neither of these requirements is met.
31+
*/
32+
@throws[SecurityException]("permission denied")
33+
def listPermissions(
34+
principal: Option[Principal] = None,
35+
securable: Securable): Seq[Permission]
36+
37+
/**
38+
* Modify the current permissions. The current principal must hold the correct permissions to
39+
* execute all of the given modifications. A [[SecurityException]] will be thrown if this
40+
* condition is not met, and none of the modifications will be executed.
41+
*/
42+
@throws[SecurityException]("permission denied")
43+
def modify(modifications: Seq[ModifyPermission]): Unit
44+
}
45+
46+
/**
47+
* Modify permission command.
48+
*/
49+
sealed trait ModifyPermission
50+
51+
/**
52+
* Grant a permission to a user.
53+
*
54+
* This user issuing the command must hold a GRANT permission for the given ActionType on the
55+
* given object.
56+
*/
57+
case class GrantPermission(permission: Permission) extends ModifyPermission {
58+
require(permission.validateGrant, s"$permission cannot be granted.")
59+
}
60+
61+
/**
62+
* Revoke a permission from a user.
63+
*/
64+
case class RevokePermission(permission: Permission) extends ModifyPermission {
65+
require(permission.validateGrant, s"$permission cannot be revoked.")}
66+
67+
/**
68+
* Remove all permissions for the given [[Securable]].
69+
*/
70+
case class DropSecurable(securable: Securable) extends ModifyPermission
71+
72+
/**
73+
* Rename a [[Securable]].
74+
*/
75+
case class Rename(before: Securable, after: Securable) extends ModifyPermission
76+
77+
/**
78+
* Create a new securable which will be owned by the current user.
79+
*/
80+
case class CreateSecurable(securable: Securable) extends ModifyPermission
81+
82+
/**
83+
* Change the owner of the [[Securable]]
84+
*/
85+
case class ChangeOwner(securable: Securable, next: Principal) extends ModifyPermission
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright © 2016 Databricks, Inc.
3+
*
4+
* Portions of this software incorporate or are derived from software contained within Apache Spark,
5+
* and this modified software differs from the Apache Spark software provided under the Apache
6+
* License, Version 2.0, a copy of which you may obtain at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*/
9+
package com.databricks.sql.acl
10+
11+
import com.databricks.sql.acl.AclCommandBaseParser._
12+
import org.antlr.v4.runtime._
13+
import org.antlr.v4.runtime.atn.PredictionMode
14+
import org.antlr.v4.runtime.misc.ParseCancellationException
15+
16+
import org.apache.spark.sql.AnalysisException
17+
import org.apache.spark.sql.catalyst.TableIdentifier
18+
import org.apache.spark.sql.catalyst.expressions.Expression
19+
import org.apache.spark.sql.catalyst.parser._
20+
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
21+
import org.apache.spark.sql.catalyst.trees.Origin
22+
import org.apache.spark.sql.types.DataType
23+
24+
/**
25+
* Parser for ACL related commands. The parser passes the query to an underlying (more complete)
26+
* parser if it cannot parse the query.
27+
*/
28+
class AclCommandParser(client: AclClient, delegate: ParserInterface) extends ParserInterface {
29+
30+
val builder = new AstCommandBuilder(client)
31+
32+
override def parseDataType(sqlText: String): DataType =
33+
delegate.parseDataType(sqlText)
34+
35+
override def parseExpression(sqlText: String): Expression =
36+
delegate.parseExpression(sqlText)
37+
38+
override def parseTableIdentifier(sqlText: String): TableIdentifier =
39+
delegate.parseTableIdentifier(sqlText)
40+
41+
/** Creates LogicalPlan for a given SQL string. */
42+
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) { parser =>
43+
builder.visit(parser.singleStatement()) match {
44+
case plan: LogicalPlan => plan
45+
case _ => delegate.parsePlan(sqlText)
46+
}
47+
}
48+
49+
protected def parse[T](command: String)(toResult: AclCommandBaseParser => T): T = {
50+
val lexer = new AclCommandBaseLexer(new ANTLRNoCaseStringStream(command))
51+
lexer.removeErrorListeners()
52+
lexer.addErrorListener(ParseErrorListener)
53+
54+
val tokenStream = new CommonTokenStream(lexer)
55+
val parser = new AclCommandBaseParser(tokenStream)
56+
parser.addParseListener(PostProcessor)
57+
parser.removeErrorListeners()
58+
parser.addErrorListener(ParseErrorListener)
59+
60+
try {
61+
try {
62+
// first, try parsing with potentially faster SLL mode
63+
parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
64+
toResult(parser)
65+
} catch {
66+
case e: ParseCancellationException =>
67+
// if we fail, parse with LL mode
68+
tokenStream.reset() // rewind input stream
69+
parser.reset()
70+
71+
// Try Again.
72+
parser.getInterpreter.setPredictionMode(PredictionMode.LL)
73+
toResult(parser)
74+
}
75+
} catch {
76+
case e: ParseException if e.command.isDefined =>
77+
throw e
78+
case e: ParseException =>
79+
throw e.withCommand(command)
80+
case e: AnalysisException =>
81+
val position = Origin(e.line, e.startPosition)
82+
throw new ParseException(Option(command), e.message, position, position)
83+
}
84+
}
85+
}
86+
87+
// TODO open this up in the existing parsing infrastructure.
88+
class ANTLRNoCaseStringStream(input: String) extends ANTLRInputStream(input) {
89+
override def LA(i: Int): Int = {
90+
val la = super.LA(i)
91+
if (la == 0 || la == IntStream.EOF) la
92+
else Character.toUpperCase(la)
93+
}
94+
}
95+
96+
/**
97+
* The post-processor validates & cleans-up the parse tree during the parse process.
98+
*/
99+
case object PostProcessor extends AclCommandBaseBaseListener {
100+
101+
/** Remove the back ticks from an Identifier. */
102+
override def exitQuotedIdentifier(ctx: QuotedIdentifierContext): Unit = {
103+
replaceTokenByIdentifier(ctx, 1) { token =>
104+
// Remove the double back ticks in the string.
105+
token.setText(token.getText.replace("``", "`"))
106+
token
107+
}
108+
}
109+
110+
/** Treat non-reserved keywords as Identifiers. */
111+
override def exitNonReserved(ctx: NonReservedContext): Unit = {
112+
replaceTokenByIdentifier(ctx, 0)(identity)
113+
}
114+
115+
private def replaceTokenByIdentifier(
116+
ctx: ParserRuleContext,
117+
stripMargins: Int)(
118+
f: CommonToken => CommonToken = identity): Unit = {
119+
val parent = ctx.getParent
120+
parent.removeLastChild()
121+
val token = ctx.getChild(0).getPayload.asInstanceOf[Token]
122+
parent.addChild(f(new CommonToken(
123+
new org.antlr.v4.runtime.misc.Pair(token.getTokenSource, token.getInputStream),
124+
AclCommandBaseParser.IDENTIFIER,
125+
token.getChannel,
126+
token.getStartIndex + stripMargins,
127+
token.getStopIndex - stripMargins)))
128+
}
129+
}

0 commit comments

Comments
 (0)