use std::error::Error;
use std::fmt;
use ed25519_dalek::VerifyingKey;
use crate::blob::encodings::simplearchive::SimpleArchive;
use crate::capability::{is_valid_capability_principal, CapabilityHandle};
use crate::id::{id_hex, Id};
use crate::inline::encodings::ed25519::ED25519PublicKey;
use crate::inline::encodings::genid::GenId;
use crate::inline::encodings::hash::Handle;
use crate::inline::encodings::iu256::U256;
use crate::metadata;
use crate::prelude::{attributes, entity, exists, find, pattern};
use crate::trible::{Fragment, TribleSet};
attributes! {
"D6065F21923709C72144866C05F74B46" as pub resource_policy: GenId;
"2949050AA6092F5689EA7EAA52700CE9" as pub capability_handle: Handle<SimpleArchive>;
"5059416BAF824B364FF52F56A0D1CF9F" as pub resource_handle: Handle<SimpleArchive>;
"62E951EC3ABB7F9B7F123BB7DE2F9F99" as pub resource_collection: Handle<SimpleArchive>;
"E9AC4E4749FD219705E9533B02AAA405" as pub admission_policy_root: ED25519PublicKey;
"AF874E4D44C3A6565754D3EE8EDE48B5" as pub admission_invoke_threshold: U256;
"1300EF404FE61D26FC091B0CEC1C41EC" as pub admission_delegate_threshold: U256;
}
pub const KIND_ADMISSION_POLICY_OPEN: Id = id_hex!("77983C388E5109F9D55106A28D1C18FA");
pub const KIND_ADMISSION_POLICY_QUORUM: Id = id_hex!("DC81E78C55E759F71AFFA645A02C44C5");
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AdmissionPolicyError {
EmptyRoots,
InvalidRoot { key: [u8; 32] },
InvalidThreshold {
field: &'static str,
threshold: u32,
roots: usize,
},
}
impl fmt::Display for AdmissionPolicyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyRoots => formatter.write_str("an admission quorum needs at least one root"),
Self::InvalidRoot { .. } => {
formatter.write_str("an admission root must be a canonical, non-weak Ed25519 key")
}
Self::InvalidThreshold {
field,
threshold,
roots,
} => write!(
formatter,
"{field} threshold {threshold} is outside 1..={roots}",
),
}
}
}
impl Error for AdmissionPolicyError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AdmissionPolicy {
Open,
Quorum(ValidatedQuorum),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ValidatedQuorum {
roots: Vec<VerifyingKey>,
invoke_threshold: u32,
delegate_threshold: Option<u32>,
}
impl ValidatedQuorum {
pub fn roots(&self) -> &[VerifyingKey] {
&self.roots
}
pub const fn invoke_threshold(&self) -> u32 {
self.invoke_threshold
}
pub const fn delegate_threshold(&self) -> Option<u32> {
self.delegate_threshold
}
}
impl AdmissionPolicy {
pub fn quorum(
roots: impl IntoIterator<Item = VerifyingKey>,
invoke_threshold: u32,
delegate_threshold: Option<u32>,
) -> Result<Self, AdmissionPolicyError> {
let mut roots: Vec<_> = roots.into_iter().collect();
if let Some(root) = roots
.iter()
.find(|root| !is_valid_capability_principal(root))
{
return Err(AdmissionPolicyError::InvalidRoot {
key: root.to_bytes(),
});
}
roots.sort_unstable_by_key(VerifyingKey::to_bytes);
roots.dedup_by_key(|key| key.to_bytes());
if roots.is_empty() {
return Err(AdmissionPolicyError::EmptyRoots);
}
validate_threshold("invoke", invoke_threshold, roots.len())?;
if let Some(threshold) = delegate_threshold {
validate_threshold("delegate", threshold, roots.len())?;
}
Ok(Self::Quorum(ValidatedQuorum {
roots,
invoke_threshold,
delegate_threshold,
}))
}
pub fn direct(root: VerifyingKey) -> Self {
Self::quorum([root], 1, None).expect("one-root direct policy is valid")
}
pub fn delegable(root: VerifyingKey) -> Self {
Self::quorum([root], 1, Some(1)).expect("one-root delegable policy is valid")
}
pub fn fragment(&self) -> Fragment {
match self {
Self::Open => {
let kind = KIND_ADMISSION_POLICY_OPEN;
entity! { _ @ metadata::tag: kind }
}
Self::Quorum(quorum) => {
let kind = KIND_ADMISSION_POLICY_QUORUM;
entity! { _ @
metadata::tag: kind,
admission_policy_root*: quorum.roots.iter().copied(),
admission_invoke_threshold: quorum.invoke_threshold,
admission_delegate_threshold?: quorum.delegate_threshold,
}
}
}
}
pub fn binding(&self, capability: CapabilityHandle) -> Fragment {
match self {
Self::Open => entity! {
capability_handle: capability,
metadata::tag: KIND_ADMISSION_POLICY_OPEN,
},
Self::Quorum(quorum) => entity! {
capability_handle: capability,
metadata::tag: KIND_ADMISSION_POLICY_QUORUM,
admission_policy_root*: quorum.roots.iter().copied(),
admission_invoke_threshold: quorum.invoke_threshold,
admission_delegate_threshold?: quorum.delegate_threshold,
},
}
}
pub fn roots(&self) -> Option<&[VerifyingKey]> {
match self {
Self::Open => None,
Self::Quorum(quorum) => Some(quorum.roots()),
}
}
pub fn has_root(&self, root: VerifyingKey) -> bool {
self.roots().is_some_and(|roots| {
roots
.binary_search_by_key(&root.to_bytes(), VerifyingKey::to_bytes)
.is_ok()
})
}
pub const fn invoke_threshold(&self) -> Option<u32> {
match self {
Self::Open => None,
Self::Quorum(quorum) => Some(quorum.invoke_threshold()),
}
}
pub const fn delegate_threshold(&self) -> Option<u32> {
match self {
Self::Open => None,
Self::Quorum(quorum) => quorum.delegate_threshold(),
}
}
}
fn validate_threshold(
field: &'static str,
threshold: u32,
roots: usize,
) -> Result<(), AdmissionPolicyError> {
if threshold == 0 || threshold as usize > roots {
return Err(AdmissionPolicyError::InvalidThreshold {
field,
threshold,
roots,
});
}
Ok(())
}
pub fn resource_policies<'a>(
facts: &'a TribleSet,
resource: Id,
capability: Option<CapabilityHandle>,
) -> impl Iterator<Item = (CapabilityHandle, AdmissionPolicy)> + 'a {
let bindings: Box<dyn Iterator<Item = (Id, CapabilityHandle)> + 'a> = match capability {
Some(capability) => Box::new(
find!(
binding: Id,
pattern!(facts, [
{ resource @ resource_policy: ?binding },
{ ?binding @ capability_handle: capability },
])
)
.map(move |binding| (binding, capability)),
),
None => Box::new(find!(
(binding: Id, capability: CapabilityHandle),
pattern!(facts, [
{ resource @ resource_policy: ?binding },
{ ?binding @ capability_handle: ?capability },
])
)),
};
bindings.flat_map(move |(binding, capability)| {
let open = exists!(pattern!(facts, [{
binding @ metadata::tag: KIND_ADMISSION_POLICY_OPEN,
}]))
.then_some((capability, AdmissionPolicy::Open));
let roots: Vec<_> = find!(
root: VerifyingKey,
pattern!(facts, [{ binding @ admission_policy_root: ?root }])
)
.filter(is_valid_capability_principal)
.collect();
let quorums = find!(
invoke: u32,
pattern!(facts, [{ binding @
metadata::tag: KIND_ADMISSION_POLICY_QUORUM,
admission_invoke_threshold: ?invoke,
}])
)
.filter_map(move |invoke| {
AdmissionPolicy::quorum(roots.iter().copied(), invoke, None)
.ok()
.map(|policy| (capability, policy))
});
open.into_iter().chain(quorums)
})
}
#[cfg(test)]
mod tests {
use ed25519_dalek::SigningKey;
use super::*;
fn key(byte: u8) -> VerifyingKey {
SigningKey::from_bytes(&[byte; 32]).verifying_key()
}
#[test]
fn quorum_roots_are_a_canonical_set() {
let a = AdmissionPolicy::quorum([key(2), key(1), key(2)], 1, Some(1)).unwrap();
let b = AdmissionPolicy::quorum([key(1), key(2)], 1, Some(1)).unwrap();
assert_eq!(a, b);
assert_eq!(a.fragment(), b.fragment());
}
#[test]
fn invalid_thresholds_are_rejected() {
assert!(AdmissionPolicy::quorum([], 1, None).is_err());
assert!(AdmissionPolicy::quorum([key(1)], 0, None).is_err());
assert!(AdmissionPolicy::quorum([key(1)], 1, Some(2)).is_err());
let weak = VerifyingKey::from_bytes(&[0; 32]).unwrap();
assert_eq!(
AdmissionPolicy::quorum([weak], 1, None),
Err(AdmissionPolicyError::InvalidRoot { key: [0; 32] })
);
}
#[test]
fn policies_keep_capabilities_roots_and_resource_entities_joined() {
let resource = crate::id::rngid();
let other_resource = crate::id::rngid();
let binding = crate::id::rngid();
let first = CapabilityHandle::new([0; 32]);
let second = CapabilityHandle::new([2; 32]);
let unknown = CapabilityHandle::new([3; 32]);
let facts = entity! { &resource @
resource_policy*: entity! { &binding @
capability_handle: first,
metadata::tag: KIND_ADMISSION_POLICY_QUORUM,
admission_policy_root: key(1),
admission_invoke_threshold*: [0_u32, 1_u32, 2_u32],
metadata::name: "opaque binding entity",
},
resource_policy*: AdmissionPolicy::direct(key(2)).binding(second),
} + entity! { &other_resource @
resource_policy*: AdmissionPolicy::Open.binding(unknown),
};
assert_eq!(
resource_policies(facts.facts(), resource.id, Some(first)).collect::<Vec<_>>(),
vec![(first, AdmissionPolicy::direct(key(1)))]
);
assert_eq!(
resource_policies(facts.facts(), resource.id, Some(second)).collect::<Vec<_>>(),
vec![(second, AdmissionPolicy::direct(key(2)))]
);
assert_eq!(
resource_policies(facts.facts(), resource.id, None).count(),
2
);
assert_eq!(
resource_policies(facts.facts(), resource.id, Some(unknown)).count(),
0
);
}
#[test]
fn unknown_policy_kind_does_not_hide_supported_handle_interpretations() {
let resource = crate::id::rngid();
let unknown_kind = crate::id::rngid();
let capability = CapabilityHandle::new([4; 32]);
let facts = entity! { &resource @
resource_policy*: entity! {
capability_handle: capability,
metadata::tag: unknown_kind.id,
} + AdmissionPolicy::direct(key(4)).binding(capability)
+ AdmissionPolicy::Open.binding(capability),
};
let policies: Vec<_> = resource_policies(facts.facts(), resource.id, Some(capability))
.map(|(_, policy)| policy)
.collect();
assert_eq!(policies.len(), 2);
assert!(policies.contains(&AdmissionPolicy::direct(key(4))));
assert!(policies.contains(&AdmissionPolicy::Open));
}
}