Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ucan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ thiserror = { workspace = true, default-features = false }
tracing = { workspace = true, default-features = false, features = ["attributes"] }
varsig = { path = "../varsig", default-features = false, features = ["dag_cbor", "ed25519"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
wasm-bindgen = "0.2"

[dev-dependencies]
arbitrary = { workspace = true, optional = false }
base64 = "0.22.1"
Expand Down
1 change: 0 additions & 1 deletion ucan/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//! Helpers for cryptographic operations.

pub mod nonce;
pub mod signed;
9 changes: 0 additions & 9 deletions ucan/src/crypto/signed.rs

This file was deleted.

9 changes: 5 additions & 4 deletions ucan/src/delegation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! UCAN Delegation
//!
//! The spec for UCAN Delegations can be found at
//! [the GitHub repo](https://github.com/ucan-wg/invocation/).
//! [the GitHub repo](https://github.com/ucan-wg/delegation/).

pub mod builder;
pub mod policy;
Expand Down Expand Up @@ -378,6 +378,7 @@ where
let command = command.ok_or_else(|| de::Error::missing_field("cmd"))?;
let policy = policy.ok_or_else(|| de::Error::missing_field("pol"))?;
let nonce = nonce.ok_or_else(|| de::Error::missing_field("nonce"))?;
let expiration = expiration.ok_or_else(|| de::Error::missing_field("exp"))?;

Ok(DelegationPayload {
issuer,
Expand All @@ -386,7 +387,7 @@ where
command,
policy,
nonce,
expiration: expiration.unwrap_or(None),
expiration,
not_before: not_before.unwrap_or(None),
meta: meta.unwrap_or_default(),
})
Expand Down Expand Up @@ -430,7 +431,7 @@ mod tests {
.issuer(iss.clone())
.audience(aud)
.subject(DelegatedSubject::Specific(sub))
.command(vec!["read".to_string(), "write".to_string()]);
.command_from_str("/read/write")?;

let delegation = builder.try_build()?;

Expand Down Expand Up @@ -485,7 +486,7 @@ mod tests {
issuer: iss,
audience: aud,
subject: DelegatedSubject::Any,
command: Command::new(vec!["/".to_string()]),
command: Command::parse("/")?,
policy: vec![],
expiration: None,
not_before: None,
Expand Down
20 changes: 16 additions & 4 deletions ucan/src/delegation/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use super::{policy::predicate::Predicate, subject::DelegatedSubject};
use crate::{
command::Command,
command::{Command, CommandParseError},
crypto::nonce::Nonce,
did::{Did, DidSigner},
envelope::{Envelope, EnvelopePayload},
Expand Down Expand Up @@ -138,13 +138,13 @@ impl<
}
}

/// Sets the command of the [`Delegation`].
pub fn command(self, command: Vec<String>) -> DelegationBuilder<D, Audience, Subject, Command> {
/// Sets the command of the [`Delegation`] from a pre-validated [`Command`].
pub fn command(self, command: Command) -> DelegationBuilder<D, Audience, Subject, Command> {
DelegationBuilder {
issuer: self.issuer,
audience: self.audience,
subject: self.subject,
command: Command::new(command),
command,
policy: self.policy,
expiration: self.expiration,
not_before: self.not_before,
Expand All @@ -154,6 +154,18 @@ impl<
}
}

/// Parses a command string and sets it on the [`Delegation`].
///
/// # Errors
///
/// Returns [`CommandParseError`] if the command string is invalid.
pub fn command_from_str(
self,
s: &str,
) -> Result<DelegationBuilder<D, Audience, Subject, Command>, CommandParseError> {
Ok(self.command(Command::parse(s)?))
}

/// Sets the policy of the [`Delegation`].
#[must_use]
pub fn policy(self, policy: Vec<Predicate>) -> Self {
Expand Down
87 changes: 74 additions & 13 deletions ucan/src/delegation/policy/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use arbitrary::{self, Arbitrary, Unstructured};
#[cfg(any(test, feature = "test_utils"))]
use crate::ipld::InternalIpld;

/// Validtor for [`Ipld`] values.
/// Validator for [`Ipld`] values.
#[derive(Debug, Clone, PartialEq)]
pub enum Predicate {
/// Selector equality check
Expand All @@ -37,7 +37,7 @@ pub enum Predicate {
/// Selector less than or equal check
LessThanOrEqual(Select<Number>, Number),

/// Seelctor `like` matcher check (glob patterns)
/// Selector `like` matcher check (glob patterns)
Like(Select<String>, String),

/// Negation
Expand All @@ -51,7 +51,7 @@ pub enum Predicate {

/// Universal quantification over a collection
///
/// "For all elements of a collection" (∀x ∈ xs) the precicate must hold
/// "For all elements of a collection" (∀x ∈ xs) the predicate must hold
All(Select<Collection>, Box<Predicate>),

/// Existential quantification over a collection
Expand Down Expand Up @@ -111,10 +111,18 @@ impl Serialize for Predicate {
triple.end()
}
Self::Not(inner) => {
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&"not")?;
tuple.serialize_element(inner)?;
tuple.end()
if let Predicate::Equal(lhs, rhs) = inner.as_ref() {
let mut triple = serializer.serialize_tuple(3)?;
triple.serialize_element(&"!=")?;
triple.serialize_element(lhs)?;
triple.serialize_element(rhs)?;
triple.end()
} else {
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&"not")?;
tuple.serialize_element(inner)?;
tuple.end()
}
Comment thread
expede marked this conversation as resolved.
}
Self::And(inner) => {
let mut tuple = serializer.serialize_tuple(2)?;
Expand Down Expand Up @@ -178,6 +186,13 @@ impl<'de> Deserialize<'de> for Predicate {
de::Error::invalid_length(2, &"expected an Ipld value")
})?,
)),
"!=" => Ok(Predicate::Not(Box::new(Predicate::Equal(
seq.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &"expected a selector"))?,
seq.next_element()?.ok_or_else(|| {
de::Error::invalid_length(2, &"expected an Ipld value")
})?,
)))),
">" => Ok(Predicate::GreaterThan(
seq.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &"expected a selector"))?,
Expand Down Expand Up @@ -241,7 +256,8 @@ impl<'de> Deserialize<'de> for Predicate {
_ => Err(de::Error::unknown_variant(
&op,
&[
"==", ">", ">=", "<", "<=", "like", "not", "and", "or", "all", "any",
"==", "!=", ">", ">=", "<", "<=", "like", "not", "and", "or", "all",
"any",
],
)),
}
Expand Down Expand Up @@ -592,7 +608,7 @@ pub enum FromIpldError {

/// Invalid String selector.
#[error("Invalid String selector {0:?}")]
InvalidStringSelector(<Select<Collection> as FromStr>::Err),
InvalidStringSelector(<Select<String> as FromStr>::Err),

/// Cannot parse [`Number`].
#[error("Cannot parse Number {0:?}")]
Expand Down Expand Up @@ -642,10 +658,23 @@ impl From<Predicate> for Ipld {
lhs.into(),
rhs.into(),
]),
Predicate::Not(inner) => {
let unboxed = *inner;
Ipld::List(vec![Ipld::String("not".to_string()), unboxed.into()])
}
Predicate::Not(inner) => match *inner {
Predicate::Equal(lhs, rhs) => {
Ipld::List(vec![Ipld::String("!=".to_string()), lhs.into(), rhs])
}
other @ (Predicate::GreaterThan(_, _)
| Predicate::GreaterThanOrEqual(_, _)
| Predicate::LessThan(_, _)
| Predicate::LessThanOrEqual(_, _)
| Predicate::Like(_, _)
| Predicate::Not(_)
| Predicate::And(_)
| Predicate::Or(_)
| Predicate::All(_, _)
| Predicate::Any(_, _)) => {
Ipld::List(vec![Ipld::String("not".to_string()), other.into()])
}
},
Predicate::And(inner) => {
let inner_ipld: Vec<Ipld> = inner.into_iter().map(Into::into).collect();
vec![Ipld::String("and".to_string()), inner_ipld.into()].into()
Expand Down Expand Up @@ -1392,4 +1421,36 @@ mod tests {
Ok(())
}
}

mod roundtrip {
use super::*;

#[test_log::test]
fn test_not_equal_dagcbor_roundtrip() -> TestResult {
let pred = Predicate::Not(Box::new(Predicate::Equal(
Select::from_str(".foo")?,
Ipld::Integer(42),
)));

let cbor = serde_ipld_dagcbor::to_vec(&pred)?;
let back: Predicate = serde_ipld_dagcbor::from_slice(&cbor)?;

assert_eq!(back, pred);
Ok(())
}

#[test_log::test]
fn test_not_equal_ipld_roundtrip() -> TestResult {
let pred = Predicate::Not(Box::new(Predicate::Equal(
Select::from_str(".bar")?,
Ipld::String("hello".into()),
)));

let ipld: Ipld = pred.clone().into();
let back = Predicate::try_from(ipld)?;

assert_eq!(back, pred);
Ok(())
}
}
}
Loading
Loading