use std::collections::{HashMap, HashSet};
use anyhow::Result;
use ed25519_dalek::SignatureError;
use meadowcap::{IsCommunal, NamespaceIsNotCommunalError, OwnedCapabilityCreationError};
use tracing::{debug, trace};
use crate::{
interest::{
AreaOfInterestSelector, CapSelector, CapabilityPack, DelegateTo, InterestMap, Interests,
InvalidCapabilityPack, RestrictArea,
},
proto::{
data_model::WriteCapability,
grouping::AreaOfInterest,
keys::{NamespaceId, UserId},
meadowcap::{
AccessMode, FailedDelegationError, McCapability, McSubspaceCapability,
ReadAuthorisation,
},
},
store::traits::{CapsStorage, SecretStorage, SecretStoreError, Storage},
};
#[derive(Debug, Clone)]
pub struct Auth<S: Storage> {
secrets: S::Secrets,
caps: S::Caps,
}
impl<S: Storage> Auth<S> {
pub fn new(secrets: S::Secrets, caps: S::Caps) -> Self {
Self { secrets, caps }
}
pub fn get_write_cap(
&self,
selector: &CapSelector,
) -> Result<Option<WriteCapability>, AuthError> {
let cap = self.caps.get_write_cap(selector)?;
Ok(cap)
}
pub fn get_read_cap(
&self,
selector: &CapSelector,
) -> Result<Option<ReadAuthorisation>, AuthError> {
let cap = self.caps.get_read_cap(selector)?;
Ok(cap)
}
pub fn list_read_caps(&self) -> Result<impl Iterator<Item = ReadAuthorisation> + '_> {
self.caps.list_read_caps(None)
}
pub fn import_caps(
&self,
caps: impl IntoIterator<Item = CapabilityPack>,
) -> Result<(), AuthError> {
for cap in caps.into_iter() {
debug!(?cap, "import cap");
cap.validate()?;
let user_id = cap.receiver();
if !self.secrets.has_user(&user_id)? {
return Err(AuthError::MissingUserSecret(user_id));
}
self.caps.insert(cap)?;
trace!("imported");
}
Ok(())
}
pub fn insert_caps_unchecked(
&self,
caps: impl IntoIterator<Item = CapabilityPack>,
) -> Result<(), AuthError> {
for cap in caps.into_iter() {
debug!(?cap, "insert cap");
self.caps.insert(cap)?;
}
Ok(())
}
pub fn resolve_interests(&self, interests: Interests) -> Result<InterestMap, AuthError> {
match interests {
Interests::All => {
let out = self
.list_read_caps()?
.map(|auth| {
let area = auth.read_cap().granted_area();
let aoi = AreaOfInterest::new(area, 0, 0);
(auth, HashSet::from_iter([aoi]))
})
.collect::<HashMap<_, _>>();
Ok(out)
}
Interests::Select(interests) => {
let mut out: InterestMap = HashMap::new();
for (cap_selector, aoi_selector) in interests {
let cap = self.get_read_cap(&cap_selector)?;
if let Some(cap) = cap {
let entry = out.entry(cap.clone()).or_default();
match aoi_selector {
AreaOfInterestSelector::Widest => {
let area = cap.read_cap().granted_area();
let aoi = AreaOfInterest::new(area, 0, 0);
entry.insert(aoi);
}
AreaOfInterestSelector::Exact(aois) => {
for aoi in aois {
entry.insert(aoi);
}
}
}
}
}
Ok(out)
} }
}
pub fn create_full_caps(
&self,
namespace_id: NamespaceId,
user_id: UserId,
) -> Result<[CapabilityPack; 2], AuthError> {
let read_cap = self.create_read_cap(namespace_id, user_id)?;
let write_cap = self.create_write_cap(namespace_id, user_id)?;
let pack = [read_cap, write_cap];
self.insert_caps_unchecked(pack.clone())?;
Ok(pack)
}
pub fn create_read_cap(
&self,
namespace_key: NamespaceId,
user_key: UserId,
) -> Result<CapabilityPack, AuthError> {
let cap = if namespace_key.is_communal() {
let read_cap = McCapability::new_communal(namespace_key, user_key, AccessMode::Read)?;
ReadAuthorisation::new(read_cap, None)
} else {
let namespace_secret = self
.secrets
.get_namespace(&namespace_key)?
.ok_or(AuthError::MissingNamespaceSecret(namespace_key))?;
let read_cap = McCapability::new_owned(
namespace_key,
&namespace_secret,
user_key,
AccessMode::Read,
)?;
let subspace_cap =
McSubspaceCapability::new(namespace_key, &namespace_secret, user_key)
.map_err(AuthError::SubspaceCapDelegationFailed)?;
ReadAuthorisation::new(read_cap, Some(subspace_cap))
};
let pack = CapabilityPack::Read(cap);
Ok(pack)
}
pub fn create_write_cap(
&self,
namespace_key: NamespaceId,
user_key: UserId,
) -> Result<CapabilityPack, AuthError> {
let cap = if namespace_key.is_communal() {
McCapability::new_communal(namespace_key, user_key, AccessMode::Write)?
} else {
let namespace_secret = self
.secrets
.get_namespace(&namespace_key)?
.ok_or(AuthError::MissingNamespaceSecret(namespace_key))?;
McCapability::new_owned(
namespace_key,
&namespace_secret,
user_key,
AccessMode::Write,
)?
};
let pack = CapabilityPack::Write(cap);
Ok(pack)
}
pub fn delegate_full_caps(
&self,
from: CapSelector,
access_mode: AccessMode,
to: DelegateTo,
store: bool,
) -> Result<Vec<CapabilityPack>, AuthError> {
let mut out = Vec::with_capacity(2);
let restrict_area = to.restrict_area;
let read_cap = self.delegate_read_cap(&from, to.user, restrict_area.clone())?;
out.push(read_cap);
if access_mode == AccessMode::Write {
let write_cap = self.delegate_write_cap(&from, to.user, restrict_area)?;
out.push(write_cap);
}
if store {
self.insert_caps_unchecked(out.clone())?;
}
Ok(out)
}
pub fn delegate_read_cap(
&self,
from: &CapSelector,
to: UserId,
restrict_area: RestrictArea,
) -> Result<CapabilityPack, AuthError> {
let auth = self.get_read_cap(from)?.ok_or(AuthError::NoCapability)?;
let read_cap = auth.read_cap();
let subspace_cap = auth.subspace_cap();
let user_id = read_cap.receiver();
let user_secret = self
.secrets
.get_user(user_id)?
.ok_or(AuthError::MissingUserSecret(*user_id))?;
let area = restrict_area.or_default(read_cap.granted_area());
let new_read_cap = read_cap.delegate(&user_secret, &to, &area)?;
let new_subspace_cap = if let Some(subspace_cap) = subspace_cap {
if area.subspace().is_any() {
Some(
subspace_cap
.delegate(&user_secret, &to)
.map_err(AuthError::SubspaceCapDelegationFailed)?,
)
} else {
None
}
} else {
None
};
let pack = CapabilityPack::Read(ReadAuthorisation::new(new_read_cap, new_subspace_cap));
Ok(pack)
}
pub fn delegate_write_cap(
&self,
from: &CapSelector,
to: UserId,
restrict_area: RestrictArea,
) -> Result<CapabilityPack, AuthError> {
let cap = self.get_write_cap(from)?.ok_or(AuthError::NoCapability)?;
let user_secret = self
.secrets
.get_user(cap.receiver())?
.ok_or(AuthError::MissingUserSecret(*cap.receiver()))?;
let area = restrict_area.or_default(cap.granted_area());
let new_cap = cap.delegate(&user_secret, &to, &area)?;
Ok(CapabilityPack::Write(new_cap))
}
}
#[derive(thiserror::Error, Debug)]
pub enum AuthError {
#[error("invalid user id: {}", .0.fmt_short())]
InvalidUserId(UserId),
#[error("invalid namespace id: {}", .0.fmt_short())]
InvalidNamespaceId(NamespaceId),
#[error("missing user secret: {}", .0.fmt_short())]
MissingUserSecret(UserId),
#[error("missing namespace secret: {}", .0.fmt_short())]
MissingNamespaceSecret(NamespaceId),
#[error("secret store error: {0}")]
SecretStore(#[from] SecretStoreError),
#[error("no capability found")]
NoCapability,
#[error("{0}")]
Other(#[from] anyhow::Error),
#[error("Invalid capability pack")]
InvalidPack(#[from] InvalidCapabilityPack),
#[error("Failed to create owned capability: {0}")]
CreateOwnedCap(#[from] OwnedCapabilityCreationError<NamespaceId>),
#[error("Failed to create communal capability: {0}")]
CreateCommunalCap(#[from] NamespaceIsNotCommunalError<NamespaceId>),
#[error("Failed to delegate capability: {0}")]
DelegationFailed(#[from] FailedDelegationError),
#[error("Failed to delegate suubspace capability: {0}")]
SubspaceCapDelegationFailed(SignatureError),
}