use std::collections::{BTreeMap, BTreeSet};
use std::convert::Infallible;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::num::NonZeroUsize;
use std::sync::Arc;
use ed25519_dalek::{SigningKey, VerifyingKey};
use crate::blob::encodings::simplearchive::SimpleArchive;
use crate::blob::Blob;
use crate::capability::{
capability_quorum_authorized_subjects, capability_quorum_decide, CapabilityHandle,
CapabilityProof, CapabilityRequest, CapabilityResource, QuorumOutcome,
};
use crate::id::Id;
use crate::inline::encodings::hash::Handle;
use crate::inline::{Inline, InlineEncoding};
use crate::patch::{Blake3Merkle, IdentitySchema, PATCH};
use crate::repo::async_store::AsyncBlobStoreAcquire;
use crate::repo::{BlobStoreGet, BlobStorePut, CapabilityProofRead};
use crate::repo::{CapabilityProofStore, SnapshotSource, Store, StoreRead, StoreSnapshot};
use crate::trible::{Fragment, TribleSet};
use super::exact_derived::CollectionRealizationError;
use super::simplearchive_union::PreparedCollectionCommit;
use super::{
descriptor, read_capability, write_capability, Collection, CollectionCommit, CollectionData,
CollectionEncoding, CollectionHandle, CollectionSnapshot, CollectionStore, CollectionTypeError,
RecordDecodeError, TryFromCover, TryFromCoverError,
};
use super::{
AdmissionPolicy, AttachedSnapshot, CanonicalAttachment, CanonicalDerivation,
CollectionAttachment, CollectionDerivation, CollectionPolicy, DeriveMapping, MapMapping,
};
#[derive(Debug)]
pub enum CollectionEvidenceDiscoveryError<ProofsError> {
Proofs(ProofsError),
}
impl<ProofsError> fmt::Display for CollectionEvidenceDiscoveryError<ProofsError>
where
ProofsError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Proofs(source) => {
write!(
formatter,
"failed to enumerate resident capability proofs: {source}"
)
}
}
}
}
impl<ProofsError> Error for CollectionEvidenceDiscoveryError<ProofsError>
where
ProofsError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Proofs(source) => Some(source),
}
}
}
#[derive(Debug)]
pub enum CollectionDescriptorError<GetError> {
Get {
collection: CollectionHandle,
source: GetError,
},
Invalid {
collection: CollectionHandle,
source: RecordDecodeError,
},
}
impl<GetError> fmt::Display for CollectionDescriptorError<GetError>
where
GetError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Get { collection, source } => write!(
formatter,
"failed to fetch collection descriptor {}: {source}",
hex::encode_upper(collection.raw),
),
Self::Invalid { collection, source } => write!(
formatter,
"collection descriptor {} is invalid: {source}",
hex::encode_upper(collection.raw),
),
}
}
}
impl<GetError> Error for CollectionDescriptorError<GetError>
where
GetError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Get { source, .. } => Some(source),
Self::Invalid { source, .. } => Some(source),
}
}
}
#[derive(Debug)]
pub enum CollectionOpenError<GetError> {
Descriptor(CollectionDescriptorError<GetError>),
WrongType(CollectionTypeError),
}
impl<GetError> fmt::Display for CollectionOpenError<GetError>
where
GetError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Descriptor(source) => source.fmt(formatter),
Self::WrongType(source) => write!(formatter, "wrong collection type: {source}"),
}
}
}
impl<GetError> Error for CollectionOpenError<GetError>
where
GetError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Descriptor(source) => Some(source),
Self::WrongType(source) => Some(source),
}
}
}
#[derive(Debug)]
pub enum CollectionGrantError<SnapshotError, GetError, InsertError> {
Snapshot(SnapshotError),
Descriptor(CollectionDescriptorError<GetError>),
OpenPolicy {
capability: CapabilityHandle,
collection: CollectionHandle,
},
RootNotAuthorized {
capability: CapabilityHandle,
collection: CollectionHandle,
root: VerifyingKey,
},
ProofInsert(InsertError),
}
impl<SnapshotError, GetError, InsertError> fmt::Display
for CollectionGrantError<SnapshotError, GetError, InsertError>
where
SnapshotError: fmt::Display,
GetError: fmt::Display,
InsertError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Snapshot(source) => {
write!(formatter, "failed to freeze store snapshot: {source}")
}
Self::Descriptor(source) => source.fmt(formatter),
Self::OpenPolicy {
capability,
collection,
} => write!(
formatter,
"collection {} has an open {} policy; no proof is required",
hex::encode_upper(collection.raw),
collection_capability_label(*capability),
),
Self::RootNotAuthorized {
capability,
collection,
root,
} => write!(
formatter,
"key {} is not a {} root of collection {}",
hex::encode_upper(root.to_bytes()),
collection_capability_label(*capability),
hex::encode_upper(collection.raw),
),
Self::ProofInsert(source) => {
write!(formatter, "failed to insert capability proof: {source}")
}
}
}
}
impl<SnapshotError, GetError, InsertError> Error
for CollectionGrantError<SnapshotError, GetError, InsertError>
where
SnapshotError: Error + 'static,
GetError: Error + 'static,
InsertError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Snapshot(source) => Some(source),
Self::Descriptor(source) => Some(source),
Self::OpenPolicy { .. } | Self::RootNotAuthorized { .. } => None,
Self::ProofInsert(source) => Some(source),
}
}
}
pub type CollectionReadGrantError<SnapshotError, GetError, InsertError> =
CollectionGrantError<SnapshotError, GetError, InsertError>;
pub type CollectionWriteGrantError<SnapshotError, GetError, InsertError> =
CollectionGrantError<SnapshotError, GetError, InsertError>;
fn collection_capability_label(capability: CapabilityHandle) -> String {
if capability == read_capability() {
"READ".to_owned()
} else if capability == write_capability() {
"WRITE".to_owned()
} else {
hex::encode_upper(capability.raw)
}
}
#[derive(Debug)]
pub enum CollectionAdmissionError<ProofsError, GetError> {
Descriptor(CollectionDescriptorError<GetError>),
Evidence(CollectionEvidenceDiscoveryError<ProofsError>),
Definition(crate::repo::ReadFailure),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CollectionReadAudience {
Open,
Restricted(Vec<VerifyingKey>),
}
impl<ProofsError, GetError> fmt::Display for CollectionAdmissionError<ProofsError, GetError>
where
ProofsError: fmt::Display,
GetError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Descriptor(source) => source.fmt(formatter),
Self::Evidence(source) => source.fmt(formatter),
Self::Definition(source) => source.fmt(formatter),
}
}
}
impl<ProofsError, GetError> Error for CollectionAdmissionError<ProofsError, GetError>
where
ProofsError: Error + 'static,
GetError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Descriptor(source) => Some(source),
Self::Evidence(source) => Some(source),
Self::Definition(source) => Some(source),
}
}
}
#[derive(Debug)]
pub enum CollectionRegistrationError<PutError> {
WrongType(CollectionTypeError),
DependencyPut(PutError),
}
impl<PutError> fmt::Display for CollectionRegistrationError<PutError>
where
PutError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WrongType(source) => write!(formatter, "wrong collection type: {source}"),
Self::DependencyPut(source) => {
write!(
formatter,
"failed to store collection descriptor closure: {source}"
)
}
}
}
}
impl<PutError> Error for CollectionRegistrationError<PutError>
where
PutError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::WrongType(source) => Some(source),
Self::DependencyPut(source) => Some(source),
}
}
}
pub struct Cover<L: CollectionEncoding> {
collection: Collection<L>,
members: PATCH<32, IdentitySchema, (), Blake3Merkle>,
}
impl<L: CollectionEncoding> Cover<L> {
pub(crate) fn from_members(
collection: Collection<L>,
members: impl IntoIterator<Item = Inline<Handle<L>>>,
) -> Self {
Self {
collection,
members: PATCH::from_keys(members.into_iter().map(|member| member.raw)),
}
}
pub(crate) fn from_data(
collection: Collection<L>,
members: impl IntoIterator<Item = CollectionData>,
) -> Self {
Self {
collection,
members: PATCH::from_keys(members.into_iter().map(|member| member.raw)),
}
}
pub(crate) fn from_patch(
collection: Collection<L>,
members: PATCH<32, IdentitySchema, (), Blake3Merkle>,
) -> Self {
Self {
collection,
members,
}
}
pub const fn collection(&self) -> Collection<L> {
self.collection
}
pub fn members(&self) -> impl ExactSizeIterator<Item = Inline<Handle<L>>> + '_ {
self.members
.iter_ordered()
.map(|member| Inline::new(*member))
}
pub fn contains(&self, member: Inline<Handle<L>>) -> bool {
self.members.get(&member.raw).is_some()
}
pub(crate) fn data_members(&self) -> impl ExactSizeIterator<Item = CollectionData> + '_ {
self.members
.iter_ordered()
.map(|member| Inline::new(*member))
}
pub fn len(&self) -> usize {
self.members.len().min(usize::MAX as u64) as usize
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
fn ensure_same_collection(&self, other: &Self) -> Result<(), CoverAlgebraError> {
if self.collection == other.collection {
Ok(())
} else {
Err(CoverAlgebraError::DifferentCollection {
left: self.collection.handle(),
right: other.collection.handle(),
})
}
}
pub fn union(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
self.ensure_same_collection(other)?;
let mut members = self.members.clone();
members.union(other.members.clone());
Ok(Self::from_patch(self.collection, members))
}
pub fn intersection(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
self.ensure_same_collection(other)?;
Ok(Self::from_patch(
self.collection,
self.members.intersect(&other.members),
))
}
pub fn difference(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
self.ensure_same_collection(other)?;
Ok(Self::from_patch(
self.collection,
self.members.difference(&other.members),
))
}
pub fn is_subset(&self, other: &Self) -> Result<bool, CoverAlgebraError> {
self.ensure_same_collection(other)?;
Ok(self.members <= other.members)
}
pub fn additions_since(&self, previous: &Self) -> Result<Self, CoverAdvanceError> {
if self.collection != previous.collection {
return Err(CoverAdvanceError::DifferentCollection {
previous: previous.collection.handle(),
current: self.collection.handle(),
});
}
let missing = previous.members.difference(&self.members);
if let Some(member) = missing.iter_ordered().next() {
return Err(CoverAdvanceError::ResetRequired {
missing: Inline::new(*member),
});
}
Ok(Self::from_patch(
self.collection,
self.members.difference(&previous.members),
))
}
}
impl<L: CollectionEncoding> Clone for Cover<L> {
fn clone(&self) -> Self {
Self {
collection: self.collection,
members: self.members.clone(),
}
}
}
impl<L: CollectionEncoding> fmt::Debug for Cover<L> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Cover")
.field("collection", &self.collection)
.field("members", &self.data_members().collect::<Vec<_>>())
.finish()
}
}
impl<L: CollectionEncoding> PartialEq for Cover<L> {
fn eq(&self, other: &Self) -> bool {
self.collection == other.collection && self.members == other.members
}
}
impl<L: CollectionEncoding> Eq for Cover<L> {}
pub type Support<E = SimpleArchive> = Cover<E>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CoverAlgebraError {
DifferentCollection {
left: CollectionHandle,
right: CollectionHandle,
},
}
impl fmt::Display for CoverAlgebraError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DifferentCollection { left, right } => write!(
formatter,
"cover collection {} differs from {}",
hex::encode_upper(left.raw),
hex::encode_upper(right.raw),
),
}
}
}
impl Error for CoverAlgebraError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CoverAdvanceError {
DifferentCollection {
previous: CollectionHandle,
current: CollectionHandle,
},
ResetRequired {
missing: CollectionData,
},
}
impl fmt::Display for CoverAdvanceError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DifferentCollection { previous, current } => write!(
formatter,
"cover collection {} differs from {}",
hex::encode_upper(current.raw),
hex::encode_upper(previous.raw),
),
Self::ResetRequired { missing } => write!(
formatter,
"previous cover member {} is absent from the current observation; additions-only processing requires a reset",
hex::encode_upper(missing.raw),
),
}
}
}
impl Error for CoverAdvanceError {}
#[derive(Debug)]
pub enum CollectionCommitError<PutError, InsertError> {
DependencyPut(PutError),
RecordInsert(InsertError),
}
impl<PutError, InsertError> fmt::Display for CollectionCommitError<PutError, InsertError>
where
PutError: fmt::Display,
InsertError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DependencyPut(source) => {
write!(formatter, "failed to store collection dependency: {source}")
}
Self::RecordInsert(source) => {
write!(formatter, "failed to insert collection commit: {source}")
}
}
}
}
impl<PutError, InsertError> Error for CollectionCommitError<PutError, InsertError>
where
PutError: Error + 'static,
InsertError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::DependencyPut(source) => Some(source),
Self::RecordInsert(source) => Some(source),
}
}
}
#[derive(Debug)]
pub enum CollectionReadError<GetError, ViewError> {
Realization(CollectionRealizationError),
View(TryFromCoverError<GetError, ViewError>),
}
impl<GetError, ViewError> fmt::Display for CollectionReadError<GetError, ViewError>
where
GetError: fmt::Display,
ViewError: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Realization(source) => source.fmt(formatter),
Self::View(source) => source.fmt(formatter),
}
}
}
impl<GetError, ViewError> Error for CollectionReadError<GetError, ViewError>
where
GetError: Error + 'static,
ViewError: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Realization(source) => Some(source),
Self::View(source) => Some(source),
}
}
}
pub(crate) struct LoadedCollectionDescriptor {
pub(crate) fragment: Fragment,
}
pub(crate) fn load_collection_descriptor<R>(
snapshot: &R,
collection: CollectionHandle,
) -> Result<LoadedCollectionDescriptor, CollectionDescriptorError<R::GetError<Infallible>>>
where
R: BlobStoreGet,
{
let descriptor_blob: Blob<SimpleArchive> = snapshot
.get(collection)
.map_err(|source| CollectionDescriptorError::Get { collection, source })?;
let facts =
<TribleSet as crate::blob::TryFromBlob<SimpleArchive>>::try_from_blob(descriptor_blob)
.map_err(|source| CollectionDescriptorError::Invalid {
collection,
source: RecordDecodeError::from(source),
})?;
Ok(LoadedCollectionDescriptor {
fragment: Fragment::from(facts),
})
}
pub(crate) enum AdmissionEvidence {
Open,
Alternatives(Vec<AdmissionEvidence>),
Quorum {
roots: Vec<VerifyingKey>,
invoke_threshold: NonZeroUsize,
request: CapabilityRequest,
proofs: Arc<[CapabilityProof]>,
},
}
impl AdmissionEvidence {
pub(crate) fn authorizes<R: BlobStoreGet>(&self, reader: &R, subject: VerifyingKey) -> bool {
matches!(self.decide(reader, subject), QuorumOutcome::Met)
}
pub(crate) fn decide<R: BlobStoreGet>(
&self,
reader: &R,
subject: VerifyingKey,
) -> QuorumOutcome {
match self {
Self::Open => QuorumOutcome::Met,
Self::Alternatives(alternatives) => {
let mut undefined = Vec::new();
for evidence in alternatives {
match evidence.decide(reader, subject) {
QuorumOutcome::Met => return QuorumOutcome::Met,
QuorumOutcome::Unmet => {}
QuorumOutcome::Undefined(handles) => undefined.extend(handles),
}
}
if undefined.is_empty() {
QuorumOutcome::Unmet
} else {
QuorumOutcome::Undefined(undefined)
}
}
Self::Quorum {
roots,
invoke_threshold,
request,
proofs,
} => capability_quorum_decide(
reader,
proofs.iter(),
roots.iter().copied(),
subject,
*request,
*invoke_threshold,
),
}
}
fn authorized_subjects<R: BlobStoreGet>(&self, reader: &R) -> CollectionReadAudience {
match self {
Self::Open => CollectionReadAudience::Open,
Self::Alternatives(alternatives) => {
let mut subjects = BTreeMap::new();
for alternative in alternatives {
match alternative.authorized_subjects(reader) {
CollectionReadAudience::Open => return CollectionReadAudience::Open,
CollectionReadAudience::Restricted(audience) => {
subjects.extend(
audience
.into_iter()
.map(|subject| (subject.to_bytes(), subject)),
);
}
}
}
CollectionReadAudience::Restricted(subjects.into_values().collect())
}
Self::Quorum {
roots,
invoke_threshold,
request,
proofs,
} => CollectionReadAudience::Restricted(capability_quorum_authorized_subjects(
reader,
proofs.iter(),
roots.iter().copied(),
*request,
*invoke_threshold,
)),
}
}
}
pub(crate) fn admission_evidence_from_proofs(
policy: &AdmissionPolicy,
action: Id,
collection: CollectionHandle,
proofs: Arc<[CapabilityProof]>,
) -> AdmissionEvidence {
let AdmissionPolicy::Quorum(quorum) = policy else {
return AdmissionEvidence::Open;
};
let request = CapabilityRequest::new(CapabilityResource::from(collection), action);
AdmissionEvidence::Quorum {
roots: quorum.roots().to_vec(),
invoke_threshold: NonZeroUsize::new(quorum.invoke_threshold() as usize)
.expect("validated collection policy has a nonzero invoke threshold"),
request,
proofs,
}
}
pub(super) fn discover_admission_evidence<S>(
snapshot: &S,
policies: impl IntoIterator<Item = AdmissionPolicy>,
action: Id,
collection: CollectionHandle,
) -> Result<AdmissionEvidence, CollectionEvidenceDiscoveryError<S::ProofsError>>
where
S: CapabilityProofRead,
{
let policies: Vec<_> = policies.into_iter().collect();
if policies
.iter()
.any(|policy| matches!(policy, AdmissionPolicy::Open))
{
return Ok(AdmissionEvidence::Open);
}
if policies.is_empty() {
return Ok(AdmissionEvidence::Alternatives(Vec::new()));
}
let proofs = snapshot
.proofs()
.map_err(CollectionEvidenceDiscoveryError::Proofs)?
.collect::<Result<Vec<_>, _>>()
.map_err(CollectionEvidenceDiscoveryError::Proofs)?;
let proofs: Arc<[CapabilityProof]> = proofs.into();
Ok(AdmissionEvidence::Alternatives(
policies
.iter()
.map(|policy| {
admission_evidence_from_proofs(policy, action, collection, Arc::clone(&proofs))
})
.collect(),
))
}
pub fn grant_collection_read<S>(
store: &mut S,
collection: CollectionHandle,
root: &SigningKey,
recipient: VerifyingKey,
) -> Result<
CapabilityProof,
CollectionReadGrantError<
<S as SnapshotSource>::SnapshotError,
<<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
<S as CapabilityProofStore>::InsertError,
>,
>
where
S: SnapshotSource + CapabilityProofStore,
<S as SnapshotSource>::Snapshot: BlobStoreGet,
{
grant_collection_capability(store, collection, read_capability(), root, recipient)
}
pub fn grant_collection_write<S>(
store: &mut S,
collection: CollectionHandle,
root: &SigningKey,
recipient: VerifyingKey,
) -> Result<
CapabilityProof,
CollectionWriteGrantError<
<S as SnapshotSource>::SnapshotError,
<<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
<S as CapabilityProofStore>::InsertError,
>,
>
where
S: SnapshotSource + CapabilityProofStore,
<S as SnapshotSource>::Snapshot: BlobStoreGet,
{
grant_collection_capability(store, collection, write_capability(), root, recipient)
}
pub fn grant_collection_capability<S>(
store: &mut S,
collection: CollectionHandle,
capability: CapabilityHandle,
root: &SigningKey,
recipient: VerifyingKey,
) -> Result<
CapabilityProof,
CollectionGrantError<
<S as SnapshotSource>::SnapshotError,
<<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
<S as CapabilityProofStore>::InsertError,
>,
>
where
S: SnapshotSource + CapabilityProofStore,
<S as SnapshotSource>::Snapshot: BlobStoreGet,
{
let snapshot = store.snapshot().map_err(CollectionGrantError::Snapshot)?;
let descriptor = load_collection_descriptor(&snapshot, collection)
.map_err(CollectionGrantError::Descriptor)?;
let definition = load_collection_descriptor(&snapshot, capability)
.map_err(CollectionGrantError::Descriptor)?;
let actions = crate::prelude::find!(
action: Id,
crate::prelude::temp!(
(grant),
crate::prelude::or!(
crate::prelude::pattern!(definition.fragment.facts(), [{ ?grant @ crate::capability::capability_action: ?action }]),
crate::prelude::pattern!(definition.fragment.facts(), [{ ?grant @ crate::capability::capability_delegate_action: ?action }]),
)
)
);
let root_key = root.verifying_key();
let mut has_action = false;
let mut needs_grant = false;
for action in actions {
has_action = true;
let mut open = false;
let mut root_is_authorized = false;
for policy in
descriptor::admission_policies(&snapshot, descriptor.fragment.facts(), action, None)
{
open |= matches!(policy, AdmissionPolicy::Open);
root_is_authorized |= policy
.roots()
.is_some_and(|roots| roots.contains(&root_key));
}
if open {
continue;
}
if !root_is_authorized {
return Err(CollectionGrantError::RootNotAuthorized {
capability,
collection,
root: root_key,
});
}
needs_grant = true;
}
if !has_action {
return Err(CollectionGrantError::RootNotAuthorized {
capability,
collection,
root: root_key,
});
}
if !needs_grant {
return Err(CollectionGrantError::OpenPolicy {
capability,
collection,
});
}
drop(snapshot);
let proof = CapabilityProof::new(
CapabilityResource::from(collection),
root,
capability,
recipient,
);
store
.insert_proof(proof.clone())
.map_err(CollectionGrantError::ProofInsert)?;
Ok(proof)
}
pub fn collection_reader_is_admitted_by<S>(
snapshot: &S,
collection: CollectionHandle,
subject: VerifyingKey,
proofs: &[CapabilityProof],
) -> Result<bool, CollectionDescriptorError<S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet,
{
let loaded = load_collection_descriptor(snapshot, collection)?;
let admitted =
descriptor::admission_policies(snapshot, loaded.fragment.facts(), super::ACTION_READ, None)
.any(|policy| {
collection_reader_is_admitted_by_policy(
snapshot, collection, &policy, subject, proofs,
)
});
Ok(admitted)
}
pub fn collection_reader_is_admitted_by_policy<R: BlobStoreGet>(
reader: &R,
collection: CollectionHandle,
policy: &AdmissionPolicy,
subject: VerifyingKey,
proofs: &[CapabilityProof],
) -> bool {
let evidence = admission_evidence_from_proofs(
policy,
super::ACTION_READ,
collection,
proofs.to_vec().into(),
);
evidence.authorizes(reader, subject)
}
pub fn collection_read_audience_by_policy<R: BlobStoreGet>(
reader: &R,
collection: CollectionHandle,
policy: &AdmissionPolicy,
proofs: &[CapabilityProof],
) -> CollectionReadAudience {
admission_evidence_from_proofs(
policy,
super::ACTION_READ,
collection,
proofs.to_vec().into(),
)
.authorized_subjects(reader)
}
pub fn collection_read_audience<S>(
snapshot: &S,
collection: CollectionHandle,
) -> Result<CollectionReadAudience, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
collection_action_audience(snapshot, collection, super::ACTION_READ)
}
pub fn collection_action_audience<S>(
snapshot: &S,
collection: CollectionHandle,
action: Id,
) -> Result<CollectionReadAudience, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
let loaded = load_collection_descriptor(snapshot, collection)
.map_err(CollectionAdmissionError::Descriptor)?;
let evidence = discover_admission_evidence(
snapshot,
descriptor::admission_policies(snapshot, loaded.fragment.facts(), action, None),
action,
collection,
)
.map_err(CollectionAdmissionError::Evidence)?;
Ok(evidence.authorized_subjects(snapshot))
}
pub fn collection_writer_is_admitted_by_policy<R: BlobStoreGet>(
reader: &R,
collection: CollectionHandle,
policy: &AdmissionPolicy,
subject: VerifyingKey,
proofs: &[CapabilityProof],
) -> bool {
let evidence = admission_evidence_from_proofs(
policy,
super::ACTION_WRITE,
collection,
proofs.to_vec().into(),
);
evidence.authorizes(reader, subject)
}
impl<L: CollectionEncoding> Collection<L> {
pub fn cover(self, members: impl IntoIterator<Item = Inline<Handle<L>>>) -> Cover<L> {
Cover::from_members(self, members)
}
pub fn open<S>(
snapshot: &S,
handle: CollectionHandle,
) -> Result<Self, CollectionOpenError<S::GetError<Infallible>>>
where
S: BlobStoreGet,
{
let descriptor = load_collection_descriptor(snapshot, handle)
.map_err(CollectionOpenError::Descriptor)?;
super::encoding::validate_descriptor_type::<L>(&descriptor.fragment)
.map_err(CollectionOpenError::WrongType)?;
Ok(Self::from_handle(handle))
}
pub fn policy<S>(
self,
snapshot: &S,
) -> Result<CollectionPolicy, CollectionDescriptorError<S::GetError<Infallible>>>
where
S: BlobStoreGet,
{
let loaded = load_collection_descriptor(snapshot, self.handle())?;
descriptor::policy(loaded.fragment.facts()).map_err(|source| {
CollectionDescriptorError::Invalid {
collection: self.handle(),
source,
}
})
}
pub fn writer_is_admitted<S>(
self,
snapshot: &S,
subject: VerifyingKey,
) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
let loaded = load_collection_descriptor(snapshot, self.handle())
.map_err(CollectionAdmissionError::Descriptor)?;
let evidence = discover_admission_evidence(
snapshot,
descriptor::admission_policies(
snapshot,
loaded.fragment.facts(),
super::ACTION_WRITE,
Some(L::id()),
),
super::ACTION_WRITE,
self.handle(),
)
.map_err(CollectionAdmissionError::Evidence)?;
Ok(evidence.authorizes(snapshot, subject))
}
pub fn writer_is_admitted_acquiring<S>(
self,
snapshot: &S,
subject: VerifyingKey,
) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
self.admitted_acquiring(snapshot, subject, super::ACTION_WRITE)
}
pub fn reader_is_admitted_acquiring<S>(
self,
snapshot: &S,
subject: VerifyingKey,
) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
self.admitted_acquiring(snapshot, subject, super::ACTION_READ)
}
fn admitted_acquiring<S>(
self,
snapshot: &S,
subject: VerifyingKey,
action: Id,
) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
let loaded = load_collection_descriptor(snapshot, self.handle())
.map_err(CollectionAdmissionError::Descriptor)?;
let attempt = crate::repo::read_attempt::ReadAttempt::new(snapshot);
let evidence = discover_admission_evidence(
snapshot,
descriptor::admission_policies(
&attempt,
loaded.fragment.facts(),
action,
Some(L::id()),
),
action,
self.handle(),
)
.map_err(CollectionAdmissionError::Evidence)?;
let admitted = evidence.authorizes(&attempt, subject);
attempt
.finish()
.map_err(CollectionAdmissionError::Definition)?;
Ok(admitted)
}
pub fn reader_is_admitted<S>(
self,
snapshot: &S,
subject: VerifyingKey,
) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
{
let loaded = load_collection_descriptor(snapshot, self.handle())
.map_err(CollectionAdmissionError::Descriptor)?;
let evidence = discover_admission_evidence(
snapshot,
descriptor::admission_policies(
snapshot,
loaded.fragment.facts(),
super::ACTION_READ,
Some(L::id()),
),
super::ACTION_READ,
self.handle(),
)
.map_err(CollectionAdmissionError::Evidence)?;
Ok(evidence.authorizes(snapshot, subject))
}
pub fn reader_is_admitted_by<S>(
self,
snapshot: &S,
subject: VerifyingKey,
proofs: &[CapabilityProof],
) -> Result<bool, CollectionDescriptorError<S::GetError<Infallible>>>
where
S: StoreSnapshot + BlobStoreGet,
{
let loaded = load_collection_descriptor(snapshot, self.handle())?;
let admitted = descriptor::admission_policies(
snapshot,
loaded.fragment.facts(),
super::ACTION_READ,
Some(L::id()),
)
.any(|policy| {
collection_reader_is_admitted_by_policy(
snapshot,
self.handle(),
&policy,
subject,
proofs,
)
});
Ok(admitted)
}
pub fn admitted<S>(self, snapshot: &S) -> Result<Cover<L>, S::RecordsError>
where
S: StoreRead,
{
let coverage = snapshot.coverage(&BTreeSet::from([self.handle()]))?;
let (support, _) = coverage.frontier_support(self.handle());
Ok(Cover::from_patch(self, support))
}
pub fn read<V, S>(
self,
snapshot: &S,
) -> Result<V, CollectionReadError<S::GetError<Infallible>, V::Error>>
where
S: StoreRead,
V: TryFromCover<L>,
{
snapshot
.collection(self)
.map_err(CollectionReadError::Realization)?
.view()
.map_err(CollectionReadError::View)
}
pub fn read_acquiring<V, S>(
self,
snapshot: &S,
) -> Result<V, CollectionReadError<S::GetError<Infallible>, V::Error>>
where
S: StoreRead,
V: TryFromCover<L>,
{
snapshot
.collection_acquiring(self)
.map_err(CollectionReadError::Realization)?
.view()
.map_err(CollectionReadError::View)
}
}
pub trait CollectionSnapshotExt: StoreRead + Sized {
fn collection<E>(
&self,
target: Collection<E>,
) -> Result<CollectionSnapshot<Self, E>, CollectionRealizationError>
where
E: CollectionEncoding,
Handle<E>: InlineEncoding,
{
super::observation::attach(self, target)
}
fn collection_acquiring<E>(
&self,
target: Collection<E>,
) -> Result<CollectionSnapshot<Self, E>, CollectionRealizationError>
where
E: CollectionEncoding,
Handle<E>: InlineEncoding,
{
super::observation::attach_acquiring(self, target)
}
fn attached<E>(
&self,
attached: Collection<E>,
) -> Result<AttachedSnapshot<Self, E>, CollectionRealizationError>
where
E: CollectionEncoding,
Handle<E>: InlineEncoding,
{
super::observation::attach_attached(self, attached)
}
fn attached_acquiring<E>(
&self,
attached: Collection<E>,
) -> Result<AttachedSnapshot<Self, E>, CollectionRealizationError>
where
E: CollectionEncoding,
Handle<E>: InlineEncoding,
{
super::observation::attach_attached_acquiring(self, attached)
}
}
impl<R> CollectionSnapshotExt for R where R: StoreRead {}
pub trait CollectionRealization: CollectionEncoding {
fn ensure<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<S::Snapshot, CollectionRealizationError>> + Send + 'a
where
S: Store + AsyncBlobStoreAcquire + Send;
fn maintain<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<S::Snapshot, CollectionRealizationError>> + Send + 'a
where
S: Store + AsyncBlobStoreAcquire + Send;
}
impl<T: CollectionDerivation> CollectionRealization for T {
async fn ensure<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> Result<S::Snapshot, CollectionRealizationError>
where
S: Store + AsyncBlobStoreAcquire + Send,
{
store
.ensure_with::<CanonicalDerivation<T>>(target, signing_key)
.await
}
async fn maintain<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> Result<S::Snapshot, CollectionRealizationError>
where
S: Store + AsyncBlobStoreAcquire + Send,
{
store
.maintain_with::<CanonicalDerivation<T>>(target, signing_key)
.await
}
}
impl CollectionRealization for SimpleArchive {
async fn ensure<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> Result<S::Snapshot, CollectionRealizationError>
where
S: Store + AsyncBlobStoreAcquire + Send,
{
realize_root(store, target, signing_key, false).await
}
async fn maintain<'a, S>(
store: &'a mut S,
target: Collection<Self>,
signing_key: &'a SigningKey,
) -> Result<S::Snapshot, CollectionRealizationError>
where
S: Store + AsyncBlobStoreAcquire + Send,
{
realize_root(store, target, signing_key, true).await
}
}
async fn realize_root<S>(
store: &mut S,
target: Collection<SimpleArchive>,
signing_key: &SigningKey,
compact: bool,
) -> Result<S::Snapshot, CollectionRealizationError>
where
S: Store + AsyncBlobStoreAcquire,
{
super::exact_derived::acquire_authority(store, target).await?;
if compact {
super::exact_derived::maintain_root_acquiring(store, target, signing_key).await?;
} else {
super::exact_derived::ensure_root(store, target).await?;
}
store.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze realized root snapshot", error)
})
}
pub trait CollectionStoreExt: BlobStorePut + CollectionStore + Sized {
fn register_collection<L>(
&mut self,
descriptor: Fragment,
) -> Result<Collection<L>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
where
L: CollectionEncoding,
{
super::encoding::validate_descriptor_type::<L>(&descriptor)
.map_err(CollectionRegistrationError::WrongType)?;
let handle = descriptor::put_closure(self, &descriptor)
.map_err(CollectionRegistrationError::DependencyPut)?;
Ok(Collection::from_handle(handle))
}
fn collection(
&mut self,
name: &str,
policy: CollectionPolicy,
) -> Result<
Collection<SimpleArchive>,
CollectionRegistrationError<<Self as BlobStorePut>::PutError>,
> {
let descriptor = descriptor::naming::<SimpleArchive>(name, policy);
let handle = descriptor::put_closure(self, &descriptor)
.map_err(CollectionRegistrationError::DependencyPut)?;
Ok(Collection::from_handle(handle))
}
fn derive<T>(
&mut self,
source: Collection<T::Source>,
argument: T::Argument,
policy: CollectionPolicy,
) -> Result<Collection<T>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
where
T: CollectionDerivation,
{
self.derive_with::<CanonicalDerivation<T>>(
source,
CanonicalDerivation::new(argument),
policy,
)
}
fn derive_with<M>(
&mut self,
source: Collection<M::Source>,
mapping: M,
policy: CollectionPolicy,
) -> Result<Collection<M::Target>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
where
M: DeriveMapping,
{
let descriptor = descriptor::deriving_with(source.handle(), &mapping, policy);
M::Target::validate_descriptor(&descriptor).map_err(|source| {
CollectionRegistrationError::WrongType(CollectionTypeError::InvalidDescriptor(source))
})?;
let handle = descriptor::put_closure(self, &descriptor)
.map_err(CollectionRegistrationError::DependencyPut)?;
Ok(Collection::from_handle(handle))
}
fn attach<T>(
&mut self,
parent: Collection<SimpleArchive>,
argument: T::Argument,
) -> Result<Collection<T>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
where
T: CollectionAttachment,
{
self.attach_with::<CanonicalAttachment<T>>(parent, CanonicalAttachment::new(argument))
}
fn attach_with<M>(
&mut self,
parent: Collection<SimpleArchive>,
mapping: M,
) -> Result<Collection<M::Target>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
where
M: MapMapping,
{
let descriptor = descriptor::attaching_with(parent.handle(), &mapping);
M::Target::validate_descriptor(&descriptor).map_err(|source| {
CollectionRegistrationError::WrongType(CollectionTypeError::InvalidDescriptor(source))
})?;
let handle = descriptor::put_closure(self, &descriptor)
.map_err(CollectionRegistrationError::DependencyPut)?;
Ok(Collection::from_handle(handle))
}
fn ensure_attached<'a, T>(
&'a mut self,
attached: Collection<T>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
T: CollectionAttachment,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<T>: InlineEncoding,
{
self.ensure_attached_with::<CanonicalAttachment<T>>(attached, signing_key)
}
fn ensure_attached_with<'a, M>(
&'a mut self,
attached: Collection<M::Target>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
M: MapMapping,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<M::Target>: InlineEncoding,
{
async move {
super::exact_derived::acquire_attached_lineage(self, attached).await?;
super::exact_derived::attach_acquiring_with::<Self, M>(self, attached, signing_key)
.await?;
self.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze post-attach snapshot", error)
})
}
}
fn maintain_attached<'a, T>(
&'a mut self,
attached: Collection<T>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
T: CollectionAttachment,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<T>: InlineEncoding,
{
self.maintain_attached_with::<CanonicalAttachment<T>>(attached, signing_key)
}
fn maintain_attached_with<'a, M>(
&'a mut self,
attached: Collection<M::Target>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
M: MapMapping,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<M::Target>: InlineEncoding,
{
async move {
super::exact_derived::acquire_attached_lineage(self, attached).await?;
let snapshot = self.snapshot().map_err(|error| {
CollectionRealizationError::storage("observe attached parent", error)
})?;
let (parent, _, _) = super::maintenance::attached_lineage(&snapshot, attached)?;
drop(snapshot);
realize_root(self, parent, signing_key, true).await?;
super::exact_derived::attach_acquiring_with::<Self, M>(self, attached, signing_key)
.await?;
self.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze post-attach snapshot", error)
})
}
}
fn ensure<'a, T>(
&'a mut self,
target: Collection<T>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
T: CollectionRealization,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<T>: InlineEncoding,
{
T::ensure(self, target, signing_key)
}
fn ensure_with<'a, M>(
&'a mut self,
target: Collection<M::Target>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
M: DeriveMapping,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<M::Target>: InlineEncoding,
{
async move {
super::exact_derived::acquire_authority(self, target).await?;
super::exact_derived::ensure_acquiring_with::<Self, M>(self, target, signing_key)
.await?;
self.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze post-ensure snapshot", error)
})
}
}
fn maintain<'a, T>(
&'a mut self,
target: Collection<T>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
T: CollectionRealization,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<T>: InlineEncoding,
{
T::maintain(self, target, signing_key)
}
fn maintain_with<'a, M>(
&'a mut self,
target: Collection<M::Target>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
M: DeriveMapping,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<M::Target>: InlineEncoding,
{
async move {
super::exact_derived::acquire_authority(self, target).await?;
super::exact_derived::maintain_acquiring_with::<Self, M>(self, target, signing_key)
.await?;
self.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze post-maintenance snapshot", error)
})
}
}
fn rederive<'a, T>(
&'a mut self,
target: Collection<T>,
foundations: &Cover<T::Source>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
T: CollectionDerivation,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<T>: InlineEncoding,
{
self.rederive_with::<CanonicalDerivation<T>>(target, foundations, signing_key)
}
fn rederive_with<'a, M>(
&'a mut self,
target: Collection<M::Target>,
foundations: &Cover<M::Source>,
signing_key: &'a SigningKey,
) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
where
M: DeriveMapping,
Self: Store + AsyncBlobStoreAcquire + Send,
Handle<M::Target>: InlineEncoding,
{
let source = foundations.collection().handle();
let members: BTreeSet<CollectionData> = foundations.data_members().collect();
async move {
super::exact_derived::acquire_authority(self, target).await?;
super::exact_derived::rederive_acquiring_with::<Self, M>(
self,
target,
source,
&members,
signing_key,
)
.await?;
self.snapshot().map_err(|error| {
CollectionRealizationError::storage("freeze post-re-derivation snapshot", error)
})
}
}
fn commit(
&mut self,
collection: Collection<SimpleArchive>,
signing_key: &SigningKey,
fragment: Fragment,
) -> Result<
CollectionCommit,
CollectionCommitError<
<Self as BlobStorePut>::PutError,
<Self as CollectionStore>::InsertError,
>,
> {
PreparedCollectionCommit::from_fragment(fragment)
.stage_for(self, collection, signing_key)?
.finalize()
}
}
impl<S> CollectionStoreExt for S where S: BlobStorePut + CollectionStore {}
#[cfg(test)]
mod grant_tests {
use super::*;
use crate::capability::{capability_action, capability_delegate_action};
use crate::collection::{ACTION_READ, ACTION_WRITE};
use crate::prelude::entity;
use crate::repo::memoryrepo::MemoryRepo;
#[test]
fn compound_grants_require_a_root_for_every_invocation_and_delegation_action() {
let read_root = SigningKey::from_bytes(&[71; 32]);
let write_root = SigningKey::from_bytes(&[72; 32]);
let recipient = SigningKey::from_bytes(&[73; 32]).verifying_key();
let mut store = MemoryRepo::default();
let collection = store
.collection(
"split action roots",
CollectionPolicy::new(
AdmissionPolicy::direct(read_root.verifying_key()),
AdmissionPolicy::direct(write_root.verifying_key()),
),
)
.unwrap();
for definition in [
entity! { capability_action*: [ACTION_READ, ACTION_WRITE] },
entity! {
capability_action: ACTION_READ,
capability_delegate_action: ACTION_WRITE,
},
entity! { capability_delegate_action*: [ACTION_READ, ACTION_WRITE] },
] {
let capability = store
.put::<SimpleArchive, _>(definition.facts().clone())
.unwrap();
for root in [&read_root, &write_root] {
assert!(matches!(
grant_collection_capability(
&mut store,
collection.handle(),
capability,
root,
recipient,
),
Err(CollectionGrantError::RootNotAuthorized { .. })
));
}
}
assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 0);
}
#[test]
fn an_unknown_action_or_empty_definition_cannot_borrow_a_known_action_root() {
let root = SigningKey::from_bytes(&[74; 32]);
let recipient = SigningKey::from_bytes(&[75; 32]).verifying_key();
let mut store = MemoryRepo::default();
let collection = store
.collection(
"unknown grant actions",
CollectionPolicy::new(
AdmissionPolicy::direct(root.verifying_key()),
AdmissionPolicy::direct(root.verifying_key()),
),
)
.unwrap();
let unknown = crate::id::genid().id;
for definition in [
entity! { capability_action*: [ACTION_READ, unknown] },
entity! {
capability_action: ACTION_READ,
capability_delegate_action: unknown,
},
Fragment::empty(),
] {
let capability = store
.put::<SimpleArchive, _>(definition.facts().clone())
.unwrap();
assert!(matches!(
grant_collection_capability(
&mut store,
collection.handle(),
capability,
&root,
recipient,
),
Err(CollectionGrantError::RootNotAuthorized { .. })
));
}
assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 0);
}
#[test]
fn compound_grants_preserve_independent_quorum_shares() {
let first = SigningKey::from_bytes(&[76; 32]);
let second = SigningKey::from_bytes(&[77; 32]);
let recipient = SigningKey::from_bytes(&[78; 32]).verifying_key();
let mut store = MemoryRepo::default();
let quorum =
AdmissionPolicy::quorum([first.verifying_key(), second.verifying_key()], 2, None)
.unwrap();
let collection = store
.collection(
"compound quorum shares",
CollectionPolicy::new(quorum.clone(), quorum),
)
.unwrap();
let capability = store
.put::<SimpleArchive, _>(
entity! {
capability_action*: [ACTION_READ, ACTION_WRITE],
capability_delegate_action: ACTION_READ,
}
.facts()
.clone(),
)
.unwrap();
let first_proof = grant_collection_capability(
&mut store,
collection.handle(),
capability,
&first,
recipient,
)
.unwrap();
let one_share = store.snapshot().unwrap();
assert!(!collection
.reader_is_admitted(&one_share, recipient)
.unwrap());
assert!(!collection
.writer_is_admitted(&one_share, recipient)
.unwrap());
grant_collection_capability(
&mut store,
collection.handle(),
capability,
&second,
recipient,
)
.unwrap();
let repeated = grant_collection_capability(
&mut store,
collection.handle(),
capability,
&first,
recipient,
)
.unwrap();
assert_eq!(repeated, first_proof);
let both_shares = store.snapshot().unwrap();
assert_eq!(both_shares.proofs().unwrap().count(), 2);
assert!(collection
.reader_is_admitted(&both_shares, recipient)
.unwrap());
assert!(collection
.writer_is_admitted(&both_shares, recipient)
.unwrap());
}
#[test]
fn delegate_only_read_grant_can_confer_read_without_invoking_it() {
let root = SigningKey::from_bytes(&[79; 32]);
let delegate = SigningKey::from_bytes(&[80; 32]);
let reader = SigningKey::from_bytes(&[81; 32]).verifying_key();
let mut store = MemoryRepo::default();
let collection = store
.collection(
"delegate without invocation",
CollectionPolicy::new(
AdmissionPolicy::direct(root.verifying_key()),
AdmissionPolicy::direct(root.verifying_key()),
),
)
.unwrap();
let capability = store
.put::<SimpleArchive, _>(
entity! { capability_delegate_action: ACTION_READ }
.facts()
.clone(),
)
.unwrap();
let proof = grant_collection_capability(
&mut store,
collection.handle(),
capability,
&root,
delegate.verifying_key(),
)
.unwrap();
let child = proof
.delegate(&delegate, read_capability(), reader)
.unwrap();
store.insert_proof(child).unwrap();
let snapshot = store.snapshot().unwrap();
assert!(!collection
.reader_is_admitted(&snapshot, delegate.verifying_key())
.unwrap());
assert!(!collection
.writer_is_admitted(&snapshot, delegate.verifying_key())
.unwrap());
assert!(collection.reader_is_admitted(&snapshot, reader).unwrap());
assert!(!collection.writer_is_admitted(&snapshot, reader).unwrap());
}
#[test]
fn open_actions_can_accompany_a_restricted_grant_but_cannot_authorize_it() {
let root = SigningKey::from_bytes(&[82; 32]);
let outsider = SigningKey::from_bytes(&[83; 32]);
let recipient = SigningKey::from_bytes(&[84; 32]).verifying_key();
let mut store = MemoryRepo::default();
let collection = store
.collection(
"public reads private writes",
CollectionPolicy::new(
AdmissionPolicy::Open,
AdmissionPolicy::direct(root.verifying_key()),
),
)
.unwrap();
let capability = store
.put::<SimpleArchive, _>(
entity! {
capability_action*: [ACTION_READ, ACTION_WRITE],
capability_delegate_action: ACTION_READ,
}
.facts()
.clone(),
)
.unwrap();
assert!(matches!(
grant_collection_capability(
&mut store,
collection.handle(),
capability,
&outsider,
recipient,
),
Err(CollectionGrantError::RootNotAuthorized { .. })
));
grant_collection_capability(
&mut store,
collection.handle(),
capability,
&root,
recipient,
)
.unwrap();
let snapshot = store.snapshot().unwrap();
assert!(collection.reader_is_admitted(&snapshot, recipient).unwrap());
assert!(collection.writer_is_admitted(&snapshot, recipient).unwrap());
assert!(matches!(
grant_collection_read(&mut store, collection.handle(), &root, recipient),
Err(CollectionGrantError::OpenPolicy { .. })
));
let open = store
.collection(
"both actions open",
CollectionPolicy::new(AdmissionPolicy::Open, AdmissionPolicy::Open),
)
.unwrap();
assert!(matches!(
grant_collection_capability(&mut store, open.handle(), capability, &root, recipient),
Err(CollectionGrantError::OpenPolicy { .. })
));
assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 1);
}
}