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
19pub static GRANT_BEARER_CHARACTER_POOL: &[u8] =
21 b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
22pub static GRANT_BEARER_ID_LENGTH: usize = 12;
27pub 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), Show(AccessStatementShow), Revoke(AccessStatementRevoke), Purge(AccessStatementPurge), }
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, pub ac: Ident, pub creation: Datetime, pub expiration: Option<Datetime>, pub revocation: Option<Datetime>, pub subject: Subject, pub grant: Grant, }
98
99impl AccessGrant {
100 pub fn redacted(&self) -> AccessGrant {
104 let mut ags = self.clone();
105 ags.grant = match ags.grant {
106 Grant::Jwt(mut gr) => {
107 gr.token = None;
109 Grant::Jwt(gr)
110 }
111 Grant::Record(mut gr) => {
112 gr.token = None;
114 Grant::Record(gr)
115 }
116 Grant::Bearer(mut gr) => {
117 gr.key = "[REDACTED]".into();
119 Grant::Bearer(gr)
120 }
121 };
122 ags
123 }
124
125 pub fn is_expired(&self) -> bool {
127 match &self.expiration {
128 Some(exp) => exp < &Datetime::default(),
129 None => false,
130 }
131 }
132
133 pub fn is_revoked(&self) -> bool {
135 self.revocation.is_some()
136 }
137
138 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 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 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, pub token: Option<Strand>, }
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, pub jti: Uuid, pub token: Option<Strand>, }
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, 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 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 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 opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
312 let txn = ctx.tx();
314 txn.clear();
316 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 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 !matches!(base, Base::Db) {
344 return Err(Error::DbEmpty);
345 }
346 }
347 };
348 let atb = match &at.bearer {
350 Some(bearer) => bearer,
351 None => return Err(Error::AccessMethodMismatch),
352 };
353 let grant = GrantBearer::new(atb.kind.prefix());
355 let gr = AccessGrant {
356 ac: ac.name.clone(),
357 id: grant.id.clone(),
360 creation: Datetime::default(),
362 expiration: ac.duration.grant.map(|d| d + Datetime::default()),
364 revocation: None,
366 subject: stmt.subject.to_owned(),
368 grant: Grant::Bearer(grant.clone()),
370 };
371
372 let res = match base {
375 Base::Db => {
376 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 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 Ok(gr)
407 }
408 AccessType::Bearer(at) => {
409 match &stmt.subject {
410 Subject::User(user) => {
411 if !matches!(&at.subject, BearerAccessSubject::User) {
413 return Err(Error::AccessGrantInvalidSubject);
414 }
415 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 !matches!(base, Base::Db) {
431 return Err(Error::DbEmpty);
432 }
433 if !matches!(&at.subject, BearerAccessSubject::Record) {
435 return Err(Error::AccessGrantInvalidSubject);
436 }
437 }
439 };
440 let grant = GrantBearer::new(at.kind.prefix());
442 let gr = AccessGrant {
443 ac: ac.name.clone(),
444 id: grant.id.clone(),
447 creation: Datetime::default(),
449 expiration: ac.duration.grant.map(|d| d + Datetime::default()),
451 revocation: None,
453 subject: stmt.subject.to_owned(),
455 grant: Grant::Bearer(grant.clone()),
457 };
458
459 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 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 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 opt.is_allowed(Action::View, ResourceKind::Access, &base)?;
534 let txn = ctx.tx();
536 txn.clear();
538 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 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 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 let Some(cond) = &stmt.cond {
593 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 continue;
611 }
612 }
613
614 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 opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
635 let txn = ctx.tx();
637 txn.clear();
639 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 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 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 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 gr.revocation.is_some() {
733 continue;
734 }
735
736 if let Some(cond) = &stmt.cond {
738 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 continue;
756 }
757 }
758
759 let mut gr = gr.clone();
760 gr.revocation = Some(Datetime::default());
761
762 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 revoked.push(Value::Object(gr.redacted().into()));
798 }
799 }
800 }
801
802 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 opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
829 let txn = ctx.tx();
831 txn.clear();
833 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 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 let now = Datetime::default();
867 let purge_expired = stmt.expired
872 && gr.expiration.as_ref().is_some_and(|exp| {
873 now.timestamp() >= exp.timestamp() && (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() && (now.timestamp().saturating_sub(rev.timestamp()) as u64) > stmt.grace.secs()
880 });
881 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 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 (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}