Skip to content

Commit

Permalink
move Manifest to file, impl Orbit::new
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles Cunningham authored and Charles Cunningham committed Feb 22, 2022
1 parent 70b3e1b commit a1a60cf
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 222 deletions.
7 changes: 4 additions & 3 deletions src/auth.rs
@@ -1,6 +1,7 @@
use crate::cas::CidWrap;
use crate::config;
use crate::orbit::{create_orbit, hash_same, load_orbit, resolve, AuthTokens, Orbit};
use crate::manifest::Manifest;
use crate::orbit::{create_orbit, hash_same, load_orbit, AuthTokens, Orbit};
use crate::relay::RelayNode;
use anyhow::Result;
use ipfs_embed::{Keypair, Multiaddr, PeerId};
Expand Down Expand Up @@ -220,15 +221,15 @@ impl<'r> FromRequest<'r> for CreateAuthWrapper {
// Create actions dont have an existing orbit to authorize against, it's a node policy
// TODO have policy config, for now just be very permissive :shrug:
Action::Create { .. } => {
let md = match resolve(token.target_orbit()).await {
let md = match Manifest::resolve_dyn(token.target_orbit(), None).await {
Ok(Some(md)) => md,
Ok(None) => {
return Outcome::Failure((
Status::NotFound,
anyhow!("Orbit Manifest Doesnt Exist"),
))
}
Err(e) => return Outcome::Failure((Status::InternalServerError, e)),
Err(e) => return Outcome::Failure((Status::InternalServerError, anyhow!(e))),
};

match md.authorize(&token).await {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -14,6 +14,7 @@ pub mod cas;
pub mod codec;
pub mod config;
pub mod ipfs;
pub mod manifest;
pub mod orbit;
pub mod relay;
pub mod routes;
Expand Down
254 changes: 254 additions & 0 deletions src/manifest.rs
@@ -0,0 +1,254 @@
use libipld::cid::{multibase::Base, Cid, Error as CidError};
use libp2p::{Multiaddr, PeerId};

use crate::{auth::AuthorizationPolicy, orbit::AuthTokens};
use ssi::{
did::{Document, RelativeDIDURL, Service, ServiceEndpoint, VerificationMethod, DIDURL},
did_resolve::DIDResolver,
one_or_many::OneOrMany,
};
use std::{
convert::{TryFrom, TryInto},
str::FromStr,
};
use thiserror::Error;

/// An implementation of an Orbit Manifest.
///
/// Orbit Manifests are [DID Documents](https://www.w3.org/TR/did-spec-registries/#did-methods) used directly as the root of a capabilities
/// authorization framework. This enables Orbits to be managed using independant DID lifecycle management tools.
#[derive(Clone, Debug)]
pub struct Manifest {
id: String,
delegators: Vec<DIDURL>,
invokers: Vec<DIDURL>,
bootstrap_peers: Vec<BootstrapPeer>,
}

impl Manifest {
/// ID of the Orbit, usually a DID
pub fn id(&self) -> &str {
&self.id
}

/// The set of Peers discoverable from the Orbit Manifest.
pub fn bootstrap_peers(&self) -> &[BootstrapPeer] {
&self.bootstrap_peers
}

/// The set of [Verification Methods](https://www.w3.org/TR/did-core/#verification-methods) who are authorized to delegate any capability.
pub fn delegators(&self) -> &[DIDURL] {
&self.delegators
}

/// The set of [Verification Methods](https://www.w3.org/TR/did-core/#verification-methods) who are authorized to invoke any capability.
pub fn invokers(&self) -> &[DIDURL] {
&self.invokers
}

/// Creates a Kepler URI for a given CID in the IPFS Store
pub fn make_uri(&self, cid: &Cid) -> Result<String, CidError> {
Ok(format!(
"kepler:{}/ipfs/{}",
self.id(),
cid.to_string_of_base(Base::Base58Btc)?
))
}

pub async fn resolve_dyn(
id: &str,
resolver: Option<&dyn DIDResolver>,
) -> Result<Option<Self>, ResolutionError> {
resolve_dyn(id, resolver).await
}

pub async fn resolve<D: DIDResolver>(
id: &str,
resolver: &D,
) -> Result<Option<Self>, ResolutionError> {
resolve(id, resolver).await
}
}

#[derive(Clone, Debug, Hash)]
pub struct BootstrapPeer {
pub id: PeerId,
pub addrs: Vec<Multiaddr>,
}

impl From<Document> for Manifest {
fn from(
Document {
id,
capability_delegation,
capability_invocation,
verification_method,
service,
..
}: Document,
) -> Self {
Self {
delegators: capability_delegation
.or_else(|| verification_method.clone())
.unwrap_or_else(|| vec![])
.into_iter()
.map(|vm| id_from_vm(&id, vm))
.collect(),
invokers: capability_invocation
.or(verification_method)
.unwrap_or_else(|| vec![])
.into_iter()
.map(|vm| id_from_vm(&id, vm))
.collect(),
bootstrap_peers: service
.unwrap_or_else(|| vec![])
.into_iter()
.filter_map(|s| s.try_into().ok())
.collect(),
id,
}
}
}

#[derive(Error, Debug)]
pub enum ResolutionError {
#[error("DID Resolution Error: {0}")]
Resolver(String),
#[error("DID Deactivated")]
Deactivated,
}

pub async fn resolve_dyn(
id: &str,
resolver: Option<&dyn DIDResolver>,
) -> Result<Option<Manifest>, ResolutionError> {
let (md, doc, doc_md) = resolver
.unwrap_or(didkit::DID_METHODS.to_resolver())
.resolve(id, &Default::default())
.await;

match (md.error, doc, doc_md.and_then(|d| d.deactivated)) {
(Some(e), _, _) => Err(ResolutionError::Resolver(e)),
(_, _, Some(true)) => Err(ResolutionError::Deactivated),
(_, None, _) => Ok(None),
(None, Some(d), None | Some(false)) => Ok(Some(d.into())),
}
}

pub async fn resolve<D: DIDResolver>(
id: &str,
resolver: &D,
) -> Result<Option<Manifest>, ResolutionError> {
let (md, doc, doc_md) = resolver.resolve(id, &Default::default()).await;

match (md.error, doc, doc_md.and_then(|d| d.deactivated)) {
(Some(e), _, _) => Err(ResolutionError::Resolver(e)),
(_, _, Some(true)) => Err(ResolutionError::Deactivated),
(_, None, _) => Ok(None),
(None, Some(d), None | Some(false)) => Ok(Some(d.into())),
}
}

#[derive(Error, Debug)]
pub enum ServicePeerConversionError {
#[error(transparent)]
IdParse(<PeerId as FromStr>::Err),
#[error("Missing KeplerOrbitPeer type string")]
WrongType,
}

impl TryFrom<Service> for BootstrapPeer {
type Error = ServicePeerConversionError;
fn try_from(s: Service) -> Result<Self, Self::Error> {
if s.type_.any(|t| t == "KeplerOrbitPeer") {
Ok(Self {
id: s.id[1..].parse().map_err(|e| Self::Error::IdParse(e))?,
addrs: s
.service_endpoint
.unwrap_or(OneOrMany::Many(vec![]))
.into_iter()
.filter_map(|e| match e {
ServiceEndpoint::URI(a) => {
a.strip_prefix("multiaddr:").and_then(|a| a.parse().ok())
}
_ => None,
})
.collect(),
})
} else {
Err(Self::Error::WrongType)
}
}
}

impl From<BootstrapPeer> for Service {
fn from(p: BootstrapPeer) -> Self {
Self {
id: format!("#{}", p.id),
type_: OneOrMany::One("KeplerOrbitPeer".into()),
service_endpoint: match p.addrs.len() {
0 => None,
1 => Some(OneOrMany::One(ServiceEndpoint::URI(format!(
"multiaddr:{}",
p.addrs[0]
)))),
_ => Some(OneOrMany::Many(
p.addrs
.into_iter()
.map(|a| ServiceEndpoint::URI(format!("multiaddr:{}", a)))
.collect(),
)),
},
property_set: Default::default(),
}
}
}

fn id_from_vm(did: &str, vm: VerificationMethod) -> DIDURL {
match vm {
VerificationMethod::DIDURL(d) => d,
VerificationMethod::RelativeDIDURL(f) => f.to_absolute(did),
VerificationMethod::Map(m) => {
if let Ok(abs_did_url) = DIDURL::from_str(&m.id) {
abs_did_url
} else if let Ok(rel_did_url) = RelativeDIDURL::from_str(&m.id) {
rel_did_url.to_absolute(did)
} else {
// HACK well-behaved did methods should not allow id's which lead to this path
DIDURL {
did: m.id,
..Default::default()
}
}
}
}
}

#[rocket::async_trait]
impl AuthorizationPolicy<AuthTokens> for Manifest {
async fn authorize(&self, auth_token: &AuthTokens) -> anyhow::Result<()> {
match auth_token {
AuthTokens::Tezos(token) => self.authorize(token).await,
AuthTokens::ZCAP(token) => self.authorize(token.as_ref()).await,
AuthTokens::SIWEDelegated(token) => self.authorize(token.as_ref()).await,
AuthTokens::SIWEZcapDelegated(token) => self.authorize(token.as_ref()).await,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use didkit::DID_METHODS;
use ssi::{did::Source, jwk::JWK};

#[test]
async fn basic_manifest() {
let j = JWK::generate_secp256k1().unwrap();
let did = DID_METHODS
.generate(&Source::KeyAndPattern(&j, "pkh:tz"))
.unwrap();

let _md = Manifest::resolve_dyn(&did, None).await.unwrap().unwrap();
}
}

0 comments on commit a1a60cf

Please sign in to comment.