Add basic support for GRANT and REVOKE (#365)

* Add basic support for GRANT privileges on tables and sequences

* Cargo fmt

* Make enum for granted privileges

* Implement and use Display for GrantObjects

* Simplify Display for GrantPrivileges

* Add column privileges

* Add second column privilege to test

* Add REVOKE privileges and reformat

* Fix some clippy warnings

* Fix more clippy

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
Ben Cook 2021-12-08 12:55:23 -08:00 committed by GitHub
parent d33bacf679
commit d7e84be3e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 528 additions and 0 deletions

View file

@ -763,6 +763,22 @@ pub enum Statement {
condition: Expr,
message: Option<Expr>,
},
/// GRANT privileges ON objects TO grantees
Grant {
privileges: Privileges,
objects: GrantObjects,
grantees: Vec<Ident>,
with_grant_option: bool,
granted_by: Option<Ident>,
},
/// REVOKE privileges ON objects FROM grantees
Revoke {
privileges: Privileges,
objects: GrantObjects,
grantees: Vec<Ident>,
granted_by: Option<Ident>,
cascade: bool,
},
/// `DEALLOCATE [ PREPARE ] { name | ALL }`
///
/// Note: this is a PostgreSQL-specific statement.
@ -1330,6 +1346,40 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::Grant {
privileges,
objects,
grantees,
with_grant_option,
granted_by,
} => {
write!(f, "GRANT {} ", privileges)?;
write!(f, "ON {} ", objects)?;
write!(f, "TO {}", display_comma_separated(grantees))?;
if *with_grant_option {
write!(f, " WITH GRANT OPTION")?;
}
if let Some(grantor) = granted_by {
write!(f, " GRANTED BY {}", grantor)?;
}
Ok(())
}
Statement::Revoke {
privileges,
objects,
grantees,
granted_by,
cascade,
} => {
write!(f, "REVOKE {} ", privileges)?;
write!(f, "ON {} ", objects)?;
write!(f, "FROM {}", display_comma_separated(grantees))?;
if let Some(grantor) = granted_by {
write!(f, " GRANTED BY {}", grantor)?;
}
write!(f, " {}", if *cascade { "CASCADE" } else { "RESTRICT" })?;
Ok(())
}
Statement::Deallocate { name, prepare } => write!(
f,
"DEALLOCATE {prepare}{name}",
@ -1358,6 +1408,137 @@ impl fmt::Display for Statement {
}
}
/// Privileges granted in a GRANT statement or revoked in a REVOKE statement.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Privileges {
/// All privileges applicable to the object type
All {
/// Optional keyword from the spec, ignored in practice
with_privileges_keyword: bool,
},
/// Specific privileges (e.g. `SELECT`, `INSERT`)
Actions(Vec<Action>),
}
impl fmt::Display for Privileges {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Privileges::All {
with_privileges_keyword,
} => {
write!(
f,
"ALL{}",
if *with_privileges_keyword {
" PRIVILEGES"
} else {
""
}
)
}
Privileges::Actions(actions) => {
write!(f, "{}", display_comma_separated(actions).to_string())
}
}
}
}
/// A privilege on a database object (table, sequence, etc.).
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Action {
Connect,
Create,
Delete,
Execute,
Insert { columns: Option<Vec<Ident>> },
References { columns: Option<Vec<Ident>> },
Select { columns: Option<Vec<Ident>> },
Temporary,
Trigger,
Truncate,
Update { columns: Option<Vec<Ident>> },
Usage,
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Action::Connect => f.write_str("CONNECT")?,
Action::Create => f.write_str("CREATE")?,
Action::Delete => f.write_str("DELETE")?,
Action::Execute => f.write_str("EXECUTE")?,
Action::Insert { .. } => f.write_str("INSERT")?,
Action::References { .. } => f.write_str("REFERENCES")?,
Action::Select { .. } => f.write_str("SELECT")?,
Action::Temporary => f.write_str("TEMPORARY")?,
Action::Trigger => f.write_str("TRIGGER")?,
Action::Truncate => f.write_str("TRUNCATE")?,
Action::Update { .. } => f.write_str("UPDATE")?,
Action::Usage => f.write_str("USAGE")?,
};
match self {
Action::Insert { columns }
| Action::References { columns }
| Action::Select { columns }
| Action::Update { columns } => {
if let Some(columns) = columns {
write!(f, " ({})", display_comma_separated(columns))?;
}
}
_ => (),
};
Ok(())
}
}
/// Objects on which privileges are granted in a GRANT statement.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GrantObjects {
/// Grant privileges on `ALL SEQUENCES IN SCHEMA <schema_name> [, ...]`
AllSequencesInSchema { schemas: Vec<ObjectName> },
/// Grant privileges on `ALL TABLES IN SCHEMA <schema_name> [, ...]`
AllTablesInSchema { schemas: Vec<ObjectName> },
/// Grant privileges on specific schemas
Schemas(Vec<ObjectName>),
/// Grant privileges on specific sequences
Sequences(Vec<ObjectName>),
/// Grant privileges on specific tables
Tables(Vec<ObjectName>),
}
impl fmt::Display for GrantObjects {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GrantObjects::Sequences(sequences) => {
write!(f, "SEQUENCE {}", display_comma_separated(sequences))
}
GrantObjects::Schemas(schemas) => {
write!(f, "SCHEMA {}", display_comma_separated(schemas))
}
GrantObjects::Tables(tables) => {
write!(f, "{}", display_comma_separated(tables))
}
GrantObjects::AllSequencesInSchema { schemas } => {
write!(
f,
"ALL SEQUENCES IN SCHEMA {}",
display_comma_separated(schemas)
)
}
GrantObjects::AllTablesInSchema { schemas } => {
write!(
f,
"ALL TABLES IN SCHEMA {}",
display_comma_separated(schemas)
)
}
}
}
}
/// SQL assignment `foo = expr` as used in SQLUpdate
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View file

@ -225,6 +225,7 @@ define_keywords!(
GET,
GLOBAL,
GRANT,
GRANTED,
GROUP,
GROUPING,
GROUPS,
@ -318,6 +319,7 @@ define_keywords!(
ON,
ONLY,
OPEN,
OPTION,
OR,
ORC,
ORDER,
@ -348,6 +350,7 @@ define_keywords!(
PRECISION,
PREPARE,
PRIMARY,
PRIVILEGES,
PROCEDURE,
PURGE,
RANGE,
@ -395,7 +398,9 @@ define_keywords!(
SECOND,
SELECT,
SENSITIVE,
SEQUENCE,
SEQUENCEFILE,
SEQUENCES,
SERDE,
SERIALIZABLE,
SESSION,
@ -432,6 +437,7 @@ define_keywords!(
SYSTEM_TIME,
SYSTEM_USER,
TABLE,
TABLES,
TABLESAMPLE,
TBLPROPERTIES,
TEMP,
@ -468,6 +474,7 @@ define_keywords!(
UNNEST,
UPDATE,
UPPER,
USAGE,
USER,
USING,
UUID,

View file

@ -156,6 +156,8 @@ impl<'a> Parser<'a> {
Keyword::COPY => Ok(self.parse_copy()?),
Keyword::SET => Ok(self.parse_set()?),
Keyword::SHOW => Ok(self.parse_show()?),
Keyword::GRANT => Ok(self.parse_grant()?),
Keyword::REVOKE => Ok(self.parse_revoke()?),
Keyword::START => Ok(self.parse_start_transaction()?),
// `BEGIN` is a nonstandard but common alias for the
// standard `START TRANSACTION` statement. It is supported
@ -2882,6 +2884,148 @@ impl<'a> Parser<'a> {
}
}
/// Parse a GRANT statement.
pub fn parse_grant(&mut self) -> Result<Statement, ParserError> {
let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?;
self.expect_keyword(Keyword::TO)?;
let grantees = self.parse_comma_separated(Parser::parse_identifier)?;
let with_grant_option =
self.parse_keywords(&[Keyword::WITH, Keyword::GRANT, Keyword::OPTION]);
let granted_by = self
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
.then(|| self.parse_identifier().unwrap());
Ok(Statement::Grant {
privileges,
objects,
grantees,
with_grant_option,
granted_by,
})
}
fn parse_grant_revoke_privileges_objects(
&mut self,
) -> Result<(Privileges, GrantObjects), ParserError> {
let privileges = if self.parse_keyword(Keyword::ALL) {
Privileges::All {
with_privileges_keyword: self.parse_keyword(Keyword::PRIVILEGES),
}
} else {
Privileges::Actions(
self.parse_comma_separated(Parser::parse_grant_permission)?
.into_iter()
.map(|(kw, columns)| match kw {
Keyword::DELETE => Action::Delete,
Keyword::INSERT => Action::Insert { columns },
Keyword::REFERENCES => Action::References { columns },
Keyword::SELECT => Action::Select { columns },
Keyword::TRIGGER => Action::Trigger,
Keyword::TRUNCATE => Action::Truncate,
Keyword::UPDATE => Action::Update { columns },
Keyword::USAGE => Action::Usage,
_ => unreachable!(),
})
.collect(),
)
};
self.expect_keyword(Keyword::ON)?;
let objects = if self.parse_keywords(&[
Keyword::ALL,
Keyword::TABLES,
Keyword::IN,
Keyword::SCHEMA,
]) {
GrantObjects::AllTablesInSchema {
schemas: self.parse_comma_separated(Parser::parse_object_name)?,
}
} else if self.parse_keywords(&[
Keyword::ALL,
Keyword::SEQUENCES,
Keyword::IN,
Keyword::SCHEMA,
]) {
GrantObjects::AllSequencesInSchema {
schemas: self.parse_comma_separated(Parser::parse_object_name)?,
}
} else {
let object_type =
self.parse_one_of_keywords(&[Keyword::SEQUENCE, Keyword::SCHEMA, Keyword::TABLE]);
let objects = self.parse_comma_separated(Parser::parse_object_name);
match object_type {
Some(Keyword::SCHEMA) => GrantObjects::Schemas(objects?),
Some(Keyword::SEQUENCE) => GrantObjects::Sequences(objects?),
Some(Keyword::TABLE) | None => GrantObjects::Tables(objects?),
_ => unreachable!(),
}
};
Ok((privileges, objects))
}
fn parse_grant_permission(&mut self) -> Result<(Keyword, Option<Vec<Ident>>), ParserError> {
if let Some(kw) = self.parse_one_of_keywords(&[
Keyword::CONNECT,
Keyword::CREATE,
Keyword::DELETE,
Keyword::EXECUTE,
Keyword::INSERT,
Keyword::REFERENCES,
Keyword::SELECT,
Keyword::TEMPORARY,
Keyword::TRIGGER,
Keyword::TRUNCATE,
Keyword::UPDATE,
Keyword::USAGE,
]) {
let columns = match kw {
Keyword::INSERT | Keyword::REFERENCES | Keyword::SELECT | Keyword::UPDATE => {
let columns = self.parse_parenthesized_column_list(Optional)?;
if columns.is_empty() {
None
} else {
Some(columns)
}
}
_ => None,
};
Ok((kw, columns))
} else {
self.expected("a privilege keyword", self.peek_token())?
}
}
/// Parse a REVOKE statement
pub fn parse_revoke(&mut self) -> Result<Statement, ParserError> {
let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?;
self.expect_keyword(Keyword::FROM)?;
let grantees = self.parse_comma_separated(Parser::parse_identifier)?;
let granted_by = self
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
.then(|| self.parse_identifier().unwrap());
let cascade = self.parse_keyword(Keyword::CASCADE);
let restrict = self.parse_keyword(Keyword::RESTRICT);
if cascade && restrict {
return parser_err!("Cannot specify both CASCADE and RESTRICT in REVOKE");
}
Ok(Statement::Revoke {
privileges,
objects,
grantees,
granted_by,
cascade,
})
}
/// Parse an INSERT statement
pub fn parse_insert(&mut self) -> Result<Statement, ParserError> {
let or = if !dialect_of!(self is SQLiteDialect) {

View file

@ -3589,6 +3589,202 @@ fn parse_drop_index() {
}
}
#[test]
fn parse_grant() {
let sql = "GRANT SELECT, INSERT, UPDATE (shape, size), USAGE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON abc, def TO xyz, m WITH GRANT OPTION GRANTED BY jj";
match verified_stmt(sql) {
Statement::Grant {
privileges,
objects,
grantees,
with_grant_option,
granted_by,
..
} => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::Tables(objects)) => {
assert_eq!(
vec![
Action::Select { columns: None },
Action::Insert { columns: None },
Action::Update {
columns: Some(vec![
Ident {
value: "shape".into(),
quote_style: None
},
Ident {
value: "size".into(),
quote_style: None
}
])
},
Action::Usage,
Action::Delete,
Action::Truncate,
Action::References { columns: None },
Action::Trigger,
],
actions
);
assert_eq!(
vec!["abc", "def"],
objects.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert_eq!(
vec!["xyz", "m"],
grantees.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert!(with_grant_option);
assert_eq!("jj", granted_by.unwrap().to_string());
}
_ => unreachable!(),
},
_ => unreachable!(),
}
let sql2 = "GRANT INSERT ON ALL TABLES IN SCHEMA public TO browser";
match verified_stmt(sql2) {
Statement::Grant {
privileges,
objects,
grantees,
with_grant_option,
..
} => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::AllTablesInSchema { schemas }) => {
assert_eq!(vec![Action::Insert { columns: None }], actions);
assert_eq!(
vec!["public"],
schemas.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert_eq!(
vec!["browser"],
grantees.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert!(!with_grant_option);
}
_ => unreachable!(),
},
_ => unreachable!(),
}
let sql3 = "GRANT USAGE, SELECT ON SEQUENCE p TO u";
match verified_stmt(sql3) {
Statement::Grant {
privileges,
objects,
grantees,
granted_by,
..
} => match (privileges, objects, granted_by) {
(Privileges::Actions(actions), GrantObjects::Sequences(objects), None) => {
assert_eq!(
vec![Action::Usage, Action::Select { columns: None }],
actions
);
assert_eq!(
vec!["p"],
objects.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert_eq!(
vec!["u"],
grantees.iter().map(ToString::to_string).collect::<Vec<_>>()
);
}
_ => unreachable!(),
},
_ => unreachable!(),
}
let sql4 = "GRANT ALL PRIVILEGES ON aa, b TO z";
match verified_stmt(sql4) {
Statement::Grant { privileges, .. } => {
assert_eq!(
Privileges::All {
with_privileges_keyword: true
},
privileges
);
}
_ => unreachable!(),
}
let sql5 = "GRANT ALL ON SCHEMA aa, b TO z";
match verified_stmt(sql5) {
Statement::Grant {
privileges,
objects,
..
} => match (privileges, objects) {
(
Privileges::All {
with_privileges_keyword,
},
GrantObjects::Schemas(schemas),
) => {
assert!(!with_privileges_keyword);
assert_eq!(
vec!["aa", "b"],
schemas.iter().map(ToString::to_string).collect::<Vec<_>>()
);
}
_ => unreachable!(),
},
_ => unreachable!(),
}
let sql6 = "GRANT USAGE ON ALL SEQUENCES IN SCHEMA bus TO a, beta WITH GRANT OPTION";
match verified_stmt(sql6) {
Statement::Grant {
privileges,
objects,
..
} => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::AllSequencesInSchema { schemas }) => {
assert_eq!(vec![Action::Usage], actions);
assert_eq!(
vec!["bus"],
schemas.iter().map(ToString::to_string).collect::<Vec<_>>()
);
}
_ => unreachable!(),
},
_ => unreachable!(),
}
}
#[test]
fn test_revoke() {
let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst CASCADE";
match verified_stmt(sql) {
Statement::Revoke {
privileges,
objects: GrantObjects::Tables(tables),
grantees,
cascade,
granted_by,
} => {
assert_eq!(
Privileges::All {
with_privileges_keyword: true
},
privileges
);
assert_eq!(
vec!["users", "auth"],
tables.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert_eq!(
vec!["analyst"],
grantees.iter().map(ToString::to_string).collect::<Vec<_>>()
);
assert!(cascade);
assert_eq!(None, granted_by);
}
_ => unreachable!(),
}
}
#[test]
fn all_keywords_sorted() {
// assert!(ALL_KEYWORDS.is_sorted())