Skip to content

Commit

Permalink
feat: add Sign trait (#459)
Browse files Browse the repository at this point in the history
background:
apache/opendal#4960 (comment)

this PR introduced a Sign trait, which is suitable on the cases like
when we want customize signing logics like
apache/iceberg-rust#506
  • Loading branch information
flaneur2020 authored Aug 6, 2024
1 parent 6ffbb8a commit d85f554
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/aws/v4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::hash::hex_hmac_sha256;
use crate::hash::hex_sha256;
use crate::hash::hmac_sha256;
use crate::request::SignableRequest;
use crate::sign::Sign;
use crate::time::format_date;
use crate::time::format_iso8601;
use crate::time::now;
Expand Down Expand Up @@ -213,6 +214,24 @@ impl Signer {
}
}

#[async_trait::async_trait]
impl Sign for Signer {
type Credential = Credential;

async fn sign(&self, req: &mut http::request::Parts, cred: &Self::Credential) -> Result<()> {
self.sign(req, cred)
}

async fn sign_query(
&self,
req: &mut http::request::Parts,
expires: Duration,
cred: &Self::Credential,
) -> Result<()> {
self.sign_query(req, expires, cred)
}
}

fn canonical_request_string(ctx: &mut SigningContext) -> Result<String> {
// 256 is specially chosen to avoid reallocation for most requests.
let mut f = String::with_capacity(256);
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,6 @@ mod ctx;
mod dirs;
mod hash;
mod request;
mod sign;
mod time;
pub use sign::Sign;
74 changes: 74 additions & 0 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,80 @@ impl SignableRequest for reqwest::Request {
}
}

/// Implement `SignableRequest` for [`http::request::Parts`]
impl SignableRequest for http::request::Parts {
fn build(&mut self) -> Result<SigningContext> {
let uri = mem::take(&mut self.uri).into_parts();
let paq = uri
.path_and_query
.unwrap_or_else(|| PathAndQuery::from_static("/"));

Ok(SigningContext {
method: self.method.clone(),
scheme: uri.scheme.unwrap_or(Scheme::HTTP),
authority: uri
.authority
.ok_or_else(|| anyhow!("request without authority is invalid for signing"))?,
path: paq.path().to_string(),
query: paq
.query()
.map(|v| {
form_urlencoded::parse(v.as_bytes())
.map(|(k, v)| (k.into_owned(), v.into_owned()))
.collect()
})
.unwrap_or_default(),

// Take the headers out of the request to avoid copy.
// We will return it back when apply the context.
headers: mem::take(&mut self.headers),
})
}

fn apply(&mut self, mut ctx: SigningContext) -> Result<()> {
let query_size = ctx.query_size();

// Return headers back.
mem::swap(&mut self.headers, &mut ctx.headers);

let mut parts = mem::take(&mut self.uri).into_parts();
// Return scheme bakc.
parts.scheme = Some(ctx.scheme);
// Return authority back.
parts.authority = Some(ctx.authority);
// Build path and query.
parts.path_and_query = {
let paq = if query_size == 0 {
ctx.path
} else {
let mut s = ctx.path;
s.reserve(query_size + 1);

s.push('?');
for (i, (k, v)) in ctx.query.iter().enumerate() {
if i > 0 {
s.push('&');
}

s.push_str(k);
if !v.is_empty() {
s.push('=');
s.push_str(v);
}
}

s
};

Some(PathAndQuery::from_str(&paq)?)
};

self.uri = Uri::from_parts(parts)?;

Ok(())
}
}

/// Implement `SignableRequest` for [`reqwest::blocking::Request`]
#[cfg(feature = "reqwest_blocking_request")]
impl SignableRequest for reqwest::blocking::Request {
Expand Down
25 changes: 25 additions & 0 deletions src/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::time::Duration;

/// Sign will be a trait that implement by all services providers supported by reqsign, user
/// can implement their own too. It will take http::request::Parts and services Credential
/// to perform sign over this request.
#[async_trait::async_trait]
pub trait Sign: Send + Sync + Unpin + 'static {
/// Credential type for this signer.
type Credential: Send + Sync + Unpin + 'static;

/// Sign the request with headers.
async fn sign(
&self,
req: &mut http::request::Parts,
cred: &Self::Credential,
) -> anyhow::Result<()>;

/// Sign the request with query parameters.
async fn sign_query(
&self,
req: &mut http::request::Parts,
expires: Duration,
cred: &Self::Credential,
) -> anyhow::Result<()>;
}

0 comments on commit d85f554

Please sign in to comment.