surrealdb_core/sql/statements/
access.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::iam::{Action, ResourceKind};
6use crate::sql::access_type::BearerAccessSubject;
7use crate::sql::{
8	AccessType, Array, Base, Cond, Datetime, Duration, Ident, Object, Strand, Thing, Uuid, Value,
9};
10use md5::Digest;
11use rand::Rng;
12use reblessive::tree::Stk;
13use revision::revisioned;
14use serde::{Deserialize, Serialize};
15use sha2::Sha256;
16use std::fmt;
17use std::fmt::{Display, Formatter};
18
19// Keys and their identifiers are generated randomly from a 62-character pool.
20pub static GRANT_BEARER_CHARACTER_POOL: &[u8] =
21	b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
22// The key identifier should not have collisions to prevent confusion.
23// However, collisions should be handled gracefully when issuing grants.
24// The first character of the key identifier will not be a digit to prevent parsing issues.
25// With 12 characters from the pool, one alphabetic, the key identifier part has ~68 bits of entropy.
26pub static GRANT_BEARER_ID_LENGTH: usize = 12;
27// With 24 characters from the pool, the key part has ~140 bits of entropy.
28pub static GRANT_BEARER_KEY_LENGTH: usize = 24;
29
30#[revisioned(revision = 1)]
31#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
32#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
33#[non_exhaustive]
34pub enum AccessStatement {
35	Grant(AccessStatementGrant),   // Create access grant.
36	Show(AccessStatementShow),     // Show access grants.
37	Revoke(AccessStatementRevoke), // Revoke access grant.
38	Purge(AccessStatementPurge),   // Purge access grants.
39}
40
41#[revisioned(revision = 1)]
42#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
43#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
44#[non_exhaustive]
45pub struct AccessStatementGrant {
46	pub ac: Ident,
47	pub base: Option<Base>,
48	pub subject: Subject,
49}
50
51#[revisioned(revision = 1)]
52#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
53#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
54#[non_exhaustive]
55pub struct AccessStatementShow {
56	pub ac: Ident,
57	pub base: Option<Base>,
58	pub gr: Option<Ident>,
59	pub cond: Option<Cond>,
60}
61
62#[revisioned(revision = 1)]
63#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
64#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
65#[non_exhaustive]
66pub struct AccessStatementRevoke {
67	pub ac: Ident,
68	pub base: Option<Base>,
69	pub gr: Option<Ident>,
70	pub cond: Option<Cond>,
71}
72
73#[revisioned(revision = 1)]
74#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
75#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
76#[non_exhaustive]
77pub struct AccessStatementPurge {
78	pub ac: Ident,
79	pub base: Option<Base>,
80	pub expired: bool,
81	pub revoked: bool,
82	pub grace: Duration,
83}
84
85#[revisioned(revision = 1)]
86#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
87#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
88#[non_exhaustive]
89pub struct AccessGrant {
90	pub id: Ident,                    // Unique grant identifier.
91	pub ac: Ident,                    // Access method used to create the grant.
92	pub creation: Datetime,           // Grant creation time.
93	pub expiration: Option<Datetime>, // Grant expiration time, if any.
94	pub revocation: Option<Datetime>, // Grant revocation time, if any.
95	pub subject: Subject,             // Subject of the grant.
96	pub grant: Grant,                 // Grant data.
97}
98
99impl AccessGrant {
100	/// Returns a version of the statement where potential secrets are redacted.
101	/// This function should be used when displaying the statement to datastore users.
102	/// This function should NOT be used when displaying the statement for export purposes.
103	pub fn redacted(&self) -> AccessGrant {
104		let mut ags = self.clone();
105		ags.grant = match ags.grant {
106			Grant::Jwt(mut gr) => {
107				// Token should not even be stored. We clear it just as a precaution.
108				gr.token = None;
109				Grant::Jwt(gr)
110			}
111			Grant::Record(mut gr) => {
112				// Token should not even be stored. We clear it just as a precaution.
113				gr.token = None;
114				Grant::Record(gr)
115			}
116			Grant::Bearer(mut gr) => {
117				// Key is stored, but should not usually be displayed.
118				gr.key = "[REDACTED]".into();
119				Grant::Bearer(gr)
120			}
121		};
122		ags
123	}
124
125	// Returns if the access grant is expired.
126	pub fn is_expired(&self) -> bool {
127		match &self.expiration {
128			Some(exp) => exp < &Datetime::default(),
129			None => false,
130		}
131	}
132
133	// Returns if the access grant is revoked.
134	pub fn is_revoked(&self) -> bool {
135		self.revocation.is_some()
136	}
137
138	// Returns if the access grant is active.
139	pub fn is_active(&self) -> bool {
140		!(self.is_expired() || self.is_revoked())
141	}
142}
143
144impl From<AccessGrant> for Object {
145	fn from(grant: AccessGrant) -> Self {
146		let mut res = Object::default();
147		res.insert("id".to_owned(), Value::from(grant.id.to_raw()));
148		res.insert("ac".to_owned(), Value::from(grant.ac.to_raw()));
149		res.insert("type".to_owned(), Value::from(grant.grant.variant()));
150		res.insert("creation".to_owned(), Value::from(grant.creation));
151		res.insert("expiration".to_owned(), Value::from(grant.expiration));
152		res.insert("revocation".to_owned(), Value::from(grant.revocation));
153		let mut sub = Object::default();
154		match grant.subject {
155			Subject::Record(id) => sub.insert("record".to_owned(), Value::from(id)),
156			Subject::User(name) => sub.insert("user".to_owned(), Value::from(name.to_raw())),
157		};
158		res.insert("subject".to_owned(), Value::from(sub));
159
160		let mut gr = Object::default();
161		match grant.grant {
162			Grant::Jwt(jg) => {
163				gr.insert("jti".to_owned(), Value::from(jg.jti));
164				if let Some(token) = jg.token {
165					gr.insert("token".to_owned(), Value::from(token));
166				}
167			}
168			Grant::Record(rg) => {
169				gr.insert("rid".to_owned(), Value::from(rg.rid));
170				gr.insert("jti".to_owned(), Value::from(rg.jti));
171				if let Some(token) = rg.token {
172					gr.insert("token".to_owned(), Value::from(token));
173				}
174			}
175			Grant::Bearer(bg) => {
176				gr.insert("id".to_owned(), Value::from(bg.id.to_raw()));
177				gr.insert("key".to_owned(), Value::from(bg.key));
178			}
179		};
180		res.insert("grant".to_owned(), Value::from(gr));
181
182		res
183	}
184}
185
186#[revisioned(revision = 1)]
187#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
188#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
189#[non_exhaustive]
190pub enum Subject {
191	Record(Thing),
192	User(Ident),
193}
194
195impl Subject {
196	// Returns the main identifier of a subject as a string.
197	pub fn id(&self) -> String {
198		match self {
199			Subject::Record(id) => id.to_raw(),
200			Subject::User(name) => name.to_raw(),
201		}
202	}
203}
204
205#[revisioned(revision = 1)]
206#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
207#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
208#[non_exhaustive]
209pub enum Grant {
210	Jwt(GrantJwt),
211	Record(GrantRecord),
212	Bearer(GrantBearer),
213}
214
215impl Grant {
216	// Returns the type of the grant as a string.
217	pub fn variant(&self) -> &str {
218		match self {
219			Grant::Jwt(_) => "jwt",
220			Grant::Record(_) => "record",
221			Grant::Bearer(_) => "bearer",
222		}
223	}
224}
225
226#[revisioned(revision = 1)]
227#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
228#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
229#[non_exhaustive]
230pub struct GrantJwt {
231	pub jti: Uuid,             // JWT ID
232	pub token: Option<Strand>, // JWT. Will not be stored after being returned.
233}
234
235#[revisioned(revision = 1)]
236#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
237#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
238#[non_exhaustive]
239pub struct GrantRecord {
240	pub rid: Uuid,             // Record ID
241	pub jti: Uuid,             // JWT ID
242	pub token: Option<Strand>, // JWT. Will not be stored after being returned.
243}
244
245#[revisioned(revision = 1)]
246#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
247#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
248#[non_exhaustive]
249pub struct GrantBearer {
250	pub id: Ident, // Key ID
251	// Key. Will not be stored and be returned as redacted.
252	// Immediately after generation, it will contain the plaintext key.
253	// Will be hashed before storage so that the plaintext key is not stored.
254	pub key: Strand,
255}
256
257impl GrantBearer {
258	#[allow(clippy::new_without_default)]
259	pub fn new(prefix: &str) -> Self {
260		let id = format!(
261			"{}{}",
262			// The pool for the first character of the key identifier excludes digits.
263			random_string(1, &GRANT_BEARER_CHARACTER_POOL[10..]),
264			random_string(GRANT_BEARER_ID_LENGTH - 1, GRANT_BEARER_CHARACTER_POOL)
265		);
266		let secret = random_string(GRANT_BEARER_KEY_LENGTH, GRANT_BEARER_CHARACTER_POOL);
267		Self {
268			id: id.clone().into(),
269			key: format!("{prefix}-{id}-{secret}").into(),
270		}
271	}
272
273	pub fn hashed(self) -> Self {
274		// The hash of the bearer key is stored to mitigate the impact of a read-only compromise.
275		// We use SHA-256 as the key needs to be verified performantly for every operation.
276		// Unlike with passwords, brute force and rainbow tables are infeasable due to the key length.
277		// When hashing the bearer keys, the prefix and key identifier are kept as salt.
278		let mut hasher = Sha256::new();
279		hasher.update(self.key.as_string());
280		let hash = hasher.finalize();
281		let hash_hex = format!("{hash:x}").into();
282
283		Self {
284			key: hash_hex,
285			..self
286		}
287	}
288}
289
290fn random_string(length: usize, pool: &[u8]) -> String {
291	let mut rng = rand::thread_rng();
292	let string: String = (0..length)
293		.map(|_| {
294			let i = rng.gen_range(0..pool.len());
295			pool[i] as char
296		})
297		.collect();
298	string
299}
300
301pub async fn create_grant(
302	stmt: &AccessStatementGrant,
303	ctx: &Context,
304	opt: &Options,
305) -> Result<AccessGrant, Error> {
306	let base = match &stmt.base {
307		Some(base) => base.clone(),
308		None => opt.selected_base()?,
309	};
310	// Allowed to run?
311	opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
312	// Get the transaction.
313	let txn = ctx.tx();
314	// Clear the cache.
315	txn.clear();
316	// Read the access definition.
317	let ac = match base {
318		Base::Root => txn.get_root_access(&stmt.ac).await?,
319		Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
320		Base::Db => {
321			let (ns, db) = opt.ns_db()?;
322			txn.get_db_access(ns, db, &stmt.ac).await?
323		}
324		_ => {
325			return Err(Error::Unimplemented(
326				"Managing access methods outside of root, namespace and database levels"
327					.to_string(),
328			))
329		}
330	};
331	// Verify the access type.
332	match &ac.kind {
333		AccessType::Jwt(_) => Err(Error::FeatureNotYetImplemented {
334			feature: format!("Grants for JWT on {base}"),
335		}),
336		AccessType::Record(at) => {
337			match &stmt.subject {
338				Subject::User(_) => {
339					return Err(Error::AccessGrantInvalidSubject);
340				}
341				Subject::Record(_) => {
342					// If the grant is being created for a record, a database must be selected.
343					if !matches!(base, Base::Db) {
344						return Err(Error::DbEmpty);
345					}
346				}
347			};
348			// The record access type must allow issuing bearer grants.
349			let atb = match &at.bearer {
350				Some(bearer) => bearer,
351				None => return Err(Error::AccessMethodMismatch),
352			};
353			// Create a new bearer key.
354			let grant = GrantBearer::new(atb.kind.prefix());
355			let gr = AccessGrant {
356				ac: ac.name.clone(),
357				// Unique grant identifier.
358				// In the case of bearer grants, the key identifier.
359				id: grant.id.clone(),
360				// Current time.
361				creation: Datetime::default(),
362				// Current time plus grant duration. Only if set.
363				expiration: ac.duration.grant.map(|d| d + Datetime::default()),
364				// The grant is initially not revoked.
365				revocation: None,
366				// Subject associated with the grant.
367				subject: stmt.subject.to_owned(),
368				// The contents of the grant.
369				grant: Grant::Bearer(grant.clone()),
370			};
371
372			// Create the grant.
373			// On the very unlikely event of a collision, "put" will return an error.
374			let res = match base {
375				Base::Db => {
376					// Create a hashed version of the grant for storage.
377					let mut gr_store = gr.clone();
378					gr_store.grant = Grant::Bearer(grant.hashed());
379					let (ns, db) = opt.ns_db()?;
380					let key = crate::key::database::access::gr::new(ns, db, &gr.ac, &gr.id);
381					txn.get_or_add_ns(ns, opt.strict).await?;
382					txn.get_or_add_db(ns, db, opt.strict).await?;
383					txn.put(key, revision::to_vec(&gr_store)?, None).await
384				}
385				_ => return Err(Error::AccessLevelMismatch),
386			};
387
388			// Check if a collision was found in order to log a specific error on the server.
389			// For an access method with a billion grants, this chance is of only one in 295 billion.
390			if let Err(Error::TxKeyAlreadyExists) = res {
391				error!("A collision was found when attempting to create a new grant. Purging inactive grants is advised")
392			}
393			res?;
394
395			info!(
396				"Access method '{}' was used to create grant '{}' of type '{}' for '{}' by '{}'",
397				gr.ac,
398				gr.id,
399				gr.grant.variant(),
400				gr.subject.id(),
401				opt.auth.id()
402			);
403
404			// Return the original version of the grant.
405			// This is the only time the the plaintext key is returned.
406			Ok(gr)
407		}
408		AccessType::Bearer(at) => {
409			match &stmt.subject {
410				Subject::User(user) => {
411					// Grant subject must match access method subject.
412					if !matches!(&at.subject, BearerAccessSubject::User) {
413						return Err(Error::AccessGrantInvalidSubject);
414					}
415					// If the grant is being created for a user, the user must exist.
416					match base {
417						Base::Root => txn.get_root_user(user).await?,
418						Base::Ns => txn.get_ns_user(opt.ns()?, user).await?,
419						Base::Db => {
420							let (ns, db) = opt.ns_db()?;
421							txn.get_db_user(ns, db, user).await?
422						},
423						_ => return Err(Error::Unimplemented(
424							"Managing access methods outside of root, namespace and database levels".to_string(),
425						)),
426					};
427				}
428				Subject::Record(_) => {
429					// If the grant is being created for a record, a database must be selected.
430					if !matches!(base, Base::Db) {
431						return Err(Error::DbEmpty);
432					}
433					// Grant subject must match access method subject.
434					if !matches!(&at.subject, BearerAccessSubject::Record) {
435						return Err(Error::AccessGrantInvalidSubject);
436					}
437					// A grant can be created for a record that does not exist yet.
438				}
439			};
440			// Create a new bearer key.
441			let grant = GrantBearer::new(at.kind.prefix());
442			let gr = AccessGrant {
443				ac: ac.name.clone(),
444				// Unique grant identifier.
445				// In the case of bearer grants, the key identifier.
446				id: grant.id.clone(),
447				// Current time.
448				creation: Datetime::default(),
449				// Current time plus grant duration. Only if set.
450				expiration: ac.duration.grant.map(|d| d + Datetime::default()),
451				// The grant is initially not revoked.
452				revocation: None,
453				// Subject associated with the grant.
454				subject: stmt.subject.to_owned(),
455				// The contents of the grant.
456				grant: Grant::Bearer(grant.clone()),
457			};
458
459			// Create the grant.
460			// On the very unlikely event of a collision, "put" will return an error.
461			// Create a hashed version of the grant for storage.
462			let mut gr_store = gr.clone();
463			gr_store.grant = Grant::Bearer(grant.hashed());
464			let res =
465				match base {
466					Base::Root => {
467						let key = crate::key::root::access::gr::new(&gr.ac, &gr.id);
468						txn.put(key, revision::to_vec(&gr_store)?, None).await
469					}
470					Base::Ns => {
471						let key = crate::key::namespace::access::gr::new(opt.ns()?, &gr.ac, &gr.id);
472						txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
473						txn.put(key, revision::to_vec(&gr_store)?, None).await
474					}
475					Base::Db => {
476						let (ns, db) = opt.ns_db()?;
477						let key = crate::key::database::access::gr::new(ns, db, &gr.ac, &gr.id);
478						txn.get_or_add_ns(ns, opt.strict).await?;
479						txn.get_or_add_db(ns, db, opt.strict).await?;
480						txn.put(key, revision::to_vec(&gr_store)?, None).await
481					}
482					_ => return Err(Error::Unimplemented(
483						"Managing access methods outside of root, namespace and database levels"
484							.to_string(),
485					)),
486				};
487
488			// Check if a collision was found in order to log a specific error on the server.
489			// For an access method with a billion grants, this chance is of only one in 295 billion.
490			if let Err(Error::TxKeyAlreadyExists) = res {
491				error!("A collision was found when attempting to create a new grant. Purging inactive grants is advised")
492			}
493			res?;
494
495			info!(
496				"Access method '{}' was used to create grant '{}' of type '{}' for '{}' by '{}'",
497				gr.ac,
498				gr.id,
499				gr.grant.variant(),
500				gr.subject.id(),
501				opt.auth.id()
502			);
503
504			// Return the original version of the grant.
505			// This is the only time the the plaintext key is returned.
506			Ok(gr)
507		}
508	}
509}
510
511async fn compute_grant(
512	stmt: &AccessStatementGrant,
513	ctx: &Context,
514	opt: &Options,
515	_doc: Option<&CursorDoc>,
516) -> Result<Value, Error> {
517	let grant = create_grant(stmt, ctx, opt).await?;
518	Ok(Value::Object(grant.into()))
519}
520
521async fn compute_show(
522	stmt: &AccessStatementShow,
523	stk: &mut Stk,
524	ctx: &Context,
525	opt: &Options,
526	_doc: Option<&CursorDoc>,
527) -> Result<Value, Error> {
528	let base = match &stmt.base {
529		Some(base) => base.clone(),
530		None => opt.selected_base()?,
531	};
532	// Allowed to run?
533	opt.is_allowed(Action::View, ResourceKind::Access, &base)?;
534	// Get the transaction.
535	let txn = ctx.tx();
536	// Clear the cache.
537	txn.clear();
538	// Check if the access method exists.
539	match base {
540		Base::Root => txn.get_root_access(&stmt.ac).await?,
541		Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
542		Base::Db => {
543			let (ns, db) = opt.ns_db()?;
544			txn.get_db_access(ns, db, &stmt.ac).await?
545		}
546		_ => {
547			return Err(Error::Unimplemented(
548				"Managing access methods outside of root, namespace and database levels"
549					.to_string(),
550			))
551		}
552	};
553
554	// Get the grants to show.
555	match &stmt.gr {
556		Some(gr) => {
557			let grant =
558				match base {
559					Base::Root => (*txn.get_root_access_grant(&stmt.ac, gr).await?).clone(),
560					Base::Ns => (*txn.get_ns_access_grant(opt.ns()?, &stmt.ac, gr).await?).clone(),
561					Base::Db => {
562						let (ns, db) = opt.ns_db()?;
563						(*txn.get_db_access_grant(ns, db, &stmt.ac, gr).await?).clone()
564					}
565					_ => return Err(Error::Unimplemented(
566						"Managing access methods outside of root, namespace and database levels"
567							.to_string(),
568					)),
569				};
570
571			Ok(Value::Object(grant.redacted().into()))
572		}
573		None => {
574			// Get all grants.
575			let grs =
576				match base {
577					Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
578					Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
579					Base::Db => {
580						let (ns, db) = opt.ns_db()?;
581						txn.all_db_access_grants(ns, db, &stmt.ac).await?
582					}
583					_ => return Err(Error::Unimplemented(
584						"Managing access methods outside of root, namespace and database levels"
585							.to_string(),
586					)),
587				};
588
589			let mut show = Vec::new();
590			for gr in grs.iter() {
591				// If provided, check if grant matches conditions.
592				if let Some(cond) = &stmt.cond {
593					// Redact grant before evaluating conditions.
594					let redacted_gr = Value::Object(gr.redacted().to_owned().into());
595					if !cond
596						.compute(
597							stk,
598							ctx,
599							opt,
600							Some(&CursorDoc {
601								rid: None,
602								ir: None,
603								doc: redacted_gr.into(),
604							}),
605						)
606						.await?
607						.is_truthy()
608					{
609						// Skip grant if it does not match the provided conditions.
610						continue;
611					}
612				}
613
614				// Store revoked version of the redacted grant.
615				show.push(Value::Object(gr.redacted().to_owned().into()));
616			}
617
618			Ok(Value::Array(show.into()))
619		}
620	}
621}
622
623pub async fn revoke_grant(
624	stmt: &AccessStatementRevoke,
625	stk: &mut Stk,
626	ctx: &Context,
627	opt: &Options,
628) -> Result<Value, Error> {
629	let base = match &stmt.base {
630		Some(base) => base.clone(),
631		None => opt.selected_base()?,
632	};
633	// Allowed to run?
634	opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
635	// Get the transaction
636	let txn = ctx.tx();
637	// Clear the cache
638	txn.clear();
639	// Check if the access method exists.
640	match base {
641		Base::Root => txn.get_root_access(&stmt.ac).await?,
642		Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
643		Base::Db => {
644			let (ns, db) = opt.ns_db()?;
645			txn.get_db_access(ns, db, &stmt.ac).await?
646		}
647		_ => {
648			return Err(Error::Unimplemented(
649				"Managing access methods outside of root, namespace and database levels"
650					.to_string(),
651			))
652		}
653	};
654
655	// Get the grants to revoke.
656	let mut revoked = Vec::new();
657	match &stmt.gr {
658		Some(gr) => {
659			let mut revoke =
660				match base {
661					Base::Root => (*txn.get_root_access_grant(&stmt.ac, gr).await?).clone(),
662					Base::Ns => (*txn.get_ns_access_grant(opt.ns()?, &stmt.ac, gr).await?).clone(),
663					Base::Db => {
664						let (ns, db) = opt.ns_db()?;
665						(*txn.get_db_access_grant(ns, db, &stmt.ac, gr).await?).clone()
666					}
667					_ => return Err(Error::Unimplemented(
668						"Managing access methods outside of root, namespace and database levels"
669							.to_string(),
670					)),
671				};
672			if revoke.revocation.is_some() {
673				return Err(Error::AccessGrantRevoked);
674			}
675			revoke.revocation = Some(Datetime::default());
676
677			// Revoke the grant.
678			match base {
679				Base::Root => {
680					let key = crate::key::root::access::gr::new(&stmt.ac, gr);
681					txn.set(key, revision::to_vec(&revoke)?, None).await?;
682				}
683				Base::Ns => {
684					let key = crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, gr);
685					txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
686					txn.set(key, revision::to_vec(&revoke)?, None).await?;
687				}
688				Base::Db => {
689					let (ns, db) = opt.ns_db()?;
690					let key = crate::key::database::access::gr::new(ns, db, &stmt.ac, gr);
691					txn.get_or_add_ns(ns, opt.strict).await?;
692					txn.get_or_add_db(ns, db, opt.strict).await?;
693					txn.set(key, revision::to_vec(&revoke)?, None).await?;
694				}
695				_ => {
696					return Err(Error::Unimplemented(
697						"Managing access methods outside of root, namespace and database levels"
698							.to_string(),
699					))
700				}
701			};
702
703			info!(
704				"Access method '{}' was used to revoke grant '{}' of type '{}' for '{}' by '{}'",
705				revoke.ac,
706				revoke.id,
707				revoke.grant.variant(),
708				revoke.subject.id(),
709				opt.auth.id()
710			);
711
712			revoked.push(Value::Object(revoke.redacted().into()));
713		}
714		None => {
715			// Get all grants.
716			let grs =
717				match base {
718					Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
719					Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
720					Base::Db => {
721						let (ns, db) = opt.ns_db()?;
722						txn.all_db_access_grants(ns, db, &stmt.ac).await?
723					}
724					_ => return Err(Error::Unimplemented(
725						"Managing access methods outside of root, namespace and database levels"
726							.to_string(),
727					)),
728				};
729
730			for gr in grs.iter() {
731				// If the grant is already revoked, it cannot be revoked again.
732				if gr.revocation.is_some() {
733					continue;
734				}
735
736				// If provided, check if grant matches conditions.
737				if let Some(cond) = &stmt.cond {
738					// Redact grant before evaluating conditions.
739					let redacted_gr = Value::Object(gr.redacted().to_owned().into());
740					if !cond
741						.compute(
742							stk,
743							ctx,
744							opt,
745							Some(&CursorDoc {
746								rid: None,
747								ir: None,
748								doc: redacted_gr.into(),
749							}),
750						)
751						.await?
752						.is_truthy()
753					{
754						// Skip grant if it does not match the provided conditions.
755						continue;
756					}
757				}
758
759				let mut gr = gr.clone();
760				gr.revocation = Some(Datetime::default());
761
762				// Revoke the grant.
763				match base {
764					Base::Root => {
765						let key = crate::key::root::access::gr::new(&stmt.ac, &gr.id);
766						txn.set(key, revision::to_vec(&gr)?, None).await?;
767					}
768					Base::Ns => {
769						let key =
770							crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, &gr.id);
771						txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
772						txn.set(key, revision::to_vec(&gr)?, None).await?;
773					}
774					Base::Db => {
775						let (ns, db) = opt.ns_db()?;
776						let key = crate::key::database::access::gr::new(ns, db, &stmt.ac, &gr.id);
777						txn.get_or_add_ns(ns, opt.strict).await?;
778						txn.get_or_add_db(ns, db, opt.strict).await?;
779						txn.set(key, revision::to_vec(&gr)?, None).await?;
780					}
781					_ => return Err(Error::Unimplemented(
782						"Managing access methods outside of root, namespace and database levels"
783							.to_string(),
784					)),
785				};
786
787				info!(
788					"Access method '{}' was used to revoke grant '{}' of type '{}' for '{}' by '{}'",
789					gr.ac,
790					gr.id,
791					gr.grant.variant(),
792					gr.subject.id(),
793					opt.auth.id()
794				);
795
796				// Store revoked version of the redacted grant.
797				revoked.push(Value::Object(gr.redacted().into()));
798			}
799		}
800	}
801
802	// Return revoked grants.
803	Ok(Value::Array(revoked.into()))
804}
805
806async fn compute_revoke(
807	stmt: &AccessStatementRevoke,
808	stk: &mut Stk,
809	ctx: &Context,
810	opt: &Options,
811	_doc: Option<&CursorDoc>,
812) -> Result<Value, Error> {
813	let revoked = revoke_grant(stmt, stk, ctx, opt).await?;
814	Ok(Value::Array(revoked.into()))
815}
816
817async fn compute_purge(
818	stmt: &AccessStatementPurge,
819	ctx: &Context,
820	opt: &Options,
821	_doc: Option<&CursorDoc>,
822) -> Result<Value, Error> {
823	let base = match &stmt.base {
824		Some(base) => base.clone(),
825		None => opt.selected_base()?,
826	};
827	// Allowed to run?
828	opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
829	// Get the transaction.
830	let txn = ctx.tx();
831	// Clear the cache.
832	txn.clear();
833	// Check if the access method exists.
834	match base {
835		Base::Root => txn.get_root_access(&stmt.ac).await?,
836		Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
837		Base::Db => {
838			let (ns, db) = opt.ns_db()?;
839			txn.get_db_access(ns, db, &stmt.ac).await?
840		}
841		_ => {
842			return Err(Error::Unimplemented(
843				"Managing access methods outside of root, namespace and database levels"
844					.to_string(),
845			))
846		}
847	};
848	// Get all grants to purge.
849	let mut purged = Array::default();
850	let grs = match base {
851		Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
852		Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
853		Base::Db => {
854			let (ns, db) = opt.ns_db()?;
855			txn.all_db_access_grants(ns, db, &stmt.ac).await?
856		}
857		_ => {
858			return Err(Error::Unimplemented(
859				"Managing access methods outside of root, namespace and database levels"
860					.to_string(),
861			))
862		}
863	};
864	for gr in grs.iter() {
865		// Determine if the grant should purged based on expiration or revocation.
866		let now = Datetime::default();
867		// We can convert to unsigned integer as substraction is saturating.
868		// Revocation times should never exceed the current time.
869		// Grants expired or revoked at a future time will not be purged.
870		// Grants expired or revoked at exactly the current second will not be purged.
871		let purge_expired = stmt.expired
872			&& gr.expiration.as_ref().is_some_and(|exp| {
873				                 now.timestamp() >= exp.timestamp() // Prevent saturating when not expired yet.
874				                     && (now.timestamp().saturating_sub(exp.timestamp()) as u64) > stmt.grace.secs()
875				             });
876		let purge_revoked = stmt.revoked
877			&& gr.revocation.as_ref().is_some_and(|rev| {
878				                 now.timestamp() >= rev.timestamp() // Prevent saturating when not revoked yet.
879				                     && (now.timestamp().saturating_sub(rev.timestamp()) as u64) > stmt.grace.secs()
880				             });
881		// If it should, delete the grant and append the redacted version to the result.
882		if purge_expired || purge_revoked {
883			match base {
884				Base::Root => txn.del(crate::key::root::access::gr::new(&stmt.ac, &gr.id)).await?,
885				Base::Ns => {
886					txn.del(crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, &gr.id))
887						.await?
888				}
889				Base::Db => {
890					let (ns, db) = opt.ns_db()?;
891					txn.del(crate::key::database::access::gr::new(ns, db, &stmt.ac, &gr.id)).await?
892				}
893				_ => {
894					return Err(Error::Unimplemented(
895						"Managing access methods outside of root, namespace and database levels"
896							.to_string(),
897					))
898				}
899			};
900
901			info!(
902				"Access method '{}' was used to purge grant '{}' of type '{}' for '{}' by '{}'",
903				gr.ac,
904				gr.id,
905				gr.grant.variant(),
906				gr.subject.id(),
907				opt.auth.id()
908			);
909
910			purged = purged + Value::Object(gr.redacted().to_owned().into());
911		}
912	}
913
914	Ok(Value::Array(purged))
915}
916
917impl AccessStatement {
918	/// Process this type returning a computed simple Value
919	pub(crate) async fn compute(
920		&self,
921		stk: &mut Stk,
922		ctx: &Context,
923		opt: &Options,
924		_doc: Option<&CursorDoc>,
925	) -> Result<Value, Error> {
926		match self {
927			AccessStatement::Grant(stmt) => compute_grant(stmt, ctx, opt, _doc).await,
928			AccessStatement::Show(stmt) => compute_show(stmt, stk, ctx, opt, _doc).await,
929			AccessStatement::Revoke(stmt) => compute_revoke(stmt, stk, ctx, opt, _doc).await,
930			AccessStatement::Purge(stmt) => compute_purge(stmt, ctx, opt, _doc).await,
931		}
932	}
933}
934
935impl Display for AccessStatement {
936	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
937		match self {
938			Self::Grant(stmt) => {
939				write!(f, "ACCESS {}", stmt.ac)?;
940				if let Some(ref v) = stmt.base {
941					write!(f, " ON {v}")?;
942				}
943				write!(f, " GRANT")?;
944				match stmt.subject {
945					Subject::User(_) => write!(f, " FOR USER {}", stmt.subject.id())?,
946					Subject::Record(_) => write!(f, " FOR RECORD {}", stmt.subject.id())?,
947				}
948				Ok(())
949			}
950			Self::Show(stmt) => {
951				write!(f, "ACCESS {}", stmt.ac)?;
952				if let Some(ref v) = stmt.base {
953					write!(f, " ON {v}")?;
954				}
955				write!(f, " SHOW")?;
956				match &stmt.gr {
957					Some(v) => write!(f, " GRANT {v}")?,
958					None => match &stmt.cond {
959						Some(v) => write!(f, " {v}")?,
960						None => write!(f, " ALL")?,
961					},
962				};
963				Ok(())
964			}
965			Self::Revoke(stmt) => {
966				write!(f, "ACCESS {}", stmt.ac)?;
967				if let Some(ref v) = stmt.base {
968					write!(f, " ON {v}")?;
969				}
970				write!(f, " REVOKE")?;
971				match &stmt.gr {
972					Some(v) => write!(f, " GRANT {v}")?,
973					None => match &stmt.cond {
974						Some(v) => write!(f, " {v}")?,
975						None => write!(f, " ALL")?,
976					},
977				};
978				Ok(())
979			}
980			Self::Purge(stmt) => {
981				write!(f, "ACCESS {}", stmt.ac)?;
982				if let Some(ref v) = stmt.base {
983					write!(f, " ON {v}")?;
984				}
985				write!(f, " PURGE")?;
986				match (stmt.expired, stmt.revoked) {
987					(true, false) => write!(f, " EXPIRED")?,
988					(false, true) => write!(f, " REVOKED")?,
989					(true, true) => write!(f, " EXPIRED, REVOKED")?,
990					// This case should not parse.
991					(false, false) => write!(f, " NONE")?,
992				};
993				if !stmt.grace.is_zero() {
994					write!(f, " FOR {}", stmt.grace)?;
995				}
996				Ok(())
997			}
998		}
999	}
1000}