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.
* Renames 'att' to 'cap' (Ucan spec 0.10.0).
* Renames 'Capability' to 'CapabilityView'.
* Leaves caveat parsing up to consumer.
  • Loading branch information
jsantell committed Jun 5, 2023
1 parent 3d30ec8 commit 1379db5
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 264 deletions.
16 changes: 8 additions & 8 deletions ucan/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
capability::{
proof::ProofDelegationSemantics, Action, Capability, CapabilityIpld, CapabilitySemantics,
proof::ProofDelegationSemantics, Action, Capability, CapabilitySemantics, CapabilityView,
Scope,
},
crypto::KeyMaterial,
Expand Down Expand Up @@ -28,7 +28,7 @@ where
pub issuer: &'a K,
pub audience: String,

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

pub expiration: u64,
pub not_before: Option<u64>,
Expand Down Expand Up @@ -79,7 +79,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 @@ -115,7 +115,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 @@ -228,12 +228,12 @@ 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,
{
self.capabilities.push(CapabilityIpld::from(capability));
self.capabilities.push(Capability::from(capability));
self
}

Expand All @@ -248,11 +248,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
191 changes: 191 additions & 0 deletions ucan/src/capability/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
collections::btree_map::Iter as BTreeMapIter,
collections::BTreeMap,
fmt::Debug,
iter::{FlatMap, Map},
ops::Deref,
};

#[derive(Debug, Clone, PartialEq, Eq)]
/// Represents a single, flattened capability containing a resource, ability, and caveats.
pub struct Capability {
pub resource: String,
pub ability: String,
pub caveats: Vec<Value>,
}

impl Capability {
pub fn new(resource: String, ability: String, caveats: Vec<Value>) -> Self {
Capability {
resource,
ability,
caveats,
}
}
}

impl From<(String, String, Vec<Value>)> for Capability {
fn from(value: (String, String, Vec<Value>)) -> Self {
Capability::new(value.0, value.1, value.2)
}
}

impl From<(&str, &str, &Vec<Value>)> for Capability {
fn from(value: (&str, &str, &Vec<Value>)) -> Self {
Capability::new(value.0.to_owned(), value.1.to_owned(), value.2.to_owned())
}
}

impl From<Capability> for (String, String, Vec<Value>) {
fn from(value: Capability) -> Self {
(value.resource, value.ability, value.caveats)
}
}

type MapImpl<K, V> = BTreeMap<K, V>;
type MapIter<'a, K, V> = BTreeMapIter<'a, K, V>;
type AbilitiesImpl = MapImpl<String, Vec<Value>>;
type CapabilitiesImpl = MapImpl<String, AbilitiesImpl>;
type AbilitiesMapClosure<'a> = Box<dyn Fn((&'a String, &'a Vec<Value>)) -> Capability + 'a>;
type AbilitiesMap<'a> = Map<MapIter<'a, String, Vec<Value>>, AbilitiesMapClosure<'a>>;
type CapabilitiesIterator<'a> = FlatMap<
MapIter<'a, String, AbilitiesImpl>,
AbilitiesMap<'a>,
fn((&'a String, &'a AbilitiesImpl)) -> AbilitiesMap<'a>,
>;

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
/// The [Capabilities] struct contains capability data in resource-map to ability-map
/// form. Use `iter()` to deconstruct this map into a sequence of [Capability] datas.
///
/// ```
/// use ucan::capability::{Capabilities, Capability};
/// use serde_json::json;
///
/// let capabilities = Capabilities::try_from(&json!({
/// "example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr": {
/// "wnfs/append": [{}]
/// },
/// "mailto:username@example.com": {
/// "msg/receive": [{ "max_count": 5 }],
/// "msg/send": [{}]
/// }
/// })).unwrap();
///
/// assert_eq!(capabilities.iter().collect::<Vec<Capability>>(), vec![
/// Capability::from(("example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr", "wnfs/append", &vec![json!({})])),
/// Capability::from(("mailto:username@example.com", "msg/receive", &vec![json!({ "max_count": 5 })])),
/// Capability::from(("mailto:username@example.com", "msg/send", &vec![json!({})])),
/// ]);
/// ```
pub struct Capabilities(CapabilitiesImpl);

impl Capabilities {
pub fn iter(&self) -> CapabilitiesIterator {
self.0
.iter()
.flat_map(|(resource, abilities): (&String, &AbilitiesImpl)| {
abilities
.iter()
.map(Box::new(|(ability, caveats): (&String, &Vec<Value>)| {
Capability::new(resource.to_owned(), ability.to_owned(), caveats.to_owned())
}))
})
}
}

impl Deref for Capabilities {
type Target = CapabilitiesImpl;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl TryFrom<Vec<Capability>> for Capabilities {
type Error = anyhow::Error;
fn try_from(value: Vec<Capability>) -> Result<Self, Self::Error> {
let mut resources: CapabilitiesImpl = BTreeMap::new();
for capability in value.into_iter() {
let (resource_name, ability, caveats) =
<(String, String, Vec<Value>)>::from(capability);

let resource = if let Some(resource) = resources.get_mut(&resource_name) {
resource
} else {
let resource: AbilitiesImpl = BTreeMap::new();
resources.insert(resource_name.clone(), resource);
resources.get_mut(&resource_name).unwrap()
};

for value in caveats.iter() {
if !value.is_object() {
return Err(anyhow!("Caveat must be an object: {}", value));
}
}
resource.insert(ability, caveats);
}
Capabilities::try_from(resources)
}
}

impl TryFrom<CapabilitiesImpl> for Capabilities {
type Error = anyhow::Error;

fn try_from(value: CapabilitiesImpl) -> Result<Self, Self::Error> {
for (resource, abilities) in value.iter() {
if abilities.is_empty() {
// 0.10.0: 3.2.6.2 One or more abilities MUST be given for each resource.
return Err(anyhow!("No abilities given for resource: {}", resource));
}
}
Ok(Capabilities(value))
}
}

impl TryFrom<&Value> for Capabilities {
type Error = anyhow::Error;

fn try_from(value: &Value) -> Result<Self, Self::Error> {
let map = value
.as_object()
.ok_or_else(|| anyhow!("Capabilities must be an object."))?;
let mut resources: CapabilitiesImpl = BTreeMap::new();

for (key, value) in map.iter() {
let resource = key.to_owned();
let abilities_object = value
.as_object()
.ok_or_else(|| anyhow!("Abilities must be an object."))?;

let abilities = {
let mut abilities: AbilitiesImpl = BTreeMap::new();
for (key, value) in abilities_object.iter() {
let ability = key.to_owned();
let mut caveats: Vec<Value> = vec![];

let array = value
.as_array()
.ok_or_else(|| anyhow!("Caveats must be defined as an array."))?;
for value in array.iter() {
if !value.is_object() {
return Err(anyhow!("Caveat must be an object: {}", value));
}
caveats.push(value.to_owned());
}
abilities.insert(ability, caveats);
}
abilities
};

if resources.insert(resource, abilities).is_some() {
// 0.10.0: 3.2.6.1 Resources MUST be unique and given as URIs.
return Err(anyhow!("Capability resource is not unique: {}", key));
}
}

Capabilities::try_from(resources)
}
}
54 changes: 0 additions & 54 deletions ucan/src/capability/iterator.rs

This file was deleted.

4 changes: 2 additions & 2 deletions ucan/src/capability/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod proof;

mod iterator;
mod data;
mod semantics;

pub use iterator::*;
pub use data::*;
pub use semantics::*;
Loading

0 comments on commit 1379db5

Please sign in to comment.