use anyhow::Result;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use super::{Action, Actor, Level, Resource, Role, is_allowed};
use crate::iam::AuthLimit;
#[revisioned(revision = 1)]
#[derive(Clone, Default, Debug, Eq, PartialEq, PartialOrd, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Auth {
actor: Actor,
}
impl Auth {
pub fn new(actor: Actor) -> Self {
Self {
actor,
}
}
pub fn id(&self) -> &str {
self.actor.id()
}
pub fn level(&self) -> &Level {
self.actor.level()
}
pub fn is_anon(&self) -> bool {
matches!(self.level(), Level::No)
}
pub fn is_root(&self) -> bool {
matches!(self.level(), Level::Root)
}
pub fn is_ns(&self) -> bool {
matches!(self.level(), Level::Namespace(_))
}
pub fn is_db(&self) -> bool {
matches!(self.level(), Level::Database(_, _))
}
pub fn is_record(&self) -> bool {
matches!(self.level(), Level::Record(_, _, _))
}
pub fn is_ns_check(&self, ns: &str) -> bool {
matches!(self.level(), Level::Namespace(n) if n.eq(ns))
}
pub fn is_db_check(&self, ns: &str, db: &str) -> bool {
matches!(self.level(), Level::Database(n, d) if n.eq(ns) && d.eq(db))
}
pub fn can_access_ns_db(&self, ns: &str, db: &str) -> bool {
match self.level() {
Level::Root => true,
Level::Namespace(n) => n.eq(ns),
Level::Database(n, d) => n.eq(ns) && d.eq(db),
Level::Record(n, d, _) => n.eq(ns) && d.eq(db),
Level::No => true,
}
}
pub fn for_root(role: Role) -> Self {
Self::new(Actor::new("system_auth".into(), vec![role], Level::Root))
}
pub fn for_ns(role: Role, ns: &str) -> Self {
Self::new(Actor::new("system_auth".into(), vec![role], Level::Namespace(ns.to_owned())))
}
pub fn for_db(role: Role, ns: &str, db: &str) -> Self {
Self::new(Actor::new(
"system_auth".into(),
vec![role],
Level::Database(ns.to_owned(), db.to_owned()),
))
}
pub fn for_record(rid: String, ns: &str, db: &str, ac: &str) -> Self {
Self::new(Actor::new(
rid,
vec![],
Level::Record(ns.to_owned(), db.to_owned(), ac.to_owned()),
))
}
pub fn new_limited(&self, limit: &AuthLimit) -> Self {
Self::new(self.actor.new_limited(limit))
}
pub fn max_role(&self) -> Option<Role> {
self.actor.max_role()
}
pub fn is_allowed(&self, action: Action, res: &Resource) -> Result<()> {
is_allowed(&self.actor, &action, res)
.map_err(crate::err::Error::from)
.map_err(anyhow::Error::new)
}
pub fn has_role(&self, role: Role) -> bool {
self.actor.has_role(role)
}
pub fn has_owner_role(&self) -> bool {
self.actor.has_owner_role()
}
pub fn has_editor_role(&self) -> bool {
self.actor.has_editor_role()
}
pub fn has_viewer_role(&self) -> bool {
self.actor.has_viewer_role()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_access_ns_db_enforces_tenant_boundary() {
let root = Auth::for_root(Role::Viewer);
assert!(root.can_access_ns_db("a", "x"));
assert!(root.can_access_ns_db("b", "y"));
let ns = Auth::for_ns(Role::Viewer, "a");
assert!(ns.can_access_ns_db("a", "x"));
assert!(ns.can_access_ns_db("a", "y"));
assert!(!ns.can_access_ns_db("b", "x"));
let db = Auth::for_db(Role::Viewer, "a", "x");
assert!(db.can_access_ns_db("a", "x"));
assert!(!db.can_access_ns_db("a", "y"));
assert!(!db.can_access_ns_db("b", "x"));
let rec = Auth::for_record("user:1".to_string(), "a", "x", "ac");
assert!(rec.can_access_ns_db("a", "x"));
assert!(!rec.can_access_ns_db("a", "y"));
assert!(!rec.can_access_ns_db("b", "x"));
assert!(Auth::default().can_access_ns_db("a", "x"));
}
}