Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

Implement orb sync subcommand #107

Merged
merged 3 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 1 addition & 10 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[workspace]
members = [
"noosphere-cbor",
"noosphere-storage",
"noosphere-collections",
"noosphere",
Expand Down
1 change: 1 addition & 0 deletions rust/noosphere-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow = "^1"
cid = "~0.8"
url = "^2"
serde = "^1"
serde_urlencoded = "~0.7"
tracing = "~0.1"
noosphere = { path = "../noosphere" }
noosphere-storage = { path = "../noosphere-storage" }
Expand Down
124 changes: 93 additions & 31 deletions rust/noosphere-api/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{
use anyhow::Result;
use cid::Cid;
use libipld_cbor::DagCborCodec;
use noosphere::authority::{SphereAction, SphereReference};

use noosphere::authority::{Authorization, SphereAction, SphereReference};
use noosphere_storage::encoding::{block_deserialize, block_serialize};
use reqwest::{header::HeaderMap, Body};
use ucan::{
Expand All @@ -29,7 +30,7 @@ where
pub sphere_identity: String,
pub api_base: Url,
pub credential: &'a K,
pub authorization: Ucan,
pub authorization: Authorization,
pub store: S,
client: reqwest::Client,
}
Expand All @@ -43,23 +44,44 @@ where
sphere_identity: &str,
api_base: &Url,
credential: &'a K,
authorization: Ucan,
authorization: &Authorization,
did_parser: &mut DidParser,
store: S,
) -> Result<Client<'a, K, S>> {
debug!("Initializing Noosphere API client");
debug!("Client represents sphere {}", sphere_identity);
debug!("Client targetting API at {}", api_base);

let client = reqwest::Client::new();

let mut url = api_base.clone();
url.set_path(&Route::Did.to_string());

let gateway_identity = client.get(url).send().await?.text().await?;

let mut url = api_base.clone();
url.set_path(&Route::Identify.to_string());

let client = reqwest::Client::new();
let (jwt, ucan_headers) = Self::make_bearer_token(
&gateway_identity,
credential,
authorization,
&Capability {
with: With::Resource {
kind: Resource::Scoped(SphereReference {
did: sphere_identity.to_string(),
}),
},
can: SphereAction::Fetch,
},
&store,
)
.await?;

let identify_response: IdentifyResponse = client
.get(url)
.bearer_auth(authorization.encode()?)
.bearer_auth(jwt)
.headers(ucan_headers)
.send()
.await?
.json()
Expand All @@ -77,52 +99,76 @@ where
sphere_identity: sphere_identity.into(),
api_base: api_base.clone(),
credential,
authorization,
authorization: authorization.clone(),
store,
client,
})
}

async fn make_bearer_token(
&self,
gateway_identity: &str,
credential: &'a K,
authorization: &Authorization,
capability: &Capability<SphereReference, SphereAction>,
store: &S,
) -> Result<(String, HeaderMap)> {
let ucan = UcanBuilder::default()
.issued_by(self.credential)
.for_audience(&self.session.sphere_identity)
let mut signable = UcanBuilder::default()
.issued_by(credential)
.for_audience(gateway_identity)
.with_lifetime(120)
.claiming_capability(capability)
.witnessed_by(&self.authorization)
.with_nonce()
.build()?
.sign()
.await?;
.build()?;

// TODO: We should integrate a helper for this kind of stuff into rs-ucan
let mut proofs_to_search: Vec<String> = ucan.proofs().clone();
let mut ucan_headers = HeaderMap::new();

debug!("Making bearer token... {:?}", proofs_to_search);
while let Some(cid_string) = proofs_to_search.pop() {
let cid = Cid::from_str(cid_string.as_str())?;
let jwt = self.store.require_token(&cid).await?;
let ucan = Ucan::try_from_token_string(&jwt)?;
let authorization_cid = Cid::try_from(authorization)?;

match authorization.resolve_ucan(store).await {
Ok(ucan) => {
// TODO(ucan-wg/rs-ucan#37): We should integrate a helper for this kind of stuff into rs-ucan
let mut proofs_to_search: Vec<String> = ucan.proofs().clone();

debug!("Making bearer token... {:?}", proofs_to_search);
while let Some(cid_string) = proofs_to_search.pop() {
let cid = Cid::from_str(cid_string.as_str())?;
let jwt = store.require_token(&cid).await?;
let ucan = Ucan::try_from_token_string(&jwt)?;

debug!("Adding UCAN header for {}", cid);

proofs_to_search.extend(ucan.proofs().clone().into_iter());
ucan_headers.append("ucan", format!("{} {}", cid, jwt).parse()?);
}

ucan_headers.append(
"ucan",
format!("{} {}", authorization_cid, ucan.encode()?).parse()?,
);
}
_ => {
warn!("Unable to resolve authorization to a UCAN; it will be used as a blind proof")
}
};

debug!("Adding UCAN header for {}", cid);
// TODO(ucan-wg/rs-ucan#32): This is kind of a hack until we can add proofs by CID
signable
.proofs
.push(Cid::try_from(authorization)?.to_string());

proofs_to_search.extend(ucan.proofs().clone().into_iter());
ucan_headers.append("ucan", format!("{} {}", cid, jwt).parse()?);
}
let jwt = signable.sign().await?.encode()?;

// TODO: It is inefficient to send the same UCANs with every request,
// we should probably establish a conventional flow for syncing UCANs
// this way only once when pairing a gateway. For now, this is about the
// same efficiency as what we had before when UCANs were all inlined to
// a single token.
Ok((ucan.encode()?, ucan_headers))
Ok((jwt, ucan_headers))
}

pub async fn fetch(&self, params: &FetchParameters) -> Result<FetchResponse> {
// println!("FOOBAR");
// println!("{:?}", serde_urlencoded::to_string(params)?);
let url = Url::try_from(RouteUrl(&self.api_base, Route::Fetch, Some(params)))?;
debug!("Client fetching blocks from {}", url);
let capability = Capability {
Expand All @@ -134,7 +180,14 @@ where
can: SphereAction::Fetch,
};

let (token, ucan_headers) = self.make_bearer_token(&capability).await?;
let (token, ucan_headers) = Self::make_bearer_token(
&self.session.gateway_identity,
self.credential,
&self.authorization,
&capability,
&self.store,
)
.await?;

let bytes = self
.client
Expand Down Expand Up @@ -166,11 +219,18 @@ where
can: SphereAction::Push,
};

let (token, ucan_headers) = self.make_bearer_token(&capability).await?;
let (token, ucan_headers) = Self::make_bearer_token(
&self.session.gateway_identity,
self.credential,
&self.authorization,
&capability,
&self.store,
)
.await?;

let (_, push_body_bytes) = block_serialize::<DagCborCodec, _>(push_body)?;

Ok(self
let bytes = self
.client
.put(url)
.bearer_auth(token)
Expand All @@ -179,7 +239,9 @@ where
.body(Body::from(push_body_bytes))
.send()
.await?
.json()
.await?)
.bytes()
.await?;

block_deserialize::<DagCborCodec, _>(bytes.as_ref())
}
}
Loading