Skip to main content

reddb_server/storage/query/parser/
auth_ddl.rs

1//! Auth-related DDL parsers — `GRANT`, `REVOKE`, `ALTER USER`.
2//!
3//! These statements live alongside the rest of DDL but their AST nodes
4//! and downstream dispatch are in `crate::auth::privileges`. The
5//! parser is intentionally thin: every shape the user types maps
6//! directly onto the [`GrantStmt`] / [`RevokeStmt`] / [`AlterUserStmt`]
7//! AST so the runtime can apply the change in one match arm.
8//!
9//! Grammar (conservative — defers the long-tail PG modifiers):
10//! ```text
11//!   GRANT { privilege_list | ALL [PRIVILEGES] }
12//!         [ ( column_list ) ]
13//!         ON [ TABLE | SCHEMA | DATABASE | FUNCTION ] object_list
14//!         TO grant_principal_list
15//!         [ WITH GRANT OPTION ]
16//!
17//!   REVOKE [ GRANT OPTION FOR ] { privilege_list | ALL [PRIVILEGES] }
18//!         [ ( column_list ) ]
19//!         ON [ TABLE | SCHEMA | DATABASE | FUNCTION ] object_list
20//!         FROM grant_principal_list
21//!
22//!   ALTER USER name
23//!         [ VALID UNTIL 'timestamp' ]
24//!         [ CONNECTION LIMIT n ]
25//!         [ ENABLE | DISABLE ]
26//!         [ SET search_path = 'csv' ]
27//!         [ PASSWORD 'plaintext' ]
28//! ```
29//!
30//! `name` accepts `tenant.username` form so a platform admin can target
31//! a tenant-scoped account. `PUBLIC` is recognised as a reserved
32//! principal.
33
34use crate::storage::query::ast::{
35    AlterUserAttribute, AlterUserStmt, GrantObject, GrantObjectKind, GrantPrincipalRef, GrantStmt,
36    PolicyPrincipalRef, PolicyResourceRef, PolicyUserRef, QueryExpr, RevokeStmt,
37};
38use crate::storage::query::lexer::Token;
39use crate::storage::query::parser::{ParseError, Parser};
40
41impl<'a> Parser<'a> {
42    /// Parse a `GRANT` statement. Caller must have already verified the
43    /// current token is the `GRANT` ident (it is not a lexer keyword —
44    /// the lexer maps it to `Token::Ident("GRANT")`).
45    pub fn parse_grant_statement(&mut self) -> Result<GrantStmt, ParseError> {
46        // Eat `GRANT`.
47        self.advance()?;
48
49        let (actions, all, columns) = self.parse_privilege_list()?;
50        self.expect(Token::On)?;
51        let object_kind = self.parse_grant_object_kind()?;
52        let objects = self.parse_grant_object_list(&object_kind)?;
53        self.expect(Token::To)?;
54        let principals = self.parse_grant_principal_list()?;
55
56        let with_grant_option = self.consume_grant_option_suffix()?;
57
58        Ok(GrantStmt {
59            actions,
60            columns,
61            object_kind,
62            objects,
63            principals,
64            with_grant_option,
65            all,
66        })
67    }
68
69    /// Parse a `REVOKE` statement. Caller must have already verified the
70    /// current token is the `REVOKE` ident.
71    pub fn parse_revoke_statement(&mut self) -> Result<RevokeStmt, ParseError> {
72        // Eat `REVOKE`.
73        self.advance()?;
74
75        // Optional `GRANT OPTION FOR`.
76        let grant_option_for = self.consume_grant_option_for_prefix()?;
77
78        let (actions, all, columns) = self.parse_privilege_list()?;
79        self.expect(Token::On)?;
80        let object_kind = self.parse_grant_object_kind()?;
81        let objects = self.parse_grant_object_list(&object_kind)?;
82        self.expect(Token::From)?;
83        let principals = self.parse_grant_principal_list()?;
84
85        Ok(RevokeStmt {
86            actions,
87            columns,
88            object_kind,
89            objects,
90            principals,
91            grant_option_for,
92            all,
93        })
94    }
95
96    /// Parse `ALTER USER name <attrs>`. Caller has just consumed
97    /// `Token::Alter`.
98    pub fn parse_alter_user_statement(&mut self) -> Result<AlterUserStmt, ParseError> {
99        // `ALTER` was already consumed by the dispatcher; expect USER ident.
100        if !self.consume_ident_ci("USER")? {
101            return Err(ParseError::expected(
102                vec!["USER"],
103                self.peek(),
104                self.position(),
105            ));
106        }
107        let (tenant, username) = self.parse_user_name()?;
108
109        let mut attributes = Vec::new();
110        loop {
111            if self.consume_ident_ci("VALID")? {
112                if !self.consume_ident_ci("UNTIL")? {
113                    return Err(ParseError::expected(
114                        vec!["UNTIL"],
115                        self.peek(),
116                        self.position(),
117                    ));
118                }
119                let ts = self.parse_string()?;
120                attributes.push(AlterUserAttribute::ValidUntil(ts));
121            } else if self.consume_ident_ci("CONNECTION")? {
122                if !self.consume(&Token::Limit)? && !self.consume_ident_ci("LIMIT")? {
123                    return Err(ParseError::expected(
124                        vec!["LIMIT"],
125                        self.peek(),
126                        self.position(),
127                    ));
128                }
129                let n = self.parse_integer()?;
130                attributes.push(AlterUserAttribute::ConnectionLimit(n));
131            } else if self.consume(&Token::Enable)? {
132                attributes.push(AlterUserAttribute::Enable);
133            } else if self.consume(&Token::Disable)? {
134                attributes.push(AlterUserAttribute::Disable);
135            } else if self.consume(&Token::Set)? {
136                // SET search_path = 'csv'  |  SET search_path TO 'csv'
137                if !self.consume_ident_ci("SEARCH_PATH")? {
138                    return Err(ParseError::expected(
139                        vec!["search_path"],
140                        self.peek(),
141                        self.position(),
142                    ));
143                }
144                if !self.consume(&Token::Eq)? && !self.consume(&Token::To)? {
145                    return Err(ParseError::expected(
146                        vec!["="],
147                        self.peek(),
148                        self.position(),
149                    ));
150                }
151                let value = self.parse_string()?;
152                attributes.push(AlterUserAttribute::SetSearchPath(value));
153            } else if self.consume(&Token::Add)? || self.consume_ident_ci("ADD")? {
154                if !self.consume(&Token::Group)? && !self.consume_ident_ci("GROUP")? {
155                    return Err(ParseError::expected(
156                        vec!["GROUP"],
157                        self.peek(),
158                        self.position(),
159                    ));
160                }
161                let group = self.expect_ident()?;
162                attributes.push(AlterUserAttribute::AddGroup(group));
163            } else if self.consume(&Token::Drop)? || self.consume_ident_ci("DROP")? {
164                if !self.consume(&Token::Group)? && !self.consume_ident_ci("GROUP")? {
165                    return Err(ParseError::expected(
166                        vec!["GROUP"],
167                        self.peek(),
168                        self.position(),
169                    ));
170                }
171                let group = self.expect_ident()?;
172                attributes.push(AlterUserAttribute::DropGroup(group));
173            } else if self.consume_ident_ci("PASSWORD")? {
174                let pw = self.parse_string()?;
175                attributes.push(AlterUserAttribute::Password(pw));
176            } else {
177                break;
178            }
179        }
180
181        if attributes.is_empty() {
182            return Err(ParseError::expected(
183                vec![
184                    "VALID",
185                    "CONNECTION",
186                    "ENABLE",
187                    "DISABLE",
188                    "SET",
189                    "ADD",
190                    "DROP",
191                    "PASSWORD",
192                ],
193                self.peek(),
194                self.position(),
195            ));
196        }
197
198        Ok(AlterUserStmt {
199            tenant,
200            username,
201            attributes,
202        })
203    }
204
205    // -----------------------------------------------------------------
206    // IAM policy DDL — CREATE / DROP / ATTACH / DETACH / SHOW / SIMULATE
207    // -----------------------------------------------------------------
208
209    /// Parse `CREATE POLICY '<id>' AS '<json>'`. Caller has consumed
210    /// `CREATE POLICY` already and confirmed the next token is a
211    /// string literal (the IAM-flavoured form). Returns the
212    /// `QueryExpr::CreateIamPolicy` variant.
213    pub fn parse_create_iam_policy_after_keywords(&mut self) -> Result<QueryExpr, ParseError> {
214        let id = self.parse_string()?;
215        if !self.consume(&Token::As)? && !self.consume_ident_ci("AS")? {
216            return Err(ParseError::expected(
217                vec!["AS"],
218                self.peek(),
219                self.position(),
220            ));
221        }
222        let json = self.parse_string()?;
223        Ok(QueryExpr::CreateIamPolicy { id, json })
224    }
225
226    /// Parse `DROP POLICY '<id>'`. Caller has consumed `DROP POLICY`
227    /// and verified the next token is a string literal.
228    pub fn parse_drop_iam_policy_after_keywords(&mut self) -> Result<QueryExpr, ParseError> {
229        let id = self.parse_string()?;
230        Ok(QueryExpr::DropIamPolicy { id })
231    }
232
233    /// Parse `ATTACH POLICY '<id>' TO { USER | GROUP } <name>`.
234    /// Caller has consumed nothing — leading `ATTACH` is still on
235    /// the token stream.
236    pub fn parse_attach_policy(&mut self) -> Result<QueryExpr, ParseError> {
237        self.advance()?; // ATTACH
238        if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
239            return Err(ParseError::expected(
240                vec!["POLICY"],
241                self.peek(),
242                self.position(),
243            ));
244        }
245        let policy_id = self.parse_string()?;
246        self.expect(Token::To)?;
247        let principal = self.parse_iam_principal_kind()?;
248        Ok(QueryExpr::AttachPolicy {
249            policy_id,
250            principal,
251        })
252    }
253
254    /// Parse `DETACH POLICY '<id>' FROM { USER | GROUP } <name>`.
255    pub fn parse_detach_policy(&mut self) -> Result<QueryExpr, ParseError> {
256        self.advance()?; // DETACH
257        if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
258            return Err(ParseError::expected(
259                vec!["POLICY"],
260                self.peek(),
261                self.position(),
262            ));
263        }
264        let policy_id = self.parse_string()?;
265        self.expect(Token::From)?;
266        let principal = self.parse_iam_principal_kind()?;
267        Ok(QueryExpr::DetachPolicy {
268            policy_id,
269            principal,
270        })
271    }
272
273    /// Parse `SIMULATE <name> ACTION <verb> ON <kind>:<name>`.
274    pub fn parse_simulate_policy(&mut self) -> Result<QueryExpr, ParseError> {
275        self.advance()?; // ident "SIMULATE"
276        let user = self.parse_iam_user_ref()?;
277        if !self.consume_ident_ci("ACTION")? {
278            return Err(ParseError::expected(
279                vec!["ACTION"],
280                self.peek(),
281                self.position(),
282            ));
283        }
284        let action = self.parse_iam_action_token()?;
285        self.expect(Token::On)?;
286        let resource = self.parse_iam_resource_ref()?;
287        Ok(QueryExpr::SimulatePolicy {
288            user,
289            action,
290            resource,
291        })
292    }
293
294    /// Parse `SHOW POLICIES [FOR USER <name> | FOR GROUP <name>]` or
295    /// `SHOW EFFECTIVE PERMISSIONS FOR <name> [ON <kind>:<name>]`.
296    /// Caller has just consumed `SHOW`.
297    pub fn parse_show_iam_after_show(&mut self) -> Result<Option<QueryExpr>, ParseError> {
298        // Disambiguate: SHOW POLICIES vs SHOW EFFECTIVE
299        if self.consume_ident_ci("POLICIES")? {
300            // Optional FOR USER / FOR GROUP
301            if self.consume(&Token::For)? || self.consume_ident_ci("FOR")? {
302                let principal = self.parse_iam_principal_kind()?;
303                return Ok(Some(QueryExpr::ShowPolicies {
304                    filter: Some(principal),
305                }));
306            }
307            return Ok(Some(QueryExpr::ShowPolicies { filter: None }));
308        }
309        if self.consume_ident_ci("EFFECTIVE")? {
310            if !self.consume_ident_ci("PERMISSIONS")? {
311                return Err(ParseError::expected(
312                    vec!["PERMISSIONS"],
313                    self.peek(),
314                    self.position(),
315                ));
316            }
317            if !self.consume(&Token::For)? && !self.consume_ident_ci("FOR")? {
318                return Err(ParseError::expected(
319                    vec!["FOR"],
320                    self.peek(),
321                    self.position(),
322                ));
323            }
324            let user = self.parse_iam_user_ref()?;
325            let resource = if self.consume(&Token::On)? || self.consume_ident_ci("ON")? {
326                Some(self.parse_iam_resource_ref()?)
327            } else {
328                None
329            };
330            return Ok(Some(QueryExpr::ShowEffectivePermissions { user, resource }));
331        }
332        Ok(None)
333    }
334
335    // ----- helpers used by the IAM policy parsers -----
336
337    pub(crate) fn parse_iam_principal_kind(&mut self) -> Result<PolicyPrincipalRef, ParseError> {
338        if self.consume_ident_ci("USER")? {
339            let user = self.parse_iam_user_ref()?;
340            Ok(PolicyPrincipalRef::User(user))
341        } else if self.consume(&Token::Group)? || self.consume_ident_ci("GROUP")? {
342            let g = self.expect_ident()?;
343            Ok(PolicyPrincipalRef::Group(g))
344        } else {
345            Err(ParseError::expected(
346                vec!["USER", "GROUP"],
347                self.peek(),
348                self.position(),
349            ))
350        }
351    }
352
353    fn parse_iam_user_ref(&mut self) -> Result<PolicyUserRef, ParseError> {
354        let (tenant, username) = self.parse_user_name()?;
355        Ok(PolicyUserRef { tenant, username })
356    }
357
358    fn parse_iam_resource_ref(&mut self) -> Result<PolicyResourceRef, ParseError> {
359        // Two accepted forms:
360        //   * `<kind>:<name>` as one string literal
361        //   * `<kind>:<dotted_name>` as `kind ':' part ('.' part)*`
362        if matches!(self.peek(), Token::String(_)) {
363            let raw = self.parse_string()?;
364            let (kind, name) = raw.split_once(':').ok_or_else(|| {
365                ParseError::new(
366                    // F-05: `raw` is caller-controlled string-literal bytes.
367                    // Render via `{:?}` so CR/LF/NUL/quotes are escaped
368                    // before the message reaches the downstream JSON /
369                    // audit / log / gRPC sinks.
370                    format!("resource must be `kind:name`, got {raw:?}"),
371                    self.position(),
372                )
373            })?;
374            return Ok(PolicyResourceRef {
375                kind: kind.to_string(),
376                name: name.to_string(),
377            });
378        }
379        // Normalise both halves to lowercase so the kernel's allowlist
380        // (`table`, `function`, …) lines up regardless of how the SQL
381        // tokens were cased / promoted by the lexer.
382        let kind = self.expect_ident_or_keyword()?.to_ascii_lowercase();
383        if !self.consume(&Token::Colon)? {
384            return Err(ParseError::expected(
385                vec![":"],
386                self.peek(),
387                self.position(),
388            ));
389        }
390        // Accept dotted resource names — `public.orders` arrives as
391        // `Ident("public")`, `Dot`, `Ident("orders")` from the lexer.
392        let mut name = self.expect_ident_or_keyword()?;
393        while self.consume(&Token::Dot)? {
394            let next = self.expect_ident_or_keyword()?;
395            name.push('.');
396            name.push_str(&next);
397        }
398        Ok(PolicyResourceRef { kind, name })
399    }
400
401    fn parse_iam_action_token(&mut self) -> Result<String, ParseError> {
402        if matches!(self.peek(), Token::String(_)) {
403            return self.parse_string();
404        }
405        // SELECT / INSERT / UPDATE / DELETE are real tokens; everything
406        // else is exposed as an `Ident` by the lexer.
407        match self.peek() {
408            Token::Select => {
409                self.advance()?;
410                Ok("select".into())
411            }
412            Token::Insert => {
413                self.advance()?;
414                Ok("insert".into())
415            }
416            Token::Update => {
417                self.advance()?;
418                Ok("update".into())
419            }
420            Token::Delete => {
421                self.advance()?;
422                Ok("delete".into())
423            }
424            Token::Ident(_) => {
425                let raw = self.expect_ident()?;
426                Ok(raw.to_ascii_lowercase())
427            }
428            other => Err(ParseError::expected(
429                vec!["action keyword"],
430                other,
431                self.position(),
432            )),
433        }
434    }
435
436    // -----------------------------------------------------------------
437    // Helpers
438    // -----------------------------------------------------------------
439
440    /// Parse a comma-separated privilege list (`SELECT, INSERT, ...`)
441    /// or `ALL [PRIVILEGES]`. Returns `(actions, is_all, columns?)`.
442    /// Column-level lists are accepted at parse time but enforcement is
443    /// deferred — see `auth::privileges` module docstring.
444    fn parse_privilege_list(
445        &mut self,
446    ) -> Result<(Vec<String>, bool, Option<Vec<String>>), ParseError> {
447        // ALL [PRIVILEGES]
448        if self.consume(&Token::All)? || self.consume_ident_ci("ALL")? {
449            let _ = self.consume_ident_ci("PRIVILEGES")?;
450            let columns = self.parse_optional_column_list()?;
451            return Ok((vec!["ALL".to_string()], true, columns));
452        }
453
454        // Privilege list.
455        let mut actions = Vec::new();
456        loop {
457            actions.push(self.parse_privilege_keyword()?);
458            if !self.consume(&Token::Comma)? {
459                break;
460            }
461        }
462        let columns = self.parse_optional_column_list()?;
463        Ok((actions, false, columns))
464    }
465
466    /// Recognise SELECT / INSERT / UPDATE / DELETE / TRUNCATE /
467    /// REFERENCES / EXECUTE / USAGE. SELECT/INSERT/UPDATE/DELETE are
468    /// real tokens; the rest are idents.
469    fn parse_privilege_keyword(&mut self) -> Result<String, ParseError> {
470        match self.peek() {
471            Token::Select => {
472                self.advance()?;
473                Ok("SELECT".to_string())
474            }
475            Token::Insert => {
476                self.advance()?;
477                Ok("INSERT".to_string())
478            }
479            Token::Update => {
480                self.advance()?;
481                Ok("UPDATE".to_string())
482            }
483            Token::Delete => {
484                self.advance()?;
485                Ok("DELETE".to_string())
486            }
487            Token::Truncate => {
488                self.advance()?;
489                Ok("TRUNCATE".to_string())
490            }
491            Token::Ident(name)
492                if matches!(
493                    name.to_ascii_uppercase().as_str(),
494                    "REFERENCES" | "EXECUTE" | "USAGE"
495                ) =>
496            {
497                let upper = name.to_ascii_uppercase();
498                self.advance()?;
499                Ok(upper)
500            }
501            other => Err(ParseError::expected(
502                vec![
503                    "SELECT",
504                    "INSERT",
505                    "UPDATE",
506                    "DELETE",
507                    "TRUNCATE",
508                    "REFERENCES",
509                    "EXECUTE",
510                    "USAGE",
511                ],
512                other,
513                self.position(),
514            )),
515        }
516    }
517
518    /// Optional `( col1, col2, ... )` after a privilege list. Returns
519    /// `None` when the next token isn't `(`.
520    fn parse_optional_column_list(&mut self) -> Result<Option<Vec<String>>, ParseError> {
521        if !self.check(&Token::LParen) {
522            return Ok(None);
523        }
524        self.expect(Token::LParen)?;
525        let mut cols = Vec::new();
526        loop {
527            cols.push(self.expect_ident()?);
528            if !self.consume(&Token::Comma)? {
529                break;
530            }
531        }
532        self.expect(Token::RParen)?;
533        Ok(Some(cols))
534    }
535
536    /// Parse the optional `[ TABLE | SCHEMA | DATABASE | FUNCTION ]`
537    /// keyword between `ON` and the object list. Defaults to `TABLE`
538    /// when absent (matches PG).
539    fn parse_grant_object_kind(&mut self) -> Result<GrantObjectKind, ParseError> {
540        if self.consume(&Token::Table)? {
541            Ok(GrantObjectKind::Table)
542        } else if self.consume(&Token::Schema)? {
543            Ok(GrantObjectKind::Schema)
544        } else if self.consume_ident_ci("DATABASE")? {
545            Ok(GrantObjectKind::Database)
546        } else if self.consume_ident_ci("FUNCTION")? {
547            Ok(GrantObjectKind::Function)
548        } else {
549            // Default: TABLE
550            Ok(GrantObjectKind::Table)
551        }
552    }
553
554    /// Parse a comma-separated list of `[schema.]name` objects.
555    fn parse_grant_object_list(
556        &mut self,
557        kind: &GrantObjectKind,
558    ) -> Result<Vec<GrantObject>, ParseError> {
559        let mut out = Vec::new();
560        loop {
561            // DATABASE objects use the database name as the object —
562            // accept a single ident.
563            if matches!(kind, GrantObjectKind::Database) {
564                let name = self.expect_ident()?;
565                out.push(GrantObject { schema: None, name });
566            } else {
567                let first = self.expect_ident()?;
568                let (schema, name) = if self.consume(&Token::Dot)? {
569                    let second = self.expect_ident_or_keyword()?;
570                    (Some(first), second)
571                } else {
572                    (None, first)
573                };
574                out.push(GrantObject { schema, name });
575            }
576            if !self.consume(&Token::Comma)? {
577                break;
578            }
579        }
580        Ok(out)
581    }
582
583    /// Parse a comma-separated principal list. Each principal is one of:
584    ///   * `PUBLIC` — every authenticated user.
585    ///   * `GROUP groupname` — role-as-group (parsed, not enforced).
586    ///   * `username` or `tenant.username` — a specific user.
587    fn parse_grant_principal_list(&mut self) -> Result<Vec<GrantPrincipalRef>, ParseError> {
588        let mut out = Vec::new();
589        loop {
590            if self.consume_ident_ci("PUBLIC")? {
591                out.push(GrantPrincipalRef::Public);
592            } else if self.consume(&Token::Group)? || self.consume_ident_ci("GROUP")? {
593                let g = self.expect_ident()?;
594                out.push(GrantPrincipalRef::Group(g));
595            } else {
596                let (tenant, name) = self.parse_user_name()?;
597                out.push(GrantPrincipalRef::User { tenant, name });
598            }
599            if !self.consume(&Token::Comma)? {
600                break;
601            }
602        }
603        Ok(out)
604    }
605
606    /// Parse a `user` or `tenant.user` form. Returns `(tenant, name)`.
607    fn parse_user_name(&mut self) -> Result<(Option<String>, String), ParseError> {
608        let first = self.expect_ident()?;
609        if self.consume(&Token::Dot)? {
610            let name = self.expect_ident()?;
611            Ok((Some(first), name))
612        } else {
613            Ok((None, first))
614        }
615    }
616
617    /// Recognise the optional `WITH GRANT OPTION` suffix on a GRANT.
618    fn consume_grant_option_suffix(&mut self) -> Result<bool, ParseError> {
619        if self.consume(&Token::With)? {
620            if !self.consume_ident_ci("GRANT")? {
621                return Err(ParseError::expected(
622                    vec!["GRANT"],
623                    self.peek(),
624                    self.position(),
625                ));
626            }
627            if !self.consume_ident_ci("OPTION")? {
628                return Err(ParseError::expected(
629                    vec!["OPTION"],
630                    self.peek(),
631                    self.position(),
632                ));
633            }
634            Ok(true)
635        } else {
636            Ok(false)
637        }
638    }
639
640    /// Recognise the optional `GRANT OPTION FOR` prefix on a REVOKE.
641    fn consume_grant_option_for_prefix(&mut self) -> Result<bool, ParseError> {
642        // `GRANT` is an ident, not a keyword — we must peek the ident
643        // text without consuming until we know the full prefix matches.
644        let saved_pos = self.position();
645        if !matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("GRANT")) {
646            return Ok(false);
647        }
648        // Consume GRANT.
649        self.advance()?;
650        if !self.consume_ident_ci("OPTION")? {
651            // Not the prefix we expected — but `REVOKE GRANT ...`
652            // makes no other sense, so this is a parse error rather
653            // than a non-match.
654            return Err(ParseError::expected(vec!["OPTION"], self.peek(), saved_pos));
655        }
656        if !self.consume(&Token::For)? && !self.consume_ident_ci("FOR")? {
657            return Err(ParseError::expected(vec!["FOR"], self.peek(), saved_pos));
658        }
659        Ok(true)
660    }
661}