Compare commits

..

17 commits
0.5.2 ... main

Author SHA1 Message Date
Nutomic 6edbc06a78
Convert content-type to lowercase for comparison (#114)
* Convert content-type to lowercase for comparison

* rust 1.78

* clippy priority

* upgrade dep
2024-06-11 11:16:04 +02:00
Felix Ableitner 175b22006b Revert "Version 0.5.7"
This reverts commit a251140952.
2024-06-06 00:02:41 +02:00
Felix Ableitner e118e4f240 Reapply "Retry activity send in case of timeout or rate limit (#102)"
This reverts commit 32da1b747c.
2024-06-06 00:02:36 +02:00
Felix Ableitner a251140952 Version 0.5.7 2024-06-05 23:09:53 +02:00
Felix Ableitner 32da1b747c Revert "Retry activity send in case of timeout or rate limit (#102)"
This reverts commit 5402bc9c19.
2024-06-05 23:05:26 +02:00
Felix Ableitner 16844f048a Version 0.5.6 2024-05-06 11:09:47 +02:00
Nutomic cf1f84993b
Make response content-type check case insensitive (#111)
* Make response content-type check case insensitive

For wordpress compat

* cleaner

* clippy

* fmt

* fmt
2024-05-06 11:09:23 +02:00
Felix Ableitner 24afad7abc Version 0.5.5 2024-05-02 13:06:22 +02:00
Nutomic c48de9e944
Upgrade dependencies (#110) 2024-05-02 06:58:08 -04:00
Nutomic be69efdee3
Require signed digest when verifying signatures (#109) 2024-05-02 10:58:56 +02:00
Nutomic ddc455510b
Dont crash when calling is_local_url() without domain (#108) 2024-05-02 10:58:33 +02:00
Felix Ableitner ee268405f7 Version 0.5.4 2024-04-10 11:32:14 +02:00
Nutomic 54e8a1145f
Add function ObjectId.is_local (#106)
* Add function ObjectId.is_local

* add test

* add test
2024-04-10 11:31:55 +02:00
Felix Ableitner 779313ac22 Version 0.5.3 2024-04-09 11:30:43 +02:00
Nutomic 7def01a19a
Avoid running ci checks twice (#105)
* Avoid running ci checks twice

* upgrade rust

* move clippy config to cargo.toml
2024-04-09 11:28:57 +02:00
Nutomic a2ac97db98
Allow fetching from local domain in case it redirects to remote (#104)
* Allow fetching from local domain in case it redirects to remote

* clippy

* fix lemmy tests
2024-04-09 11:28:22 +02:00
Nutomic 5402bc9c19
Retry activity send in case of timeout or rate limit (#102) 2024-04-09 10:38:08 +02:00
15 changed files with 224 additions and 87 deletions

View file

@ -1,54 +1,56 @@
pipeline: variables:
- &rust_image "rust:1.78-bullseye"
steps:
cargo_fmt: cargo_fmt:
image: rustdocker/rust:nightly image: rustdocker/rust:nightly
commands: commands:
- /root/.cargo/bin/cargo fmt -- --check - /root/.cargo/bin/cargo fmt -- --check
when:
cargo_check: - event: pull_request
image: rust:1.70-bullseye
environment:
CARGO_HOME: .cargo
commands:
- cargo check --all-features --all-targets
cargo_clippy: cargo_clippy:
image: rust:1.70-bullseye image: *rust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- rustup component add clippy - rustup component add clippy
- cargo clippy --all-targets --all-features -- - cargo clippy --all-targets --all-features
-D warnings -D deprecated -D clippy::perf -D clippy::complexity when:
-D clippy::dbg_macro -D clippy::inefficient_to_string - event: pull_request
-D clippy::items-after-statements -D clippy::implicit_clone
-D clippy::wildcard_imports -D clippy::cast_lossless
-D clippy::manual_string_new -D clippy::redundant_closure_for_method_calls
- cargo clippy --all-features -- -D clippy::unwrap_used
cargo_test: cargo_test:
image: rust:1.70-bullseye image: *rust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- cargo test --all-features --no-fail-fast - cargo test --all-features --no-fail-fast
when:
- event: pull_request
cargo_doc: cargo_doc:
image: rust:1.70-bullseye image: *rust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- cargo doc --all-features - cargo doc --all-features
when:
- event: pull_request
cargo_run_actix_example: cargo_run_actix_example:
image: rust:1.70-bullseye image: *rust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- cargo run --example local_federation actix-web - cargo run --example local_federation actix-web
when:
- event: pull_request
cargo_run_axum_example: cargo_run_axum_example:
image: rust:1.70-bullseye image: *rust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- cargo run --example local_federation axum - cargo run --example local_federation axum
when:
- event: pull_request

View file

@ -1,6 +1,6 @@
[package] [package]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.5.2" version = "0.5.6"
edition = "2021" edition = "2021"
description = "High-level Activitypub framework" description = "High-level Activitypub framework"
keywords = ["activitypub", "activitystreams", "federation", "fediverse"] keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
@ -14,22 +14,39 @@ actix-web = ["dep:actix-web"]
axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"] axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"]
diesel = ["dep:diesel"] diesel = ["dep:diesel"]
[lints.rust]
warnings = "deny"
deprecated = "deny"
[lints.clippy]
perf = { level = "deny", priority = -1 }
complexity = { level = "deny", priority = -1 }
dbg_macro = "deny"
inefficient_to_string = "deny"
items-after-statements = "deny"
implicit_clone = "deny"
wildcard_imports = "deny"
cast_lossless = "deny"
manual_string_new = "deny"
redundant_closure_for_method_calls = "deny"
unwrap_used = "deny"
[dependencies] [dependencies]
chrono = { version = "0.4.34", features = ["clock"], default-features = false } chrono = { version = "0.4.38", features = ["clock"], default-features = false }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.200", features = ["derive"] }
async-trait = "0.1.77" async-trait = "0.1.80"
url = { version = "2.5.0", features = ["serde"] } url = { version = "2.5.0", features = ["serde"] }
serde_json = { version = "1.0.114", features = ["preserve_order"] } serde_json = { version = "1.0.116", features = ["preserve_order"] }
reqwest = { version = "0.11.24", features = ["json", "stream"] } reqwest = { version = "0.11.27", features = ["json", "stream"] }
reqwest-middleware = "0.2.4" reqwest-middleware = "0.2.5"
tracing = "0.1.40" tracing = "0.1.40"
base64 = "0.21.7" base64 = "0.22.1"
openssl = "0.10.64" openssl = "0.10.64"
once_cell = "1.19.0" once_cell = "1.19.0"
http = "0.2.11" http = "0.2.12"
sha2 = "0.10.8" sha2 = "0.10.8"
thiserror = "1.0.57" thiserror = "1.0.59"
derive_builder = "0.12.0" derive_builder = "0.20.0"
itertools = "0.12.1" itertools = "0.12.1"
dyn-clone = "1.0.17" dyn-clone = "1.0.17"
enum_delegate = "0.2.0" enum_delegate = "0.2.0"
@ -40,20 +57,20 @@ http-signature-normalization-reqwest = { version = "0.10.0", default-features =
"default-spawner", "default-spawner",
] } ] }
http-signature-normalization = "0.7.0" http-signature-normalization = "0.7.0"
bytes = "1.5.0" bytes = "1.6.0"
futures-core = { version = "0.3.30", default-features = false } futures-core = { version = "0.3.30", default-features = false }
pin-project-lite = "0.2.13" pin-project-lite = "0.2.14"
activitystreams-kinds = "0.3.0" activitystreams-kinds = "0.3.0"
regex = { version = "1.10.3", default-features = false, features = ["std", "unicode-case"] } regex = { version = "1.10.5", default-features = false, features = ["std", "unicode"] }
tokio = { version = "1.36.0", features = [ tokio = { version = "1.37.0", features = [
"sync", "sync",
"rt", "rt",
"rt-multi-thread", "rt-multi-thread",
"time", "time",
] } ] }
diesel = { version = "2.1.4", features = ["postgres"], default-features = false, optional = true } diesel = { version = "2.1.6", features = ["postgres"], default-features = false, optional = true }
futures = "0.3.30" futures = "0.3.30"
moka = { version = "0.12.5", features = ["future"] } moka = { version = "0.12.7", features = ["future"] }
# Actix-web # Actix-web
actix-web = { version = "4.5.1", default-features = false, optional = true } actix-web = { version = "4.5.1", default-features = false, optional = true }
@ -65,12 +82,12 @@ axum = { version = "0.6.20", features = [
], default-features = false, optional = true } ], default-features = false, optional = true }
tower = { version = "0.4.13", optional = true } tower = { version = "0.4.13", optional = true }
hyper = { version = "0.14", optional = true } hyper = { version = "0.14", optional = true }
http-body-util = {version = "0.1.0", optional = true } http-body-util = {version = "0.1.1", optional = true }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.80" anyhow = "1.0.82"
rand = "0.8.5" rand = "0.8.5"
env_logger = "0.10.2" env_logger = "0.11.3"
tower-http = { version = "0.5.2", features = ["map-request-body", "util"] } tower-http = { version = "0.5.2", features = ["map-request-body", "util"] }
axum = { version = "0.6.20", features = [ axum = { version = "0.6.20", features = [
"http1", "http1",
@ -78,7 +95,7 @@ axum = { version = "0.6.20", features = [
"query", "query",
], default-features = false } ], default-features = false }
axum-macros = "0.3.8" axum-macros = "0.3.8"
tokio = { version = "1.36.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
[profile.dev] [profile.dev]
strip = "symbols" strip = "symbols"

View file

@ -1,3 +1,5 @@
#![allow(clippy::unwrap_used)]
use crate::{ use crate::{
database::Database, database::Database,
http::{http_get_user, http_post_user_inbox, webfinger}, http::{http_get_user, http_post_user_inbox, webfinger},

View file

@ -21,7 +21,6 @@ pub struct DbPost {
pub text: String, pub text: String,
pub ap_id: ObjectId<DbPost>, pub ap_id: ObjectId<DbPost>,
pub creator: ObjectId<DbUser>, pub creator: ObjectId<DbUser>,
pub local: bool,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
@ -59,7 +58,15 @@ impl Object for DbPost {
} }
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
unimplemented!() Ok(Note {
kind: NoteType::Note,
id: self.ap_id,
content: self.text,
attributed_to: self.creator,
to: vec![public()],
tag: vec![],
in_reply_to: None,
})
} }
async fn verify( async fn verify(
@ -81,7 +88,6 @@ impl Object for DbPost {
text: json.content, text: json.content,
ap_id: json.id.clone(), ap_id: json.id.clone(),
creator: json.attributed_to.clone(), creator: json.attributed_to.clone(),
local: false,
}; };
let mention = Mention { let mention = Mention {

View file

@ -28,6 +28,7 @@ pub async fn new_instance(
.domain(hostname) .domain(hostname)
.signed_fetch_actor(&system_user) .signed_fetch_actor(&system_user)
.app_data(database) .app_data(database)
.url_verifier(Box::new(MyUrlVerifier()))
.debug(true) .debug(true)
.build() .build()
.await?; .await?;

View file

@ -1,3 +1,5 @@
#![allow(clippy::unwrap_used)]
use crate::{ use crate::{
instance::{listen, new_instance, Webserver}, instance::{listen, new_instance, Webserver},
objects::post::DbPost, objects::post::DbPost,

View file

@ -416,6 +416,7 @@ async fn retry<T, E: Display + Debug, F: Future<Output = Result<T, E>>, A: FnMut
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests { mod tests {
use super::*; use super::*;
use crate::http_signatures::generate_actor_keypair; use crate::http_signatures::generate_actor_keypair;

View file

@ -12,14 +12,17 @@ use crate::{
}; };
use bytes::Bytes; use bytes::Bytes;
use futures::StreamExt; use futures::StreamExt;
use http::StatusCode;
use httpdate::fmt_http_date; use httpdate::fmt_http_date;
use itertools::Itertools; use itertools::Itertools;
use openssl::pkey::{PKey, Private}; use openssl::pkey::{PKey, Private};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Response,
};
use reqwest_middleware::ClientWithMiddleware; use reqwest_middleware::ClientWithMiddleware;
use serde::Serialize; use serde::Serialize;
use std::{ use std::{
self,
fmt::{Debug, Display}, fmt::{Debug, Display},
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
@ -90,20 +93,30 @@ impl SendActivityTask {
) )
.await?; .await?;
let response = client.execute(request).await?; let response = client.execute(request).await?;
self.handle_response(response).await
}
match response { /// Based on the HTTP status code determines if an activity was delivered successfully. In that case
o if o.status().is_success() => { /// Ok is returned. Otherwise it returns Err and the activity send should be retried later.
///
/// Equivalent code in mastodon: https://github.com/mastodon/mastodon/blob/v4.2.8/app/helpers/jsonld_helper.rb#L215-L217
async fn handle_response(&self, response: Response) -> Result<(), Error> {
match response.status() {
status if status.is_success() => {
debug!("Activity {self} delivered successfully"); debug!("Activity {self} delivered successfully");
Ok(()) Ok(())
} }
o if o.status().is_client_error() => { status
let text = o.text_limited().await?; if status.is_client_error()
&& status != StatusCode::REQUEST_TIMEOUT
&& status != StatusCode::TOO_MANY_REQUESTS =>
{
let text = response.text_limited().await?;
debug!("Activity {self} was rejected, aborting: {text}"); debug!("Activity {self} was rejected, aborting: {text}");
Ok(()) Ok(())
} }
o => { status => {
let status = o.status(); let text = response.text_limited().await?;
let text = o.text_limited().await?;
Err(Error::Other(format!( Err(Error::Other(format!(
"Activity {self} failure with status {status}: {text}", "Activity {self} failure with status {status}: {text}",
@ -211,11 +224,10 @@ pub(crate) fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{config::FederationConfig, http_signatures::generate_actor_keypair}; use crate::{config::FederationConfig, http_signatures::generate_actor_keypair};
use bytes::Bytes;
use http::StatusCode;
use std::{ use std::{
sync::{atomic::AtomicUsize, Arc}, sync::{atomic::AtomicUsize, Arc},
time::Instant, time::Instant,
@ -289,4 +301,48 @@ mod tests {
info!("Queue Sent: {:?}", start.elapsed()); info!("Queue Sent: {:?}", start.elapsed());
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_handle_response() {
let keypair = generate_actor_keypair().unwrap();
let message = SendActivityTask {
actor_id: "http://localhost:8001".parse().unwrap(),
activity_id: "http://localhost:8001/activity".parse().unwrap(),
activity: "{}".into(),
inbox: "http://localhost:8001".parse().unwrap(),
private_key: keypair.private_key().unwrap(),
http_signature_compat: true,
};
let res = |status| {
http::Response::builder()
.status(status)
.body(vec![])
.unwrap()
.into()
};
assert!(message.handle_response(res(StatusCode::OK)).await.is_ok());
assert!(message
.handle_response(res(StatusCode::BAD_REQUEST))
.await
.is_ok());
assert!(message
.handle_response(res(StatusCode::MOVED_PERMANENTLY))
.await
.is_err());
assert!(message
.handle_response(res(StatusCode::REQUEST_TIMEOUT))
.await
.is_err());
assert!(message
.handle_response(res(StatusCode::TOO_MANY_REQUESTS))
.await
.is_err());
assert!(message
.handle_response(res(StatusCode::INTERNAL_SERVER_ERROR))
.await
.is_err());
}
} }

View file

@ -45,6 +45,7 @@ where
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test { mod test {
use super::*; use super::*;
use crate::{ use crate::{

View file

@ -174,11 +174,17 @@ impl<T: Clone> FederationConfig<T> {
/// Returns true if the url refers to this instance. Handles hostnames like `localhost:8540` for /// Returns true if the url refers to this instance. Handles hostnames like `localhost:8540` for
/// local debugging. /// local debugging.
pub(crate) fn is_local_url(&self, url: &Url) -> bool { pub(crate) fn is_local_url(&self, url: &Url) -> bool {
let mut domain = url.host_str().expect("id has domain").to_string(); match url.host_str() {
if let Some(port) = url.port() { Some(domain) => {
domain = format!("{}:{}", domain, port); let domain = if let Some(port) = url.port() {
format!("{}:{}", domain, port)
} else {
domain.to_string()
};
domain == self.domain
}
None => false,
} }
domain == self.domain
} }
/// Returns the local domain /// Returns the local domain
@ -341,3 +347,34 @@ impl<T: Clone> FederationMiddleware<T> {
FederationMiddleware(config) FederationMiddleware(config)
} }
} }
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
use super::*;
async fn config() -> FederationConfig<i32> {
FederationConfig::builder()
.domain("example.com")
.app_data(1)
.build()
.await
.unwrap()
}
#[tokio::test]
async fn test_url_is_local() -> Result<(), Error> {
let config = config().await;
assert!(config.is_local_url(&Url::parse("http://example.com")?));
assert!(!config.is_local_url(&Url::parse("http://other.com")?));
// ensure that missing domain doesnt cause crash
assert!(!config.is_local_url(&Url::parse("http://127.0.0.1")?));
Ok(())
}
#[tokio::test]
async fn test_get_domain() {
let config = config().await;
assert_eq!("example.com", config.domain());
}
}

View file

@ -53,26 +53,35 @@ pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
url: &Url, url: &Url,
data: &Data<T>, data: &Data<T>,
) -> Result<FetchObjectResponse<Kind>, Error> { ) -> Result<FetchObjectResponse<Kind>, Error> {
static CONTENT_TYPE: HeaderValue = HeaderValue::from_static(FEDERATION_CONTENT_TYPE); static FETCH_CONTENT_TYPE: HeaderValue = HeaderValue::from_static(FEDERATION_CONTENT_TYPE);
static ALT_CONTENT_TYPE: HeaderValue = HeaderValue::from_static( const VALID_RESPONSE_CONTENT_TYPES: [&str; 3] = [
r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#, FEDERATION_CONTENT_TYPE, // lemmy
); r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#, // activitypub standard
static ALT_CONTENT_TYPE_MASTODON: HeaderValue = r#"application/activity+json; charset=utf-8"#, // mastodon
HeaderValue::from_static(r#"application/activity+json; charset=utf-8"#); ];
let res = fetch_object_http_with_accept(url, data, &CONTENT_TYPE).await?; let res = fetch_object_http_with_accept(url, data, &FETCH_CONTENT_TYPE).await?;
// Ensure correct content-type to prevent vulnerabilities. // Ensure correct content-type to prevent vulnerabilities, with case insensitive comparison.
if res.content_type.as_ref() != Some(&CONTENT_TYPE) let content_type = res
&& res.content_type.as_ref() != Some(&ALT_CONTENT_TYPE) .content_type
&& res.content_type.as_ref() != Some(&ALT_CONTENT_TYPE_MASTODON) .as_ref()
{ .and_then(|c| Some(c.to_str().ok()?.to_lowercase()))
.ok_or(Error::FetchInvalidContentType(res.url.clone()))?;
if !VALID_RESPONSE_CONTENT_TYPES.contains(&content_type.as_str()) {
return Err(Error::FetchInvalidContentType(res.url)); return Err(Error::FetchInvalidContentType(res.url));
} }
// Ensure id field matches final url // Ensure id field matches final url after redirect
if res.object_id.as_ref() != Some(&res.url) { if res.object_id.as_ref() != Some(&res.url) {
return Err(Error::FetchWrongId(res.url)); return Err(Error::FetchWrongId(res.url));
} }
// Dont allow fetching local object. Only check this after the request as a local url
// may redirect to a remote object.
if data.config.is_local_url(&res.url) {
return Err(Error::NotFound);
}
Ok(res) Ok(res)
} }
@ -84,8 +93,6 @@ async fn fetch_object_http_with_accept<T: Clone, Kind: DeserializeOwned>(
content_type: &HeaderValue, content_type: &HeaderValue,
) -> Result<FetchObjectResponse<Kind>, Error> { ) -> Result<FetchObjectResponse<Kind>, Error> {
let config = &data.config; let config = &data.config;
// dont fetch local objects this way
debug_assert!(url.domain() != Some(&config.domain));
config.verify_url_valid(url).await?; config.verify_url_valid(url).await?;
info!("Fetching remote object {}", url.to_string()); info!("Fetching remote object {}", url.to_string());

View file

@ -88,19 +88,13 @@ where
<Kind as Object>::Error: From<Error>, <Kind as Object>::Error: From<Error>,
{ {
let db_object = self.dereference_from_db(data).await?; let db_object = self.dereference_from_db(data).await?;
// if its a local object, only fetch it from the database and not over http
if data.config.is_local_url(&self.0) {
return match db_object {
None => Err(Error::NotFound.into()),
Some(o) => Ok(o),
};
}
// object found in database // object found in database
if let Some(object) = db_object { if let Some(object) = db_object {
// object is old and should be refetched
if let Some(last_refreshed_at) = object.last_refreshed_at() { if let Some(last_refreshed_at) = object.last_refreshed_at() {
if should_refetch_object(last_refreshed_at) { let is_local = data.config.is_local_url(&self.0);
if !is_local && should_refetch_object(last_refreshed_at) {
// object is outdated and should be refetched
return self.dereference_from_http(data, Some(object)).await; return self.dereference_from_http(data, Some(object)).await;
} }
} }
@ -175,6 +169,11 @@ where
Kind::verify(&res.object, redirect_url, data).await?; Kind::verify(&res.object, redirect_url, data).await?;
Kind::from_json(res.object, data).await Kind::from_json(res.object, data).await
} }
/// Returns true if the object's domain matches the one defined in [[FederationConfig.domain]].
pub fn is_local(&self, data: &Data<<Kind as Object>::DataType>) -> bool {
data.config.is_local_url(&self.0)
}
} }
/// Need to implement clone manually, to avoid requiring Kind to be Clone /// Need to implement clone manually, to avoid requiring Kind to be Clone
@ -345,9 +344,10 @@ const _IMPL_DIESEL_NEW_TYPE_FOR_OBJECT_ID: () = {
}; };
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::{fetch::object_id::should_refetch_object, traits::tests::DbUser}; use crate::traits::tests::DbUser;
#[test] #[test]
fn test_deserialize() { fn test_deserialize() {

View file

@ -245,6 +245,7 @@ pub struct WebfingerLink {
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{

View file

@ -189,8 +189,11 @@ fn verify_signature_inner(
uri: &Uri, uri: &Uri,
public_key: &str, public_key: &str,
) -> Result<(), Error> { ) -> Result<(), Error> {
static CONFIG: Lazy<http_signature_normalization::Config> = static CONFIG: Lazy<http_signature_normalization::Config> = Lazy::new(|| {
Lazy::new(|| http_signature_normalization::Config::new().set_expiration(EXPIRES_AFTER)); http_signature_normalization::Config::new()
.set_expiration(EXPIRES_AFTER)
.require_digest()
});
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or(""); let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
@ -275,6 +278,7 @@ pub(crate) fn verify_body_hash(
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
pub mod test { pub mod test {
use super::*; use super::*;
use crate::activity_sending::generate_request_headers; use crate::activity_sending::generate_request_headers;

View file

@ -343,7 +343,7 @@ pub mod tests {
error::Error, error::Error,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
http_signatures::{generate_actor_keypair, Keypair}, http_signatures::{generate_actor_keypair, Keypair},
protocol::{public_key::PublicKey, verification::verify_domains_match}, protocol::verification::verify_domains_match,
}; };
use activitystreams_kinds::{activity::FollowType, actor::PersonType}; use activitystreams_kinds::{activity::FollowType, actor::PersonType};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;