use crate::ctx::Context;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::iam::{Action, ResourceKind};
use crate::sql::access_type::BearerAccessSubject;
use crate::sql::{
AccessType, Array, Base, Cond, Datetime, Duration, Ident, Object, Strand, Thing, Uuid, Value,
};
use derive::Store;
use md5::Digest;
use rand::Rng;
use reblessive::tree::Stk;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::fmt;
use std::fmt::{Display, Formatter};
pub static GRANT_BEARER_CHARACTER_POOL: &[u8] =
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
pub static GRANT_BEARER_ID_LENGTH: usize = 12;
pub static GRANT_BEARER_KEY_LENGTH: usize = 24;
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum AccessStatement {
Grant(AccessStatementGrant), Show(AccessStatementShow), Revoke(AccessStatementRevoke), Purge(AccessStatementPurge), }
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct AccessStatementGrant {
pub ac: Ident,
pub base: Option<Base>,
pub subject: Subject,
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct AccessStatementShow {
pub ac: Ident,
pub base: Option<Base>,
pub gr: Option<Ident>,
pub cond: Option<Cond>,
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct AccessStatementRevoke {
pub ac: Ident,
pub base: Option<Base>,
pub gr: Option<Ident>,
pub cond: Option<Cond>,
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct AccessStatementPurge {
pub ac: Ident,
pub base: Option<Base>,
pub expired: bool,
pub revoked: bool,
pub grace: Duration,
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct AccessGrant {
pub id: Ident, pub ac: Ident, pub creation: Datetime, pub expiration: Option<Datetime>, pub revocation: Option<Datetime>, pub subject: Subject, pub grant: Grant, }
impl AccessGrant {
pub fn redacted(&self) -> AccessGrant {
let mut ags = self.clone();
ags.grant = match ags.grant {
Grant::Jwt(mut gr) => {
gr.token = None;
Grant::Jwt(gr)
}
Grant::Record(mut gr) => {
gr.token = None;
Grant::Record(gr)
}
Grant::Bearer(mut gr) => {
gr.key = "[REDACTED]".into();
Grant::Bearer(gr)
}
};
ags
}
pub fn is_expired(&self) -> bool {
match &self.expiration {
Some(exp) => exp < &Datetime::default(),
None => false,
}
}
pub fn is_revoked(&self) -> bool {
self.revocation.is_some()
}
pub fn is_active(&self) -> bool {
!(self.is_expired() || self.is_revoked())
}
}
impl From<AccessGrant> for Object {
fn from(grant: AccessGrant) -> Self {
let mut res = Object::default();
res.insert("id".to_owned(), Value::from(grant.id.to_raw()));
res.insert("ac".to_owned(), Value::from(grant.ac.to_raw()));
res.insert("type".to_owned(), Value::from(grant.grant.variant()));
res.insert("creation".to_owned(), Value::from(grant.creation));
res.insert("expiration".to_owned(), Value::from(grant.expiration));
res.insert("revocation".to_owned(), Value::from(grant.revocation));
let mut sub = Object::default();
match grant.subject {
Subject::Record(id) => sub.insert("record".to_owned(), Value::from(id)),
Subject::User(name) => sub.insert("user".to_owned(), Value::from(name.to_raw())),
};
res.insert("subject".to_owned(), Value::from(sub));
let mut gr = Object::default();
match grant.grant {
Grant::Jwt(jg) => {
gr.insert("jti".to_owned(), Value::from(jg.jti));
if let Some(token) = jg.token {
gr.insert("token".to_owned(), Value::from(token));
}
}
Grant::Record(rg) => {
gr.insert("rid".to_owned(), Value::from(rg.rid));
gr.insert("jti".to_owned(), Value::from(rg.jti));
if let Some(token) = rg.token {
gr.insert("token".to_owned(), Value::from(token));
}
}
Grant::Bearer(bg) => {
gr.insert("id".to_owned(), Value::from(bg.id.to_raw()));
gr.insert("key".to_owned(), Value::from(bg.key));
}
};
res.insert("grant".to_owned(), Value::from(gr));
res
}
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum Subject {
Record(Thing),
User(Ident),
}
impl Subject {
pub fn id(&self) -> String {
match self {
Subject::Record(id) => id.to_raw(),
Subject::User(name) => name.to_raw(),
}
}
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum Grant {
Jwt(GrantJwt),
Record(GrantRecord),
Bearer(GrantBearer),
}
impl Grant {
pub fn variant(&self) -> &str {
match self {
Grant::Jwt(_) => "jwt",
Grant::Record(_) => "record",
Grant::Bearer(_) => "bearer",
}
}
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct GrantJwt {
pub jti: Uuid, pub token: Option<Strand>, }
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct GrantRecord {
pub rid: Uuid, pub jti: Uuid, pub token: Option<Strand>, }
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct GrantBearer {
pub id: Ident, pub key: Strand,
}
impl GrantBearer {
#[allow(clippy::new_without_default)]
pub fn new(prefix: &str) -> Self {
let id = format!(
"{}{}",
random_string(1, &GRANT_BEARER_CHARACTER_POOL[10..]),
random_string(GRANT_BEARER_ID_LENGTH - 1, GRANT_BEARER_CHARACTER_POOL)
);
let secret = random_string(GRANT_BEARER_KEY_LENGTH, GRANT_BEARER_CHARACTER_POOL);
Self {
id: id.clone().into(),
key: format!("{prefix}-{id}-{secret}").into(),
}
}
pub fn hashed(self) -> Self {
let mut hasher = Sha256::new();
hasher.update(self.key.as_string());
let hash = hasher.finalize();
let hash_hex = format!("{hash:x}").into();
Self {
key: hash_hex,
..self
}
}
}
fn random_string(length: usize, pool: &[u8]) -> String {
let mut rng = rand::thread_rng();
let string: String = (0..length)
.map(|_| {
let i = rng.gen_range(0..pool.len());
pool[i] as char
})
.collect();
string
}
pub async fn create_grant(
stmt: &AccessStatementGrant,
ctx: &Context,
opt: &Options,
) -> Result<AccessGrant, Error> {
let base = match &stmt.base {
Some(base) => base.clone(),
None => opt.selected_base()?,
};
opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
let txn = ctx.tx();
txn.clear();
let ac = match base {
Base::Root => txn.get_root_access(&stmt.ac).await?,
Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.get_db_access(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
match &ac.kind {
AccessType::Jwt(_) => Err(Error::FeatureNotYetImplemented {
feature: format!("Grants for JWT on {base}"),
}),
AccessType::Record(at) => {
match &stmt.subject {
Subject::User(_) => {
return Err(Error::AccessGrantInvalidSubject);
}
Subject::Record(_) => {
if !matches!(base, Base::Db) {
return Err(Error::DbEmpty);
}
}
};
let atb = match &at.bearer {
Some(bearer) => bearer,
None => return Err(Error::AccessMethodMismatch),
};
let grant = GrantBearer::new(atb.kind.prefix());
let gr = AccessGrant {
ac: ac.name.clone(),
id: grant.id.clone(),
creation: Datetime::default(),
expiration: ac.duration.grant.map(|d| d + Datetime::default()),
revocation: None,
subject: stmt.subject.to_owned(),
grant: Grant::Bearer(grant.clone()),
};
let res = match base {
Base::Db => {
let mut gr_store = gr.clone();
gr_store.grant = Grant::Bearer(grant.hashed());
let key =
crate::key::database::access::gr::new(opt.ns()?, opt.db()?, &gr.ac, &gr.id);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.get_or_add_db(opt.ns()?, opt.db()?, opt.strict).await?;
txn.put(key, &gr_store, None).await
}
_ => return Err(Error::AccessLevelMismatch),
};
if let Err(Error::TxKeyAlreadyExists) = res {
error!("A collision was found when attempting to create a new grant. Purging inactive grants is advised")
}
res?;
info!(
"Access method '{}' was used to create grant '{}' of type '{}' for '{}' by '{}'",
gr.ac,
gr.id,
gr.grant.variant(),
gr.subject.id(),
opt.auth.id()
);
Ok(gr)
}
AccessType::Bearer(at) => {
match &stmt.subject {
Subject::User(user) => {
if !matches!(&at.subject, BearerAccessSubject::User) {
return Err(Error::AccessGrantInvalidSubject);
}
match base {
Base::Root => txn.get_root_user(user).await?,
Base::Ns => txn.get_ns_user(opt.ns()?, user).await?,
Base::Db => txn.get_db_user(opt.ns()?, opt.db()?, user).await?,
_ => return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels".to_string(),
)),
};
}
Subject::Record(_) => {
if !matches!(base, Base::Db) {
return Err(Error::DbEmpty);
}
if !matches!(&at.subject, BearerAccessSubject::Record) {
return Err(Error::AccessGrantInvalidSubject);
}
}
};
let grant = GrantBearer::new(at.kind.prefix());
let gr = AccessGrant {
ac: ac.name.clone(),
id: grant.id.clone(),
creation: Datetime::default(),
expiration: ac.duration.grant.map(|d| d + Datetime::default()),
revocation: None,
subject: stmt.subject.to_owned(),
grant: Grant::Bearer(grant.clone()),
};
let mut gr_store = gr.clone();
gr_store.grant = Grant::Bearer(grant.hashed());
let res = match base {
Base::Root => {
let key = crate::key::root::access::gr::new(&gr.ac, &gr.id);
txn.put(key, &gr_store, None).await
}
Base::Ns => {
let key = crate::key::namespace::access::gr::new(opt.ns()?, &gr.ac, &gr.id);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.put(key, &gr_store, None).await
}
Base::Db => {
let key =
crate::key::database::access::gr::new(opt.ns()?, opt.db()?, &gr.ac, &gr.id);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.get_or_add_db(opt.ns()?, opt.db()?, opt.strict).await?;
txn.put(key, &gr_store, None).await
}
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
if let Err(Error::TxKeyAlreadyExists) = res {
error!("A collision was found when attempting to create a new grant. Purging inactive grants is advised")
}
res?;
info!(
"Access method '{}' was used to create grant '{}' of type '{}' for '{}' by '{}'",
gr.ac,
gr.id,
gr.grant.variant(),
gr.subject.id(),
opt.auth.id()
);
Ok(gr)
}
}
}
async fn compute_grant(
stmt: &AccessStatementGrant,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
let grant = create_grant(stmt, ctx, opt).await?;
Ok(Value::Object(grant.into()))
}
async fn compute_show(
stmt: &AccessStatementShow,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
let base = match &stmt.base {
Some(base) => base.clone(),
None => opt.selected_base()?,
};
opt.is_allowed(Action::View, ResourceKind::Access, &base)?;
let txn = ctx.tx();
txn.clear();
match base {
Base::Root => txn.get_root_access(&stmt.ac).await?,
Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.get_db_access(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
match &stmt.gr {
Some(gr) => {
let grant = match base {
Base::Root => (*txn.get_root_access_grant(&stmt.ac, gr).await?).clone(),
Base::Ns => (*txn.get_ns_access_grant(opt.ns()?, &stmt.ac, gr).await?).clone(),
Base::Db => {
(*txn.get_db_access_grant(opt.ns()?, opt.db()?, &stmt.ac, gr).await?).clone()
}
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
Ok(Value::Object(grant.redacted().into()))
}
None => {
let grs =
match base {
Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.all_db_access_grants(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
)),
};
let mut show = Vec::new();
for gr in grs.iter() {
if let Some(cond) = &stmt.cond {
let redacted_gr = Value::Object(gr.redacted().to_owned().into());
if !cond
.compute(
stk,
ctx,
opt,
Some(&CursorDoc {
rid: None,
ir: None,
doc: redacted_gr.into(),
}),
)
.await?
.is_truthy()
{
continue;
}
}
show.push(Value::Object(gr.redacted().to_owned().into()));
}
Ok(Value::Array(show.into()))
}
}
}
pub async fn revoke_grant(
stmt: &AccessStatementRevoke,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
) -> Result<Value, Error> {
let base = match &stmt.base {
Some(base) => base.clone(),
None => opt.selected_base()?,
};
opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
let txn = ctx.tx();
txn.clear();
match base {
Base::Root => txn.get_root_access(&stmt.ac).await?,
Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.get_db_access(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
let mut revoked = Vec::new();
match &stmt.gr {
Some(gr) => {
let mut revoke = match base {
Base::Root => (*txn.get_root_access_grant(&stmt.ac, gr).await?).clone(),
Base::Ns => (*txn.get_ns_access_grant(opt.ns()?, &stmt.ac, gr).await?).clone(),
Base::Db => {
(*txn.get_db_access_grant(opt.ns()?, opt.db()?, &stmt.ac, gr).await?).clone()
}
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
if revoke.revocation.is_some() {
return Err(Error::AccessGrantRevoked);
}
revoke.revocation = Some(Datetime::default());
match base {
Base::Root => {
let key = crate::key::root::access::gr::new(&stmt.ac, gr);
txn.set(key, &revoke, None).await?;
}
Base::Ns => {
let key = crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, gr);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.set(key, &revoke, None).await?;
}
Base::Db => {
let key =
crate::key::database::access::gr::new(opt.ns()?, opt.db()?, &stmt.ac, gr);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.get_or_add_db(opt.ns()?, opt.db()?, opt.strict).await?;
txn.set(key, &revoke, None).await?;
}
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
info!(
"Access method '{}' was used to revoke grant '{}' of type '{}' for '{}' by '{}'",
revoke.ac,
revoke.id,
revoke.grant.variant(),
revoke.subject.id(),
opt.auth.id()
);
revoked.push(Value::Object(revoke.redacted().into()));
}
None => {
let grs =
match base {
Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.all_db_access_grants(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
)),
};
for gr in grs.iter() {
if gr.revocation.is_some() {
continue;
}
if let Some(cond) = &stmt.cond {
let redacted_gr = Value::Object(gr.redacted().to_owned().into());
if !cond
.compute(
stk,
ctx,
opt,
Some(&CursorDoc {
rid: None,
ir: None,
doc: redacted_gr.into(),
}),
)
.await?
.is_truthy()
{
continue;
}
}
let mut gr = gr.clone();
gr.revocation = Some(Datetime::default());
match base {
Base::Root => {
let key = crate::key::root::access::gr::new(&stmt.ac, &gr.id);
txn.set(key, &gr, None).await?;
}
Base::Ns => {
let key =
crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, &gr.id);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.set(key, &gr, None).await?;
}
Base::Db => {
let key = crate::key::database::access::gr::new(
opt.ns()?,
opt.db()?,
&stmt.ac,
&gr.id,
);
txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
txn.get_or_add_db(opt.ns()?, opt.db()?, opt.strict).await?;
txn.set(key, &gr, None).await?;
}
_ => return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
)),
};
info!(
"Access method '{}' was used to revoke grant '{}' of type '{}' for '{}' by '{}'",
gr.ac,
gr.id,
gr.grant.variant(),
gr.subject.id(),
opt.auth.id()
);
revoked.push(Value::Object(gr.redacted().into()));
}
}
}
Ok(Value::Array(revoked.into()))
}
async fn compute_revoke(
stmt: &AccessStatementRevoke,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
let revoked = revoke_grant(stmt, stk, ctx, opt).await?;
Ok(Value::Array(revoked.into()))
}
async fn compute_purge(
stmt: &AccessStatementPurge,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
let base = match &stmt.base {
Some(base) => base.clone(),
None => opt.selected_base()?,
};
opt.is_allowed(Action::Edit, ResourceKind::Access, &base)?;
let txn = ctx.tx();
txn.clear();
match base {
Base::Root => txn.get_root_access(&stmt.ac).await?,
Base::Ns => txn.get_ns_access(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.get_db_access(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
let mut purged = Array::default();
let grs = match base {
Base::Root => txn.all_root_access_grants(&stmt.ac).await?,
Base::Ns => txn.all_ns_access_grants(opt.ns()?, &stmt.ac).await?,
Base::Db => txn.all_db_access_grants(opt.ns()?, opt.db()?, &stmt.ac).await?,
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
for gr in grs.iter() {
let now = Datetime::default();
let purge_expired = stmt.expired
&& gr.expiration.as_ref().is_some_and(|exp| {
now.timestamp() >= exp.timestamp() && (now.timestamp().saturating_sub(exp.timestamp()) as u64) > stmt.grace.secs()
});
let purge_revoked = stmt.revoked
&& gr.revocation.as_ref().is_some_and(|rev| {
now.timestamp() >= rev.timestamp() && (now.timestamp().saturating_sub(rev.timestamp()) as u64) > stmt.grace.secs()
});
if purge_expired || purge_revoked {
match base {
Base::Root => txn.del(crate::key::root::access::gr::new(&stmt.ac, &gr.id)).await?,
Base::Ns => {
txn.del(crate::key::namespace::access::gr::new(opt.ns()?, &stmt.ac, &gr.id))
.await?
}
Base::Db => {
txn.del(crate::key::database::access::gr::new(
opt.ns()?,
opt.db()?,
&stmt.ac,
&gr.id,
))
.await?
}
_ => {
return Err(Error::Unimplemented(
"Managing access methods outside of root, namespace and database levels"
.to_string(),
))
}
};
info!(
"Access method '{}' was used to purge grant '{}' of type '{}' for '{}' by '{}'",
gr.ac,
gr.id,
gr.grant.variant(),
gr.subject.id(),
opt.auth.id()
);
purged = purged + Value::Object(gr.redacted().to_owned().into());
}
}
Ok(Value::Array(purged))
}
impl AccessStatement {
pub(crate) async fn compute(
&self,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
match self {
AccessStatement::Grant(stmt) => compute_grant(stmt, ctx, opt, _doc).await,
AccessStatement::Show(stmt) => compute_show(stmt, stk, ctx, opt, _doc).await,
AccessStatement::Revoke(stmt) => compute_revoke(stmt, stk, ctx, opt, _doc).await,
AccessStatement::Purge(stmt) => compute_purge(stmt, ctx, opt, _doc).await,
}
}
}
impl Display for AccessStatement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Grant(stmt) => {
write!(f, "ACCESS {}", stmt.ac)?;
if let Some(ref v) = stmt.base {
write!(f, " ON {v}")?;
}
write!(f, " GRANT")?;
match stmt.subject {
Subject::User(_) => write!(f, " FOR USER {}", stmt.subject.id())?,
Subject::Record(_) => write!(f, " FOR RECORD {}", stmt.subject.id())?,
}
Ok(())
}
Self::Show(stmt) => {
write!(f, "ACCESS {}", stmt.ac)?;
if let Some(ref v) = stmt.base {
write!(f, " ON {v}")?;
}
write!(f, " SHOW")?;
match &stmt.gr {
Some(v) => write!(f, " GRANT {v}")?,
None => match &stmt.cond {
Some(v) => write!(f, " {v}")?,
None => write!(f, " ALL")?,
},
};
Ok(())
}
Self::Revoke(stmt) => {
write!(f, "ACCESS {}", stmt.ac)?;
if let Some(ref v) = stmt.base {
write!(f, " ON {v}")?;
}
write!(f, " REVOKE")?;
match &stmt.gr {
Some(v) => write!(f, " GRANT {v}")?,
None => match &stmt.cond {
Some(v) => write!(f, " {v}")?,
None => write!(f, " ALL")?,
},
};
Ok(())
}
Self::Purge(stmt) => {
write!(f, "ACCESS {}", stmt.ac)?;
if let Some(ref v) = stmt.base {
write!(f, " ON {v}")?;
}
write!(f, " PURGE")?;
match (stmt.expired, stmt.revoked) {
(true, false) => write!(f, " EXPIRED")?,
(false, true) => write!(f, " REVOKED")?,
(true, true) => write!(f, " EXPIRED, REVOKED")?,
(false, false) => write!(f, " NONE")?,
};
if !stmt.grace.is_zero() {
write!(f, " FOR {}", stmt.grace)?;
}
Ok(())
}
}
}
}