use core::marker::PhantomData;
use crate::receipt::Digest;
use crate::witness::Witness;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Capability<W> {
pub name: String,
pub digest: Digest,
pub witness: PhantomData<W>,
}
impl<W: Witness> Capability<W> {
#[must_use]
pub fn new(name: impl Into<String>, digest: Digest) -> Self {
Self {
name: name.into(),
digest,
witness: PhantomData,
}
}
#[must_use]
pub fn is_pinned(&self) -> bool {
!self.name.trim().is_empty() && !self.digest.0.trim().is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AuthorityConstraint {
RequiresWitness,
RequiresDigestPin,
RequiresBoundedScope,
RequiresExpiry,
RequiresDataMinimization,
RequiresFairnessAttestation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthorityEnvelope<W> {
pub capability: Capability<W>,
pub constraints: Vec<AuthorityConstraint>,
pub scope: String,
pub data_minimization_note: String,
pub fairness_attestation_ref: String,
}
impl<W: Witness> AuthorityEnvelope<W> {
#[must_use]
pub fn new(
capability: Capability<W>,
constraints: Vec<AuthorityConstraint>,
scope: impl Into<String>,
) -> Self {
Self {
capability,
constraints,
scope: scope.into(),
data_minimization_note: String::new(),
fairness_attestation_ref: String::new(),
}
}
#[must_use]
pub fn with_data_minimization(mut self, note: impl Into<String>) -> Self {
self.data_minimization_note = note.into();
self
}
#[must_use]
pub fn with_fairness_attestation(mut self, attestation_ref: impl Into<String>) -> Self {
self.fairness_attestation_ref = attestation_ref.into();
self
}
#[must_use = "check the validation result"]
pub fn validate(&self) -> Result<(), Vec<AuthorityRefusal>> {
let mut refusals = Vec::new();
if self.constraints.is_empty() {
refusals.push(AuthorityRefusal::UnconstrainedEnvelope);
}
for constraint in &self.constraints {
match constraint {
AuthorityConstraint::RequiresDigestPin if !self.capability.is_pinned() => {
refusals.push(AuthorityRefusal::MissingDigestPin);
}
AuthorityConstraint::RequiresBoundedScope if self.scope.trim().is_empty() => {
refusals.push(AuthorityRefusal::UnboundedScope);
}
AuthorityConstraint::RequiresDataMinimization
if self.data_minimization_note.trim().is_empty() =>
{
refusals.push(AuthorityRefusal::MissingDataMinimizationNote);
}
AuthorityConstraint::RequiresFairnessAttestation
if self.fairness_attestation_ref.trim().is_empty() =>
{
refusals.push(AuthorityRefusal::MissingFairnessAttestation);
}
_ => {}
}
}
if refusals.is_empty() {
Ok(())
} else {
Err(refusals)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AuthorityRefusal {
MissingDigestPin,
UnboundedScope,
UnconstrainedEnvelope,
MissingDataMinimizationNote,
MissingFairnessAttestation,
}
impl core::fmt::Display for AuthorityRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
AuthorityRefusal::MissingDigestPin => "MissingDigestPin",
AuthorityRefusal::UnboundedScope => "UnboundedScope",
AuthorityRefusal::UnconstrainedEnvelope => "UnconstrainedEnvelope",
AuthorityRefusal::MissingDataMinimizationNote => "MissingDataMinimizationNote",
AuthorityRefusal::MissingFairnessAttestation => "MissingFairnessAttestation",
};
write!(f, "authority refusal: {law}")
}
}
pub trait GatedAdmit: crate::admission::Admit {
type EnvelopeWitness: Witness;
fn envelope(&self) -> &AuthorityEnvelope<Self::EnvelopeWitness>;
fn check_gate(&self) -> Result<(), Vec<AuthorityRefusal>> {
self.envelope().validate()
}
}