Add verify methods back in, some more fixes (#28)

This commit is contained in:
Nutomic 2023-03-09 22:09:44 +01:00 committed by GitHub
parent 8f2b9634b6
commit 6b3a4f8942
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 164 additions and 18 deletions

2
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = 3
[[package]] [[package]]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.3.4" version = "0.4.0-rc1"
dependencies = [ dependencies = [
"activitystreams-kinds", "activitystreams-kinds",
"actix-rt", "actix-rt",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.4.0-rc1" version = "0.4.0-rc2"
edition = "2021" edition = "2021"
description = "High-level Activitypub framework" description = "High-level Activitypub framework"
keywords = ["activitypub", "activitystreams", "federation", "fediverse"] keywords = ["activitypub", "activitystreams", "federation", "fediverse"]

View file

@ -36,6 +36,10 @@ impl ActivityHandler for Follow {
fn actor(&self) -> &Url { fn actor(&self) -> &Url {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
let actor = self.actor.dereference(data).await?; let actor = self.actor.dereference(data).await?;

View file

@ -44,6 +44,10 @@ impl ApubObject for SearchableDbObjects {
) -> Result<Self::ApubType, Self::Error> { ) -> Result<Self::ApubType, Self::Error> {
unimplemented!(); unimplemented!();
} }
async fn verify(apub: &Self::ApubType, expected_domain: &Url, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,

View file

@ -65,6 +65,11 @@ impl ActivityHandler for CreatePost {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
DbPost::verify(&self.object, &self.id, data).await?;
Ok(())
}
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
DbPost::from_apub(self.object, data).await?; DbPost::from_apub(self.object, data).await?;
Ok(()) Ok(())

View file

@ -4,7 +4,7 @@ use activitypub_federation::{
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
http_signatures::generate_actor_keypair, http_signatures::generate_actor_keypair,
kinds::actor::PersonType, kinds::actor::PersonType,
protocol::public_key::PublicKey, protocol::{public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, ApubObject}, traits::{ActivityHandler, Actor, ApubObject},
}; };
use chrono::{Local, NaiveDateTime}; use chrono::{Local, NaiveDateTime};
@ -99,6 +99,15 @@ impl ApubObject for DbUser {
}) })
} }
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
_data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,
_data: &RequestData<Self::DataType>, _data: &RequestData<Self::DataType>,
@ -117,11 +126,15 @@ impl ApubObject for DbUser {
} }
impl Actor for DbUser { impl Actor for DbUser {
fn public_key(&self) -> &str { fn public_key_pem(&self) -> &str {
&self.public_key &self.public_key
} }
fn inbox(&self) -> Url { fn inbox(&self) -> Url {
self.inbox.clone() self.inbox.clone()
} }
fn id(&self) -> &Url {
self.ap_id.inner()
}
} }

View file

@ -9,7 +9,7 @@ use activitypub_federation::{
config::RequestData, config::RequestData,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::{object::NoteType, public}, kinds::{object::NoteType, public},
protocol::helpers::deserialize_one_or_many, protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::{Actor, ApubObject}, traits::{Actor, ApubObject},
}; };
use activitystreams_kinds::link::MentionType; use activitystreams_kinds::link::MentionType;
@ -65,6 +65,15 @@ impl ApubObject for DbPost {
unimplemented!() unimplemented!()
} }
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
_data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,
data: &RequestData<Self::DataType>, data: &RequestData<Self::DataType>,

View file

@ -42,6 +42,10 @@ impl ActivityHandler for Accept {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }

View file

@ -50,6 +50,11 @@ impl ActivityHandler for CreatePost {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
DbPost::verify(&self.object, &self.id, data).await?;
Ok(())
}
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
DbPost::from_apub(self.object, data).await?; DbPost::from_apub(self.object, data).await?;
Ok(()) Ok(())

View file

@ -47,6 +47,10 @@ impl ActivityHandler for Follow {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
// Ignore clippy false positive: https://github.com/rust-lang/rust-clippy/issues/6446 // Ignore clippy false positive: https://github.com/rust-lang/rust-clippy/issues/6446
#[allow(clippy::await_holding_lock)] #[allow(clippy::await_holding_lock)]
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {

View file

@ -11,7 +11,7 @@ use activitypub_federation::{
fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor},
http_signatures::generate_actor_keypair, http_signatures::generate_actor_keypair,
kinds::actor::PersonType, kinds::actor::PersonType,
protocol::{context::WithContext, public_key::PublicKey}, protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, ApubObject}, traits::{ActivityHandler, Actor, ApubObject},
}; };
use chrono::{Local, NaiveDateTime}; use chrono::{Local, NaiveDateTime};
@ -171,6 +171,15 @@ impl ApubObject for DbUser {
}) })
} }
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
_data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,
data: &RequestData<Self::DataType>, data: &RequestData<Self::DataType>,
@ -192,7 +201,11 @@ impl ApubObject for DbUser {
} }
impl Actor for DbUser { impl Actor for DbUser {
fn public_key(&self) -> &str { fn id(&self) -> &Url {
self.ap_id.inner()
}
fn public_key_pem(&self) -> &str {
&self.public_key &self.public_key
} }

View file

@ -3,7 +3,7 @@ use activitypub_federation::{
config::RequestData, config::RequestData,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::{object::NoteType, public}, kinds::{object::NoteType, public},
protocol::helpers::deserialize_one_or_many, protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::ApubObject, traits::ApubObject,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -73,6 +73,15 @@ impl ApubObject for DbPost {
}) })
} }
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
_data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,
data: &RequestData<Self::DataType>, data: &RequestData<Self::DataType>,

View file

@ -42,10 +42,11 @@ where
request.headers(), request.headers(),
request.method(), request.method(),
request.uri(), request.uri(),
actor.public_key(), actor.public_key_pem(),
)?; )?;
debug!("Receiving activity {}", activity.id().to_string()); debug!("Receiving activity {}", activity.id().to_string());
activity.verify(data).await?;
activity.receive(data).await?; activity.receive(data).await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }

View file

@ -44,15 +44,15 @@ where
.dereference(data) .dereference(data)
.await?; .await?;
// TODO: why do errors here not get returned over http?
verify_signature( verify_signature(
&activity_data.headers, &activity_data.headers,
&activity_data.method, &activity_data.method,
&activity_data.uri, &activity_data.uri,
actor.public_key(), actor.public_key_pem(),
)?; )?;
debug!("Receiving activity {}", activity.id().to_string()); debug!("Receiving activity {}", activity.id().to_string());
activity.verify(data).await?;
activity.receive(data).await?; activity.receive(data).await?;
Ok(()) Ok(())
} }

View file

@ -148,6 +148,7 @@ where
let res2 = res?; let res2 = res?;
Kind::verify(&res2, self.inner(), data).await?;
Kind::from_apub(res2, data).await Kind::from_apub(res2, data).await
} }
} }

View file

@ -62,7 +62,7 @@ impl<T> WithContext<T> {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<T> ActivityHandler for WithContext<T> impl<T> ActivityHandler for WithContext<T>
where where
T: ActivityHandler + Send, T: ActivityHandler + Send + Sync,
{ {
type DataType = <T as ActivityHandler>::DataType; type DataType = <T as ActivityHandler>::DataType;
type Error = <T as ActivityHandler>::Error; type Error = <T as ActivityHandler>::Error;
@ -75,6 +75,10 @@ where
self.inner.actor() self.inner.actor()
} }
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
self.inner.verify(data).await
}
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
self.inner.receive(data).await self.inner.receive(data).await
} }

View file

@ -1,6 +1,6 @@
//! Traits which need to be implemented for federated data types //! Traits which need to be implemented for federated data types
use crate::config::RequestData; use crate::{config::RequestData, protocol::public_key::PublicKey};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use std::ops::Deref; use std::ops::Deref;
@ -13,6 +13,7 @@ use url::Url;
/// # use url::Url; /// # use url::Url;
/// # use activitypub_federation::protocol::public_key::PublicKey; /// # use activitypub_federation::protocol::public_key::PublicKey;
/// # use activitypub_federation::config::RequestData; /// # use activitypub_federation::config::RequestData;
/// use activitypub_federation::protocol::verification::verify_domains_match;
/// # use activitypub_federation::traits::ApubObject; /// # use activitypub_federation::traits::ApubObject;
/// # use activitypub_federation::traits::tests::{DbConnection, Person}; /// # use activitypub_federation::traits::tests::{DbConnection, Person};
/// # pub struct DbUser { /// # pub struct DbUser {
@ -53,7 +54,13 @@ use url::Url;
/// }) /// })
/// } /// }
/// ///
/// async fn from_apub(apub: Self::ApubType, data: &RequestData<Self::DataType>) -> Result<Self, Self::Error> { /// async fn verify(apub: &Self::ApubType, expected_domain: &Url, data: &RequestData<Self::DataType>,) -> Result<(), Self::Error> {
/// verify_domains_match(apub.id.inner(), expected_domain)?;
/// // additional application specific checks
/// Ok(())
/// }
///
/// async fn from_apub(apub: Self::ApubType, data: &RequestData<Self::DataType>) -> Result<Self, Self::Error> {
/// // Called when a remote object gets received over Activitypub. Validate and insert it /// // Called when a remote object gets received over Activitypub. Validate and insert it
/// // into the database. /// // into the database.
/// ///
@ -124,6 +131,19 @@ pub trait ApubObject: Sized {
data: &RequestData<Self::DataType>, data: &RequestData<Self::DataType>,
) -> Result<Self::ApubType, Self::Error>; ) -> Result<Self::ApubType, Self::Error>;
/// Verifies that the received object is valid.
///
/// You should check here that the domain of id matches `expected_domain`. Additionally you
/// should perform any application specific checks.
///
/// It is necessary to use a separate method for this, because it might be used for activities
/// like `Delete/Note`, which shouldn't perform any database write for the inner `Note`.
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error>;
/// Convert object from ActivityPub type to database type. /// Convert object from ActivityPub type to database type.
/// ///
/// Called when an object is received from HTTP fetch or as part of an activity. This method /// Called when an object is received from HTTP fetch or as part of an activity. This method
@ -166,6 +186,10 @@ pub trait ApubObject: Sized {
/// self.actor.inner() /// self.actor.inner()
/// } /// }
/// ///
/// async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
/// Ok(())
/// }
///
/// async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { /// async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
/// let local_user = self.object.dereference(data).await?; /// let local_user = self.object.dereference(data).await?;
/// let follower = self.actor.dereference(data).await?; /// let follower = self.actor.dereference(data).await?;
@ -189,6 +213,12 @@ pub trait ActivityHandler {
/// `actor` field of activity /// `actor` field of activity
fn actor(&self) -> &Url; fn actor(&self) -> &Url;
/// Verifies that the received activity is valid.
///
/// This needs to be a separate method, because it might be used for activities
/// like `Undo/Follow`, which shouldn't perform any database write for the inner `Follow`.
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error>;
/// Called when an activity is received. /// Called when an activity is received.
/// ///
/// Should perform validation and possibly write action to the database. In case the activity /// Should perform validation and possibly write action to the database. In case the activity
@ -198,12 +228,23 @@ pub trait ActivityHandler {
/// Trait to allow retrieving common Actor data. /// Trait to allow retrieving common Actor data.
pub trait Actor: ApubObject { pub trait Actor: ApubObject {
/// `id` field of the actor
fn id(&self) -> &Url;
/// The actor's public key for verifying signatures of incoming activities. /// The actor's public key for verifying signatures of incoming activities.
fn public_key(&self) -> &str; ///
/// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the
/// actor keypair.
fn public_key_pem(&self) -> &str;
/// The inbox where activities for this user should be sent to /// The inbox where activities for this user should be sent to
fn inbox(&self) -> Url; fn inbox(&self) -> Url;
/// Generates a public key struct for use in the actor json representation
fn public_key(&self) -> PublicKey {
PublicKey::new(self.id().clone(), self.public_key_pem().to_string())
}
/// The actor's shared inbox, if any /// The actor's shared inbox, if any
fn shared_inbox(&self) -> Option<Url> { fn shared_inbox(&self) -> Option<Url> {
None None
@ -219,7 +260,7 @@ pub trait Actor: ApubObject {
#[async_trait] #[async_trait]
impl<T> ActivityHandler for Box<T> impl<T> ActivityHandler for Box<T>
where where
T: ActivityHandler + Send, T: ActivityHandler + Send + Sync,
{ {
type DataType = T::DataType; type DataType = T::DataType;
type Error = T::Error; type Error = T::Error;
@ -232,6 +273,10 @@ where
self.deref().actor() self.deref().actor()
} }
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
(*self).verify(data).await
}
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
(*self).receive(data).await (*self).receive(data).await
} }
@ -247,7 +292,7 @@ pub mod tests {
use crate::{ use crate::{
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
http_signatures::{generate_actor_keypair, Keypair}, http_signatures::{generate_actor_keypair, Keypair},
protocol::public_key::PublicKey, protocol::{public_key::PublicKey, verification::verify_domains_match},
}; };
use activitystreams_kinds::{activity::FollowType, actor::PersonType}; use activitystreams_kinds::{activity::FollowType, actor::PersonType};
use anyhow::Error; use anyhow::Error;
@ -333,6 +378,15 @@ pub mod tests {
}) })
} }
async fn verify(
apub: &Self::ApubType,
expected_domain: &Url,
_data: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub( async fn from_apub(
apub: Self::ApubType, apub: Self::ApubType,
_data: &RequestData<Self::DataType>, _data: &RequestData<Self::DataType>,
@ -350,7 +404,11 @@ pub mod tests {
} }
impl Actor for DbUser { impl Actor for DbUser {
fn public_key(&self) -> &str { fn id(&self) -> &Url {
&self.apub_id
}
fn public_key_pem(&self) -> &str {
&self.public_key &self.public_key
} }
@ -382,6 +440,10 @@ pub mod tests {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, _: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
@ -413,6 +475,14 @@ pub mod tests {
todo!() todo!()
} }
async fn verify(
_: &Self::ApubType,
_: &Url,
_: &RequestData<Self::DataType>,
) -> Result<(), Self::Error> {
todo!()
}
async fn from_apub( async fn from_apub(
_: Self::ApubType, _: Self::ApubType,
_: &RequestData<Self::DataType>, _: &RequestData<Self::DataType>,