Skip to content

Commit

Permalink
feat!: Update capabilites in line with Ucan 0.9.0/0.10.0. Fixes #22.
Browse files Browse the repository at this point in the history
* Represents capabilities as map-of-maps rather than array of tuples.
* Validates caveats in proof chain
* Renames 'att' to 'cap' (Ucan spec 0.10.0).
* Renames various capability semantics structs with spec names (With=>Resource,
  Can/Action=>Ability, Resource=>ResourceURI)
* Renames 'Capability' to 'CapabilityView'.
  • Loading branch information
jsantell committed Jun 10, 2023
1 parent 12d4756 commit 6fb3927
Show file tree
Hide file tree
Showing 21 changed files with 889 additions and 290 deletions.
32 changes: 23 additions & 9 deletions ucan/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeMap;

use crate::{
capability::{
proof::ProofDelegationSemantics, Action, Capability, CapabilityIpld, CapabilitySemantics,
proof::ProofDelegationSemantics, Ability, Capability, CapabilitySemantics, CapabilityView,
Scope,
},
crypto::KeyMaterial,
Expand All @@ -29,7 +29,7 @@ where
pub issuer: &'a K,
pub audience: String,

pub capabilities: Vec<CapabilityIpld>,
pub capabilities: Vec<Capability>,

pub expiration: Option<u64>,
pub not_before: Option<u64>,
Expand Down Expand Up @@ -80,7 +80,7 @@ where
exp: self.expiration,
nbf: self.not_before,
nnc: nonce,
att: self.capabilities.clone(),
cap: self.capabilities.clone().try_into()?,
fct: facts,
prf: proofs,
})
Expand Down Expand Up @@ -116,7 +116,7 @@ where
issuer: Option<&'a K>,
audience: Option<String>,

capabilities: Vec<CapabilityIpld>,
capabilities: Vec<Capability>,

lifetime: Option<u64>,
expiration: Option<u64>,
Expand Down Expand Up @@ -231,12 +231,26 @@ where

/// Claim a capability by inheritance (from an authorizing proof) or
/// implicitly by ownership of the resource by this UCAN's issuer
pub fn claiming_capability<S, A>(mut self, capability: &Capability<S, A>) -> Self
pub fn claiming_capability<S, A>(mut self, capability: &CapabilityView<S, A>) -> Self
where
S: Scope,
A: Action,
A: Ability,
{
self.capabilities.push(CapabilityIpld::from(capability));
self.capabilities.push(Capability::from(capability));
self
}

/// Claim a capability by inheritance (from an authorizing proof) or
/// implicitly by ownership of the resource by this UCAN's issuer
pub fn claiming_capability_from_data(mut self, capability: &Capability) -> Self {
self.capabilities.push(capability.to_owned());
self
}

/// Claim capabilities by inheritance (from an authorizing proof) or
/// implicitly by ownership of the resource by this UCAN's issuer
pub fn claiming_capabilities_from_data(mut self, capabilities: &Vec<Capability>) -> Self {
self.capabilities.extend(capabilities.to_owned());
self
}

Expand All @@ -251,11 +265,11 @@ where
let proof_index = self.proofs.len() - 1;
let proof_delegation = ProofDelegationSemantics {};
let capability =
proof_delegation.parse(&format!("prf:{proof_index}"), "ucan/DELEGATE");
proof_delegation.parse(&format!("prf:{proof_index}"), "ucan/DELEGATE", None);

match capability {
Some(capability) => {
self.capabilities.push(CapabilityIpld::from(&capability));
self.capabilities.push(Capability::from(&capability));
}
None => warn!("Could not produce delegation capability"),
}
Expand Down
172 changes: 172 additions & 0 deletions ucan/src/capability/caveats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use std::ops::Deref;

use anyhow::{anyhow, Error, Result};
use serde_json::{Map, Value};

#[derive(Clone)]
pub struct Caveat(Map<String, Value>);

impl Caveat {
/// Determines if this [Caveat] enables/allows the provided caveat.
///
/// ```
/// use ucan::capability::{Caveat};
/// use serde_json::json;
///
/// let no_caveat = Caveat::try_from(json!({})).unwrap();
/// let x_caveat = Caveat::try_from(json!({ "x": true })).unwrap();
/// let x_diff_caveat = Caveat::try_from(json!({ "x": false })).unwrap();
/// let y_caveat = Caveat::try_from(json!({ "y": true })).unwrap();
/// let xz_caveat = Caveat::try_from(json!({ "x": true, "z": true })).unwrap();
///
/// assert!(no_caveat.enables(&no_caveat));
/// assert!(x_caveat.enables(&x_caveat));
/// assert!(no_caveat.enables(&x_caveat));
/// assert!(x_caveat.enables(&xz_caveat));
///
/// assert!(!x_caveat.enables(&x_diff_caveat));
/// assert!(!x_caveat.enables(&no_caveat));
/// assert!(!x_caveat.enables(&y_caveat));
/// ```
pub fn enables(&self, other: &Caveat) -> bool {
if self.is_empty() {
return true;
}

if other.is_empty() {
return false;
}

if self == other {
return true;
}

for (key, value) in self.iter() {
if let Some(other_value) = other.get(key) {
if value != other_value {
return false;
}
} else {
return false;
}
}

true
}
}

impl Deref for Caveat {
type Target = Map<String, Value>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl PartialEq for Caveat {
fn eq(&self, other: &Caveat) -> bool {
self.0 == other.0
}
}

impl TryFrom<Value> for Caveat {
type Error = Error;
fn try_from(value: Value) -> Result<Caveat> {
Ok(Caveat(match value {
Value::Object(obj) => obj,
_ => return Err(anyhow!("Caveat must be an object")),
}))
}
}

impl TryFrom<&Value> for Caveat {
type Error = Error;
fn try_from(value: &Value) -> Result<Caveat> {
Caveat::try_from(value.to_owned())
}
}

#[derive(Clone)]
pub struct Caveats(Vec<Caveat>);

impl Caveats {
/// Determines if these [Caveats] enables/allows the provided caveats.
///
/// ```
/// use ucan::capability::{Caveat, Caveats};
/// use serde_json::json;
///
/// let no_caveat = json!({});
/// let x_caveat = json!({ "x": true });
/// let y_caveat = json!({ "y": true });
/// let z_caveat = json!({ "z": true });
/// let yz_caveat = json!({ "y": true, "z": true });
///
/// let no_caveats = Caveats::from(vec![no_caveat.clone()]);
/// let x_caveats = Caveats::from(vec![x_caveat.clone()]);
/// let y_caveats = Caveats::from(vec![y_caveat.clone()]);
/// let x_y_caveats = Caveats::from(vec![x_caveat.clone(), y_caveat.clone()]);
/// let x_yz_caveats = Caveats::from(vec![x_caveat.clone(), yz_caveat.clone()]);
/// let x_y_z_caveats = Caveats::from(vec![x_caveat.clone(), y_caveat.clone(), z_caveat.clone()]);
///
/// assert!(no_caveats.enables(&no_caveats));
/// assert!(x_caveats.enables(&x_caveats));
/// assert!(no_caveats.enables(&x_caveats));
/// assert!(x_y_caveats.enables(&x_caveats)); // Removes capability
/// assert!(x_y_caveats.enables(&x_yz_caveats)); // Attenuates existing caveat
///
/// assert!(!x_caveats.enables(&no_caveats));
/// assert!(!x_caveats.enables(&y_caveats));
/// assert!(!x_y_caveats.enables(&x_y_z_caveats));
/// ```
pub fn enables(&self, other: &Caveats) -> bool {
if self == other {
return true;
}

for other_caveat in other.iter() {
// Each delegated caveat must be enabled by some proof caveat
let mut enabled = false;
for proof_caveat in self.iter() {
if proof_caveat.enables(other_caveat) {
enabled = true;
break;
}
}
if !enabled {
return false;
}
}
true
}
}

impl From<Vec<Value>> for Caveats {
fn from(value: Vec<Value>) -> Caveats {
Caveats(
value
.into_iter()
.filter_map(|c| Caveat::try_from(c).ok())
.collect(),
)
}
}

impl From<&Vec<Value>> for Caveats {
fn from(value: &Vec<Value>) -> Caveats {
Caveats::from(value.to_owned())
}
}

impl Deref for Caveats {
type Target = Vec<Caveat>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl PartialEq for Caveats {
fn eq(&self, other: &Caveats) -> bool {
self.0 == other.0
}
}
Loading

0 comments on commit 6fb3927

Please sign in to comment.