Make it work with Lemmy

This commit is contained in:
Felix Ableitner 2023-03-11 16:16:09 +01:00
parent 6b3a4f8942
commit 29d040bcd3
6 changed files with 36 additions and 11 deletions

2
Cargo.lock generated
View file

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

View file

@ -7,7 +7,7 @@ use crate::{
error::Error, error::Error,
http_signatures::sign_request, http_signatures::sign_request,
reqwest_shim::ResponseExt, reqwest_shim::ResponseExt,
traits::ActivityHandler, traits::{ActivityHandler, Actor},
APUB_JSON_CONTENT_TYPE, APUB_JSON_CONTENT_TYPE,
}; };
use anyhow::anyhow; use anyhow::anyhow;
@ -40,9 +40,9 @@ use url::Url;
/// signature. Generated with [crate::http_signatures::generate_actor_keypair]. /// signature. Generated with [crate::http_signatures::generate_actor_keypair].
/// - `inboxes`: List of actor inboxes that should receive the activity. Should be built by calling /// - `inboxes`: List of actor inboxes that should receive the activity. Should be built by calling
/// [crate::traits::Actor::shared_inbox_or_inbox] for each target actor. /// [crate::traits::Actor::shared_inbox_or_inbox] for each target actor.
pub async fn send_activity<Activity, Datatype>( pub async fn send_activity<Activity, Datatype, ActorType>(
activity: Activity, activity: Activity,
private_key: String, actor: ActorType,
inboxes: Vec<Url>, inboxes: Vec<Url>,
data: &RequestData<Datatype>, data: &RequestData<Datatype>,
) -> Result<(), <Activity as ActivityHandler>::Error> ) -> Result<(), <Activity as ActivityHandler>::Error>
@ -50,11 +50,14 @@ where
Activity: ActivityHandler + Serialize, Activity: ActivityHandler + Serialize,
<Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>, <Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>,
Datatype: Clone, Datatype: Clone,
ActorType: Actor,
for<'de2> ActorType::ApubType: Deserialize<'de2>,
{ {
let config = &data.config; let config = &data.config;
let actor_id = activity.actor(); let actor_id = activity.actor();
let activity_id = activity.id(); let activity_id = activity.id();
let activity_serialized = serde_json::to_string_pretty(&activity)?; let activity_serialized = serde_json::to_string_pretty(&activity)?;
let private_key = actor.private_key_pem().unwrap();
let inboxes: Vec<Url> = inboxes let inboxes: Vec<Url> = inboxes
.into_iter() .into_iter()
.unique() .unique()

View file

@ -112,8 +112,8 @@ mod test {
let request_builder = let request_builder =
ClientWithMiddleware::from(Client::default()).post("https://example.com/inbox"); ClientWithMiddleware::from(Client::default()).post("https://example.com/inbox");
let activity = Follow { let activity = Follow {
actor: ObjectId::new("http://localhost:123").unwrap(), actor: ObjectId::parse("http://localhost:123").unwrap(),
object: ObjectId::new("http://localhost:124").unwrap(), object: ObjectId::parse("http://localhost:124").unwrap(),
kind: Default::default(), kind: Default::default(),
id: "http://localhost:123/1".try_into().unwrap(), id: "http://localhost:123/1".try_into().unwrap(),
}; };

View file

@ -5,9 +5,21 @@ use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt::{Debug, Display, Formatter}, fmt::{Debug, Display, Formatter},
marker::PhantomData, marker::PhantomData,
str::FromStr,
}; };
use url::Url; use url::Url;
impl<T> FromStr for ObjectId<T>
where
T: ApubObject + Send + 'static,
for<'de2> <T as ApubObject>::ApubType: Deserialize<'de2>,
{
type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
ObjectId::parse(s)
}
}
/// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching. /// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching.
/// ///
/// It provides convenient methods for fetching the object from remote server or local database. /// It provides convenient methods for fetching the object from remote server or local database.
@ -32,7 +44,7 @@ use url::Url;
/// .app_data(db_connection) /// .app_data(db_connection)
/// .build()?; /// .build()?;
/// let request_data = config.to_request_data(); /// let request_data = config.to_request_data();
/// let object_id = ObjectId::<DbUser>::new("https://lemmy.ml/u/nutomic")?; /// let object_id = ObjectId::<DbUser>::parse("https://lemmy.ml/u/nutomic")?;
/// // Attempt to fetch object from local database or fall back to remote server /// // Attempt to fetch object from local database or fall back to remote server
/// let user = object_id.dereference(&request_data).await; /// let user = object_id.dereference(&request_data).await;
/// assert!(user.is_ok()); /// assert!(user.is_ok());
@ -55,7 +67,7 @@ where
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>, for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{ {
/// Construct a new objectid instance /// Construct a new objectid instance
pub fn new<T>(url: T) -> Result<Self, url::ParseError> pub fn parse<T>(url: T) -> Result<Self, url::ParseError>
where where
T: TryInto<Url>, T: TryInto<Url>,
url::ParseError: From<<T as TryInto<Url>>::Error>, url::ParseError: From<<T as TryInto<Url>>::Error>,
@ -238,7 +250,7 @@ pub mod tests {
#[test] #[test]
fn test_deserialize() { fn test_deserialize() {
let id = ObjectId::<DbUser>::new("http://test.com/").unwrap(); let id = ObjectId::<DbUser>::parse("http://test.com/").unwrap();
let string = serde_json::to_string(&id).unwrap(); let string = serde_json::to_string(&id).unwrap();
assert_eq!("\"http://test.com/\"", string); assert_eq!("\"http://test.com/\"", string);

View file

@ -21,7 +21,7 @@ impl PublicKey {
/// Create a new [PublicKey] struct for the `owner` with `public_key_pem`. /// Create a new [PublicKey] struct for the `owner` with `public_key_pem`.
/// ///
/// It uses an standard key id of `{actor_id}#main-key` /// It uses an standard key id of `{actor_id}#main-key`
pub fn new(owner: Url, public_key_pem: String) -> Self { pub(crate) fn new(owner: Url, public_key_pem: String) -> Self {
let id = main_key_id(&owner); let id = main_key_id(&owner);
PublicKey { PublicKey {
id, id,

View file

@ -227,7 +227,7 @@ 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 + Send + 'static {
/// `id` field of the actor /// `id` field of the actor
fn id(&self) -> &Url; fn id(&self) -> &Url;
@ -237,6 +237,12 @@ pub trait Actor: ApubObject {
/// actor keypair. /// actor keypair.
fn public_key_pem(&self) -> &str; fn public_key_pem(&self) -> &str;
/// The actor's private key for signing outgoing activities.
///
/// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the
/// actor keypair.
fn private_key_pem(&self) -> Option<String>;
/// 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;
@ -412,6 +418,10 @@ pub mod tests {
&self.public_key &self.public_key
} }
fn private_key_pem(&self) -> Option<String> {
self.private_key.clone()
}
fn inbox(&self) -> Url { fn inbox(&self) -> Url {
self.inbox.clone() self.inbox.clone()
} }