Async verify (#4)
* Add date header when sending activity * Version 0.2.2 * Make verify url function async
This commit is contained in:
parent
356b98bb97
commit
472d55a69e
7 changed files with 66 additions and 11 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -4,7 +4,7 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "activitypub_federation"
|
name = "activitypub_federation"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation_derive",
|
"activitypub_federation_derive",
|
||||||
"activitystreams-kinds",
|
"activitystreams-kinds",
|
||||||
|
@ -16,6 +16,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
|
"dyn-clone",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"http",
|
"http",
|
||||||
"http-signature-normalization-actix",
|
"http-signature-normalization-actix",
|
||||||
|
@ -530,6 +531,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
|
checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "activitypub_federation"
|
name = "activitypub_federation"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "High-level Activitypub framework"
|
description = "High-level Activitypub framework"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -33,6 +33,7 @@ background-jobs = "0.13.0"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
derive_builder = "0.11.2"
|
derive_builder = "0.11.2"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
|
dyn-clone = "1.0.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
activitystreams-kinds = "0.2.1"
|
activitystreams-kinds = "0.2.1"
|
||||||
|
|
|
@ -13,9 +13,11 @@ use activitypub_federation::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
InstanceSettings,
|
InstanceSettings,
|
||||||
LocalInstance,
|
LocalInstance,
|
||||||
|
UrlVerifier,
|
||||||
APUB_JSON_CONTENT_TYPE,
|
APUB_JSON_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
||||||
|
use async_trait::async_trait;
|
||||||
use http_signature_normalization_actix::prelude::VerifyDigest;
|
use http_signature_normalization_actix::prelude::VerifyDigest;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
@ -37,9 +39,27 @@ pub struct Instance {
|
||||||
pub posts: Mutex<Vec<MyPost>>,
|
pub posts: Mutex<Vec<MyPost>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use this to store your federation blocklist, or a database connection needed to retrieve it.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyUrlVerifier();
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UrlVerifier for MyUrlVerifier {
|
||||||
|
async fn verify(&self, url: &Url) -> Result<(), &'static str> {
|
||||||
|
if url.domain() == Some("malicious.com") {
|
||||||
|
Err("malicious domain")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn new(hostname: String) -> Result<InstanceHandle, Error> {
|
pub fn new(hostname: String) -> Result<InstanceHandle, Error> {
|
||||||
let settings = InstanceSettings::builder().debug(true).build()?;
|
let settings = InstanceSettings::builder()
|
||||||
|
.debug(true)
|
||||||
|
.url_verifier(Box::new(MyUrlVerifier()))
|
||||||
|
.build()?;
|
||||||
let local_instance =
|
let local_instance =
|
||||||
LocalInstance::new(hostname.clone(), Client::default().into(), settings);
|
LocalInstance::new(hostname.clone(), Client::default().into(), settings);
|
||||||
let local_user = MyUser::new(generate_object_id(&hostname)?, generate_actor_keypair()?);
|
let local_user = MyUser::new(generate_object_id(&hostname)?, generate_actor_keypair()?);
|
||||||
|
|
|
@ -16,6 +16,7 @@ use background_jobs::{
|
||||||
MaxRetries,
|
MaxRetries,
|
||||||
WorkerConfig,
|
WorkerConfig,
|
||||||
};
|
};
|
||||||
|
use chrono::Utc;
|
||||||
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
|
@ -50,12 +51,11 @@ where
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.unique()
|
.unique()
|
||||||
.filter(|i| !instance.is_local_url(i))
|
.filter(|i| !instance.is_local_url(i))
|
||||||
.filter(|i| verify_url_valid(i, &instance.settings).is_ok())
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let activity_queue = &instance.activity_queue;
|
let activity_queue = &instance.activity_queue;
|
||||||
for inbox in inboxes {
|
for inbox in inboxes {
|
||||||
if verify_url_valid(&inbox, &instance.settings).is_err() {
|
if verify_url_valid(&inbox, &instance.settings).await.is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let message = SendActivityTask {
|
let message = SendActivityTask {
|
||||||
|
@ -187,6 +187,10 @@ fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
|
||||||
HeaderName::from_static("host"),
|
HeaderName::from_static("host"),
|
||||||
HeaderValue::from_str(&host).expect("Hostname is valid"),
|
HeaderValue::from_str(&host).expect("Hostname is valid"),
|
||||||
);
|
);
|
||||||
|
headers.insert(
|
||||||
|
"date",
|
||||||
|
HeaderValue::from_str(&Utc::now().to_rfc2822()).expect("Hostname is valid"),
|
||||||
|
);
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ where
|
||||||
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
||||||
{
|
{
|
||||||
verify_domains_match(activity.id(), activity.actor())?;
|
verify_domains_match(activity.id(), activity.actor())?;
|
||||||
verify_url_valid(activity.id(), &local_instance.settings)?;
|
verify_url_valid(activity.id(), &local_instance.settings).await?;
|
||||||
if local_instance.is_local_url(activity.id()) {
|
if local_instance.is_local_url(activity.id()) {
|
||||||
return Err(Error::UrlVerificationError("Activity was sent from local instance").into());
|
return Err(Error::UrlVerificationError("Activity was sent from local instance").into());
|
||||||
}
|
}
|
||||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -1,6 +1,8 @@
|
||||||
use crate::core::activity_queue::create_activity_queue;
|
use crate::core::activity_queue::create_activity_queue;
|
||||||
|
use async_trait::async_trait;
|
||||||
use background_jobs::Manager;
|
use background_jobs::Manager;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
use dyn_clone::{clone_trait_object, DynClone};
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -23,6 +25,12 @@ pub struct LocalInstance {
|
||||||
settings: InstanceSettings,
|
settings: InstanceSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait UrlVerifier: DynClone + Send {
|
||||||
|
async fn verify(&self, url: &Url) -> Result<(), &'static str>;
|
||||||
|
}
|
||||||
|
clone_trait_object!(UrlVerifier);
|
||||||
|
|
||||||
// Use InstanceSettingsBuilder to initialize this
|
// Use InstanceSettingsBuilder to initialize this
|
||||||
#[derive(Builder)]
|
#[derive(Builder)]
|
||||||
pub struct InstanceSettings {
|
pub struct InstanceSettings {
|
||||||
|
@ -45,12 +53,13 @@ pub struct InstanceSettings {
|
||||||
/// Function used to verify that urls are valid, used when receiving activities or fetching remote
|
/// Function used to verify that urls are valid, used when receiving activities or fetching remote
|
||||||
/// objects. Use this to implement functionality like federation blocklists. In case verification
|
/// objects. Use this to implement functionality like federation blocklists. In case verification
|
||||||
/// fails, it should return an error message.
|
/// fails, it should return an error message.
|
||||||
#[builder(default = "|_| { Ok(()) }")]
|
#[builder(default = "Box::new(DefaultUrlVerifier())")]
|
||||||
verify_url_function: fn(&Url) -> Result<(), &'static str>,
|
url_verifier: Box<dyn UrlVerifier + Sync>,
|
||||||
/// Enable to sign HTTP signatures according to draft 10, which does not include (created) and
|
/// Enable to sign HTTP signatures according to draft 10, which does not include (created) and
|
||||||
/// (expires) fields. This is required for compatibility with some software like Pleroma.
|
/// (expires) fields. This is required for compatibility with some software like Pleroma.
|
||||||
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-10
|
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-10
|
||||||
/// https://git.pleroma.social/pleroma/pleroma/-/issues/2939
|
/// https://git.pleroma.social/pleroma/pleroma/-/issues/2939
|
||||||
|
#[builder(default = "false")]
|
||||||
http_signature_compat: bool,
|
http_signature_compat: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +70,16 @@ impl InstanceSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DefaultUrlVerifier();
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UrlVerifier for DefaultUrlVerifier {
|
||||||
|
async fn verify(&self, _url: &Url) -> Result<(), &'static str> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalInstance {
|
impl LocalInstance {
|
||||||
pub fn new(domain: String, client: ClientWithMiddleware, settings: InstanceSettings) -> Self {
|
pub fn new(domain: String, client: ClientWithMiddleware, settings: InstanceSettings) -> Self {
|
||||||
let activity_queue = create_activity_queue(client.clone(), &settings);
|
let activity_queue = create_activity_queue(client.clone(), &settings);
|
||||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -11,7 +11,7 @@ pub async fn fetch_object_http<Kind: DeserializeOwned>(
|
||||||
) -> Result<Kind, Error> {
|
) -> Result<Kind, Error> {
|
||||||
// dont fetch local objects this way
|
// dont fetch local objects this way
|
||||||
debug_assert!(url.domain() != Some(&instance.hostname));
|
debug_assert!(url.domain() != Some(&instance.hostname));
|
||||||
verify_url_valid(url, &instance.settings)?;
|
verify_url_valid(url, &instance.settings).await?;
|
||||||
info!("Fetching remote object {}", url.to_string());
|
info!("Fetching remote object {}", url.to_string());
|
||||||
|
|
||||||
*request_counter += 1;
|
*request_counter += 1;
|
||||||
|
@ -55,7 +55,7 @@ pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||||
/// [`InstanceSettings.verify_url_function`].
|
/// [`InstanceSettings.verify_url_function`].
|
||||||
///
|
///
|
||||||
/// https://www.w3.org/TR/activitypub/#security-considerations
|
/// https://www.w3.org/TR/activitypub/#security-considerations
|
||||||
pub fn verify_url_valid(url: &Url, settings: &InstanceSettings) -> Result<(), Error> {
|
pub async fn verify_url_valid(url: &Url, settings: &InstanceSettings) -> Result<(), Error> {
|
||||||
match url.scheme() {
|
match url.scheme() {
|
||||||
"https" => {}
|
"https" => {}
|
||||||
"http" => {
|
"http" => {
|
||||||
|
@ -78,7 +78,11 @@ pub fn verify_url_valid(url: &Url, settings: &InstanceSettings) -> Result<(), Er
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
(settings.verify_url_function)(url).map_err(Error::UrlVerificationError)?;
|
settings
|
||||||
|
.url_verifier
|
||||||
|
.verify(url)
|
||||||
|
.await
|
||||||
|
.map_err(Error::UrlVerificationError)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue