use std::collections::BTreeSet;
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use crate::core::Capability;
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Scope(BTreeSet<String>);
impl Scope {
#[must_use]
pub fn root() -> Self {
Self(BTreeSet::from(["*".to_owned()]))
}
#[must_use]
pub fn empty() -> Self {
Self(BTreeSet::new())
}
pub fn of<I, S>(patterns: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self(patterns.into_iter().map(Into::into).collect())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn patterns(&self) -> impl Iterator<Item = &str> {
self.0.iter().map(String::as_str)
}
fn pattern_covers(pattern: &str, capability: &str) -> bool {
if pattern == "*" {
return true;
}
let Some(prefix) = pattern.strip_suffix(".*") else {
return pattern == capability;
};
capability == prefix
|| (capability.starts_with(prefix)
&& capability.as_bytes().get(prefix.len()) == Some(&b'.'))
}
#[must_use]
pub fn permits(&self, capability: &Capability) -> bool {
self.0
.iter()
.any(|p| Self::pattern_covers(p, &capability.0))
}
#[must_use]
pub fn contains(&self, other: &Self) -> bool {
other.0.iter().all(|o| self.0.iter().any(|s| covers(s, o)))
}
}
fn covers(a: &str, b: &str) -> bool {
if a == "*" {
return true;
}
if b == "*" {
return false;
}
match (a.strip_suffix(".*"), b.strip_suffix(".*")) {
(Some(pa), Some(pb)) => pb == pa || (pb.starts_with(pa) && pb.as_bytes()[pa.len()] == b'.'),
(Some(_), None) => Scope::pattern_covers(a, b),
(None, Some(_)) => false,
(None, None) => a == b,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Principal {
pub id: String,
pub scope: Scope,
}
impl Principal {
pub fn new(id: impl Into<String>, scope: Scope) -> Self {
Self {
id: id.into(),
scope,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum DelegationError {
#[error(
"'{to}' would hold authority '{widened}' that its delegator '{from}' does not — \
delegation may only narrow"
)]
ScopeWidened {
from: String,
to: String,
widened: String,
},
#[error("delegation depth {depth} exceeds the limit of {max}")]
TooDeep { depth: usize, max: usize },
#[error("delegation chain is empty: there is no principal to act as")]
Empty,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "DelegationWire", try_from = "DelegationWire")]
pub struct Delegation {
root: Principal,
rest: Vec<Principal>,
}
#[derive(Serialize, Deserialize)]
struct DelegationWire {
links: Vec<Principal>,
}
impl From<Delegation> for DelegationWire {
fn from(chain: Delegation) -> Self {
Self {
links: chain.links().cloned().collect(),
}
}
}
impl TryFrom<DelegationWire> for Delegation {
type Error = DelegationError;
fn try_from(wire: DelegationWire) -> Result<Self, Self::Error> {
Self::rehydrate(wire.links)
}
}
pub const MAX_DELEGATION_DEPTH: usize = 3;
impl Delegation {
#[must_use]
pub fn root(owner: Principal) -> Self {
Self {
root: owner,
rest: Vec::new(),
}
}
pub fn delegate(&self, to: Principal) -> Result<Self, DelegationError> {
let from = self.subject();
if !from.scope.contains(&to.scope) {
let widened = to
.scope
.patterns()
.find(|p| !from.scope.contains(&Scope::of([*p])))
.unwrap_or("<unknown>")
.to_owned();
return Err(DelegationError::ScopeWidened {
from: from.id.clone(),
to: to.id,
widened,
});
}
if self.depth() + 1 > MAX_DELEGATION_DEPTH {
return Err(DelegationError::TooDeep {
depth: self.depth() + 1,
max: MAX_DELEGATION_DEPTH,
});
}
let mut next = self.clone();
next.rest.push(to);
Ok(next)
}
#[must_use]
pub const fn owner(&self) -> &Principal {
&self.root
}
#[must_use]
pub fn subject(&self) -> &Principal {
self.rest.last().unwrap_or(&self.root)
}
#[must_use]
pub const fn depth(&self) -> usize {
self.rest.len()
}
#[must_use]
pub fn effective_scope(&self) -> &Scope {
&self.subject().scope
}
pub fn links(&self) -> impl Iterator<Item = &Principal> {
std::iter::once(&self.root).chain(self.rest.iter())
}
pub fn rehydrate(links: Vec<Principal>) -> Result<Self, DelegationError> {
let mut it = links.into_iter();
let root = it.next().ok_or(DelegationError::Empty)?;
let mut chain = Self::root(root);
for link in it {
chain = chain.delegate(link)?;
}
Ok(chain)
}
}
impl Delegation {
#[must_use]
pub fn as_context(&self) -> serde_json::Value {
serde_json::json!({
"owner": self.owner().id,
"subject": self.subject().id,
"delegation_depth": self.depth(),
"scope": self.effective_scope().patterns().collect::<Vec<_>>(),
})
}
}
pub trait DelegationScheme: Send + Sync + Debug {
fn verify(&self, credential: &str) -> Result<Delegation, DelegationError>;
}