From 695272f980b2650e435f7594777a95ee78df8958 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 19 Oct 2020 16:29:35 +0200 Subject: [PATCH] Create rustdoc for activitypub code --- lemmy_apub/src/activities/receive/announce.rs | 1 + lemmy_apub/src/activities/receive/mod.rs | 20 +++++--- lemmy_apub/src/activities/send/comment.rs | 24 +++++----- lemmy_apub/src/activities/send/community.rs | 6 +++ lemmy_apub/src/activities/send/mod.rs | 2 + lemmy_apub/src/activities/send/post.rs | 18 +++---- .../src/activities/send/private_message.rs | 2 +- lemmy_apub/src/activity_queue.rs | 29 +++++++++++- lemmy_apub/src/extensions/group_extensions.rs | 2 + lemmy_apub/src/extensions/page_extension.rs | 3 ++ lemmy_apub/src/extensions/signatures.rs | 24 +++++----- lemmy_apub/src/fetcher.rs | 47 ++++++++++++++----- lemmy_apub/src/http/comment.rs | 2 +- lemmy_apub/src/http/community.rs | 4 +- lemmy_apub/src/http/post.rs | 2 +- lemmy_apub/src/http/user.rs | 2 +- lemmy_apub/src/inbox/community_inbox.rs | 4 +- lemmy_apub/src/inbox/shared_inbox.rs | 3 +- lemmy_apub/src/inbox/user_inbox.rs | 3 +- lemmy_apub/src/lib.rs | 33 +++++++++---- lemmy_apub/src/objects/comment.rs | 6 ++- lemmy_apub/src/objects/community.rs | 4 +- lemmy_apub/src/objects/post.rs | 6 ++- lemmy_apub/src/objects/private_message.rs | 3 +- lemmy_apub/src/objects/user.rs | 6 +-- 25 files changed, 174 insertions(+), 82 deletions(-) diff --git a/lemmy_apub/src/activities/receive/announce.rs b/lemmy_apub/src/activities/receive/announce.rs index 3167d6b90..5f25b58c6 100644 --- a/lemmy_apub/src/activities/receive/announce.rs +++ b/lemmy_apub/src/activities/receive/announce.rs @@ -19,6 +19,7 @@ use anyhow::Context; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; +/// Takes an announce and passes the inner activity to the appropriate handler. pub async fn receive_announce( context: &LemmyContext, activity: AnyBase, diff --git a/lemmy_apub/src/activities/receive/mod.rs b/lemmy_apub/src/activities/receive/mod.rs index ab830ca79..b8ec327ae 100644 --- a/lemmy_apub/src/activities/receive/mod.rs +++ b/lemmy_apub/src/activities/receive/mod.rs @@ -31,6 +31,7 @@ mod undo_comment; mod undo_post; pub mod update; +/// Return HTTP 501 for unsupported activities in inbox. fn receive_unhandled_activity(activity: A) -> Result where A: Debug, @@ -39,6 +40,8 @@ where Ok(HttpResponse::NotImplemented().finish()) } +/// Reads the destination community from the activity's `cc` field. If this refers to a local +/// community, the activity is announced to all community followers. async fn announce_if_community_is_local( activity: T, user: &User_, @@ -52,16 +55,12 @@ where { let cc = activity.cc().context(location_info!())?; let cc = cc.as_many().context(location_info!())?; - let community_followers_uri = cc + let community_uri = cc .first() .context(location_info!())? .as_xsd_any_uri() .context(location_info!())?; - // TODO: this is hacky but seems to be the only way to get the community ID - let community_uri = community_followers_uri - .to_string() - .replace("/followers", ""); - let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?; + let community = get_or_fetch_and_upsert_community(&community_uri, context).await?; if community.local { community @@ -71,6 +70,7 @@ where Ok(()) } +/// Reads the actor field of an activity and returns the corresponding `User_`. pub(crate) async fn get_actor_as_user( activity: &T, context: &LemmyContext, @@ -89,6 +89,9 @@ pub(crate) enum FindResults { Post(Post), } +/// Tries to find a community, post or comment in the local database, without any network requests. +/// This is used to handle deletions and removals, because in case we dont have the object, we can +/// simply ignore the activity. pub(crate) async fn find_by_id( context: &LemmyContext, apub_id: Url, @@ -123,6 +126,11 @@ pub(crate) async fn find_by_id( return Err(NotFound.into()); } +/// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally +/// also checks the ID of the inner object. +/// +/// The reason that this starts with the actor ID is that it was already confirmed as correct by the +/// HTTP signature. pub(crate) fn verify_activity_domains_valid( activity: &T, actor_id: Url, diff --git a/lemmy_apub/src/activities/send/comment.rs b/lemmy_apub/src/activities/send/comment.rs index bbf3adb17..bcfd779c8 100644 --- a/lemmy_apub/src/activities/send/comment.rs +++ b/lemmy_apub/src/activities/send/comment.rs @@ -41,7 +41,8 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ApubObjectType for Comment { - /// Send out information about a newly created comment, to the followers of the community. + /// Send out information about a newly created comment, to the followers of the community and + /// mentioned users. async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let note = self.to_apub(context.pool()).await?; @@ -68,12 +69,13 @@ impl ApubObjectType for Comment { // Set the mention tags .set_many_tags(maa.get_tags()?); - send_to_community(&creator, &community, create.clone(), context).await?; + send_to_community(create.clone(), &creator, &community, context).await?; send_comment_mentions(&creator, maa.inboxes, create, context).await?; Ok(()) } - /// Send out information about an edited post, to the followers of the community. + /// Send out information about an edited post, to the followers of the community and mentioned + /// users. async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let note = self.to_apub(context.pool()).await?; @@ -100,7 +102,7 @@ impl ApubObjectType for Comment { // Set the mention tags .set_many_tags(maa.get_tags()?); - send_to_community(&creator, &community, update.clone(), context).await?; + send_to_community(update.clone(), &creator, &community, context).await?; send_comment_mentions(&creator, maa.inboxes, update, context).await?; Ok(()) } @@ -122,7 +124,7 @@ impl ApubObjectType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, delete, context).await?; + send_to_community(delete, &creator, &community, context).await?; Ok(()) } @@ -156,7 +158,7 @@ impl ApubObjectType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, undo, context).await?; + send_to_community(undo, &creator, &community, context).await?; Ok(()) } @@ -177,7 +179,7 @@ impl ApubObjectType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&mod_, &community, remove, context).await?; + send_to_community(remove, &mod_, &community, context).await?; Ok(()) } @@ -207,7 +209,7 @@ impl ApubObjectType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&mod_, &community, undo, context).await?; + send_to_community(undo, &mod_, &community, context).await?; Ok(()) } } @@ -233,7 +235,7 @@ impl ApubLikeableType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, like, context).await?; + send_to_community(like, &creator, &community, context).await?; Ok(()) } @@ -256,7 +258,7 @@ impl ApubLikeableType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, dislike, context).await?; + send_to_community(dislike, &creator, &community, context).await?; Ok(()) } @@ -291,7 +293,7 @@ impl ApubLikeableType for Comment { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, undo, context).await?; + send_to_community(undo, &creator, &community, context).await?; Ok(()) } } diff --git a/lemmy_apub/src/activities/send/community.rs b/lemmy_apub/src/activities/send/community.rs index 3bed1a2ae..2f43f9c55 100644 --- a/lemmy_apub/src/activities/send/community.rs +++ b/lemmy_apub/src/activities/send/community.rs @@ -84,6 +84,7 @@ impl ActorType for Community { Ok(()) } + /// If the creator of a community deletes the community, send this to all followers. async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let group = self.to_apub(context.pool()).await?; @@ -98,6 +99,7 @@ impl ActorType for Community { Ok(()) } + /// If the creator of a community reverts the deletion of a community, send this to all followers. async fn send_undo_delete( &self, creator: &User_, @@ -123,6 +125,7 @@ impl ActorType for Community { Ok(()) } + /// If an admin removes a community, send this to all followers. async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let mut remove = Remove::new(mod_.actor_id.to_owned(), self.actor_id()?); remove @@ -135,6 +138,7 @@ impl ActorType for Community { Ok(()) } + /// If an admin reverts the removal of a community, send this to all followers. async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let mut remove = Remove::new(mod_.actor_id.to_owned(), self.actor_id()?); remove @@ -155,6 +159,8 @@ impl ActorType for Community { Ok(()) } + /// Wraps an activity sent to the community in an announce, and then sends the announce to all + /// community followers. async fn send_announce( &self, activity: AnyBase, diff --git a/lemmy_apub/src/activities/send/mod.rs b/lemmy_apub/src/activities/send/mod.rs index 22cc10f4a..fe898bd8f 100644 --- a/lemmy_apub/src/activities/send/mod.rs +++ b/lemmy_apub/src/activities/send/mod.rs @@ -8,6 +8,8 @@ pub mod post; pub mod private_message; pub mod user; +/// Generate a unique ID for an activity, in the format: +/// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36` fn generate_activity_id(kind: T) -> Result where T: ToString, diff --git a/lemmy_apub/src/activities/send/post.rs b/lemmy_apub/src/activities/send/post.rs index 568a5f571..32035952d 100644 --- a/lemmy_apub/src/activities/send/post.rs +++ b/lemmy_apub/src/activities/send/post.rs @@ -45,7 +45,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(creator, &community, create, context).await?; + send_to_community(create, creator, &community, context).await?; Ok(()) } @@ -66,7 +66,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(creator, &community, update, context).await?; + send_to_community(update, creator, &community, context).await?; Ok(()) } @@ -84,7 +84,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(creator, &community, delete, context).await?; + send_to_community(delete, creator, &community, context).await?; Ok(()) } @@ -114,7 +114,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(creator, &community, undo, context).await?; + send_to_community(undo, creator, &community, context).await?; Ok(()) } @@ -132,7 +132,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(mod_, &community, remove, context).await?; + send_to_community(remove, mod_, &community, context).await?; Ok(()) } @@ -158,7 +158,7 @@ impl ApubObjectType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(mod_, &community, undo, context).await?; + send_to_community(undo, mod_, &community, context).await?; Ok(()) } } @@ -181,7 +181,7 @@ impl ApubLikeableType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, like, context).await?; + send_to_community(like, &creator, &community, context).await?; Ok(()) } @@ -201,7 +201,7 @@ impl ApubLikeableType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, dislike, context).await?; + send_to_community(dislike, &creator, &community, context).await?; Ok(()) } @@ -233,7 +233,7 @@ impl ApubLikeableType for Post { .set_to(public()) .set_many_ccs(vec![community.actor_id()?]); - send_to_community(&creator, &community, undo, context).await?; + send_to_community(undo, &creator, &community, context).await?; Ok(()) } } diff --git a/lemmy_apub/src/activities/send/private_message.rs b/lemmy_apub/src/activities/send/private_message.rs index 89b654041..51d140293 100644 --- a/lemmy_apub/src/activities/send/private_message.rs +++ b/lemmy_apub/src/activities/send/private_message.rs @@ -41,7 +41,7 @@ impl ApubObjectType for PrivateMessage { Ok(()) } - /// Send out information about an edited post, to the followers of the community. + /// Send out information about an edited private message, to the followers of the community. async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { let note = self.to_apub(context.pool()).await?; diff --git a/lemmy_apub/src/activity_queue.rs b/lemmy_apub/src/activity_queue.rs index 715c53044..839cf667f 100644 --- a/lemmy_apub/src/activity_queue.rs +++ b/lemmy_apub/src/activity_queue.rs @@ -28,6 +28,11 @@ use serde::{export::fmt::Debug, Deserialize, Serialize}; use std::{collections::BTreeMap, future::Future, pin::Pin}; use url::Url; +/// Sends a local activity to a single, remote actor. +/// +/// * `activity` the apub activity to be sent +/// * `creator` the local actor which created the activity +/// * `inbox` the inbox url where the activity should be delivered to pub async fn send_activity_single_dest( activity: T, creator: &dyn ActorType, @@ -59,6 +64,12 @@ where Ok(()) } +/// From a local community, send activity to all remote followers. +/// +/// * `activity` the apub activity to send +/// * `community` the sending community +/// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner +/// activities creator, as receiving a known activity will cause an error pub async fn send_to_community_followers( activity: T, community: &Community, @@ -102,10 +113,16 @@ where Ok(()) } +/// Sends an activity from a local user to a remote community. +/// +/// * `activity` the activity to send +/// * `creator` the creator of the activity +/// * `community` the destination community +/// pub async fn send_to_community( + activity: T, creator: &User_, community: &Community, - activity: T, context: &LemmyContext, ) -> Result<(), LemmyError> where @@ -140,6 +157,11 @@ where Ok(()) } +/// Sends notification to any users mentioned in a comment +/// +/// * `creator` user who created the comment +/// * `mentions` list of inboxes of users which are mentioned in the comment +/// * `activity` either a `Create/Note` or `Update/Note` pub async fn send_comment_mentions( creator: &User_, mentions: Vec, @@ -173,7 +195,8 @@ where Ok(()) } -/// Asynchronously sends the given `activity` from `actor` to every inbox URL in `to`. +/// Create new `SendActivityTasks`, which will deliver the given activity to inboxes, as well as +/// handling signing and retrying failed deliveres. /// /// The caller of this function needs to remove any blocked domains from `to`, /// using `check_is_apub_id_valid()`. @@ -224,6 +247,8 @@ struct SendActivityTask { private_key: String, } +/// Signs the activity with the sending actor's key, and delivers to the given inbox. Also retries +/// if the delivery failed. impl ActixJob for SendActivityTask { type State = MyState; type Future = Pin>>>; diff --git a/lemmy_apub/src/extensions/group_extensions.rs b/lemmy_apub/src/extensions/group_extensions.rs index 766b3d796..cb1526720 100644 --- a/lemmy_apub/src/extensions/group_extensions.rs +++ b/lemmy_apub/src/extensions/group_extensions.rs @@ -5,6 +5,8 @@ use lemmy_db::{category::Category, Crud}; use lemmy_utils::LemmyError; use serde::{Deserialize, Serialize}; +/// Activitystreams extension to allow (de)serializing additional Community fields `category` and +/// `sensitive` (called 'nsfw' in Lemmy). #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GroupExtension { diff --git a/lemmy_apub/src/extensions/page_extension.rs b/lemmy_apub/src/extensions/page_extension.rs index aa3d01604..8ed12a0c7 100644 --- a/lemmy_apub/src/extensions/page_extension.rs +++ b/lemmy_apub/src/extensions/page_extension.rs @@ -2,6 +2,9 @@ use activitystreams::unparsed::UnparsedMutExt; use activitystreams_ext::UnparsedExtension; use serde::{Deserialize, Serialize}; +/// Activitystreams extension to allow (de)serializing additional Post fields +/// `comemnts_enabled` (called 'locked' in Lemmy), +/// `sensitive` (called 'nsfw') and `stickied`. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PageExtension { diff --git a/lemmy_apub/src/extensions/signatures.rs b/lemmy_apub/src/extensions/signatures.rs index 8e8c31e4d..6ff61df45 100644 --- a/lemmy_apub/src/extensions/signatures.rs +++ b/lemmy_apub/src/extensions/signatures.rs @@ -24,11 +24,12 @@ lazy_static! { static ref HTTP_SIG_CONFIG: Config = Config::new(); } -/// Signs request headers with the given keypair. +/// Creates an HTTP post request to `inbox_url`, with the given `client` and `headers`, and +/// `activity` as request body. The request is signed with `private_key` and then sent. pub async fn sign_and_send( client: &Client, headers: BTreeMap, - url: &Url, + inbox_url: &Url, activity: String, actor_id: &Url, private_key: String, @@ -43,7 +44,7 @@ pub async fn sign_and_send( ); } let response = client - .post(&url.to_string()) + .post(&inbox_url.to_string()) .headers(header_map) .signature_with_digest( HTTP_SIG_CONFIG.clone(), @@ -63,6 +64,7 @@ pub async fn sign_and_send( Ok(response) } +/// Verifies the HTTP signature on an incoming inbox request. pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { let public_key = actor.public_key().context(location_info!())?; let verified = CONFIG2 @@ -90,8 +92,14 @@ pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result< } } -// The following is taken from here: -// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html +/// Extension for actor public key, which is needed on user and community for HTTP signatures. +/// +/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKeyExtension { + pub public_key: PublicKey, +} #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -101,12 +109,6 @@ pub struct PublicKey { pub public_key_pem: String, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PublicKeyExtension { - pub public_key: PublicKey, -} - impl PublicKey { pub fn to_ext(&self) -> PublicKeyExtension { PublicKeyExtension { diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index 986c9dd47..908d1a5ea 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -82,11 +82,11 @@ pub enum SearchAcceptedObjects { /// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// -/// Some working examples for use with the docker/federation/ setup: -/// http://lemmy_alpha:8540/c/main, or !main@lemmy_alpha:8540 -/// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540 -/// http://lemmy_alpha:8540/post/3 -/// http://lemmy_alpha:8540/comment/2 +/// Some working examples for use with the `docker/federation/` setup: +/// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541 +/// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551 +/// http://lemmy_gamma:8561/post/3 +/// http://lemmy_delta:8571/comment/2 pub async fn search_by_apub_id( query: &str, context: &LemmyContext, @@ -191,19 +191,27 @@ pub async fn search_by_apub_id( Ok(response) } +/// Get a remote actor from its apub ID (either a user or a community). Thin wrapper around +/// `get_or_fetch_and_upsert_user()` and `get_or_fetch_and_upsert_community()`. +/// +/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. +/// Otherwise it is fetched from the remote instance, stored and returned. pub(crate) async fn get_or_fetch_and_upsert_actor( apub_id: &Url, context: &LemmyContext, ) -> Result, LemmyError> { - let user = get_or_fetch_and_upsert_user(apub_id, context).await; - let actor: Box = match user { - Ok(u) => Box::new(u), - Err(_) => Box::new(get_or_fetch_and_upsert_community(apub_id, context).await?), + let community = get_or_fetch_and_upsert_community(apub_id, context).await; + let actor: Box = match community { + Ok(c) => Box::new(c), + Err(_) => Box::new(get_or_fetch_and_upsert_user(apub_id, context).await?), }; Ok(actor) } -/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user. +/// Get a user from its apub ID. +/// +/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. +/// Otherwise it is fetched from the remote instance, stored and returned. pub(crate) async fn get_or_fetch_and_upsert_user( apub_id: &Url, context: &LemmyContext, @@ -241,7 +249,8 @@ pub(crate) async fn get_or_fetch_and_upsert_user( } /// Determines when a remote actor should be refetched from its instance. In release builds, this is -/// ACTOR_REFETCH_INTERVAL_SECONDS after the last refetch, in debug builds always. +/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds +/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`. /// /// TODO it won't pick up new avatars, summaries etc until a day after. /// Actors need an "update" activity pushed to other servers to fix this. @@ -255,7 +264,10 @@ fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool { last_refreshed.lt(&(naive_now() - update_interval)) } -/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community. +/// Get a community from its apub ID. +/// +/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. +/// Otherwise it is fetched from the remote instance, stored and returned. pub(crate) async fn get_or_fetch_and_upsert_community( apub_id: &Url, context: &LemmyContext, @@ -280,6 +292,9 @@ pub(crate) async fn get_or_fetch_and_upsert_community( } } +/// Request a community by apub ID from a remote instance, including moderators. If `community_id`, +/// is set, this is an update for a community which is already known locally. If not, we don't know +/// the community yet and also pull the outbox, to get some initial posts. async fn fetch_remote_community( apub_id: &Url, context: &LemmyContext, @@ -358,6 +373,10 @@ async fn fetch_remote_community( Ok(community) } +/// Gets a post by its apub ID. If it exists locally, it is returned directly. Otherwise it is +/// pulled from its apub ID, inserted and returned. +/// +/// The parent community is also pulled if necessary. Comments are not pulled. pub(crate) async fn get_or_fetch_and_insert_post( post_ap_id: &Url, context: &LemmyContext, @@ -383,6 +402,10 @@ pub(crate) async fn get_or_fetch_and_insert_post( } } +/// Gets a comment by its apub ID. If it exists locally, it is returned directly. Otherwise it is +/// pulled from its apub ID, inserted and returned. +/// +/// The parent community, post and comment are also pulled if necessary. pub(crate) async fn get_or_fetch_and_insert_comment( comment_ap_id: &Url, context: &LemmyContext, diff --git a/lemmy_apub/src/http/comment.rs b/lemmy_apub/src/http/comment.rs index 040be2b03..936cd9813 100644 --- a/lemmy_apub/src/http/comment.rs +++ b/lemmy_apub/src/http/comment.rs @@ -15,7 +15,7 @@ pub struct CommentQuery { comment_id: String, } -/// Return the post json over HTTP. +/// Return the ActivityPub json representation of a local comment over HTTP. pub async fn get_apub_comment( info: Path, context: web::Data, diff --git a/lemmy_apub/src/http/community.rs b/lemmy_apub/src/http/community.rs index 0559536ba..bd9c86fea 100644 --- a/lemmy_apub/src/http/community.rs +++ b/lemmy_apub/src/http/community.rs @@ -19,7 +19,7 @@ pub struct CommunityQuery { community_name: String, } -/// Return the community json over HTTP. +/// Return the ActivityPub json representation of a local community over HTTP. pub async fn get_apub_community_http( info: web::Path, context: web::Data, @@ -62,6 +62,8 @@ pub async fn get_apub_community_followers( Ok(create_apub_response(&collection)) } +/// Returns the community outbox, which is populated by a maximum of 20 posts (but no other +/// activites like votes or comments). pub async fn get_apub_community_outbox( info: web::Path, context: web::Data, diff --git a/lemmy_apub/src/http/post.rs b/lemmy_apub/src/http/post.rs index b3dd8ce89..7f4bb447c 100644 --- a/lemmy_apub/src/http/post.rs +++ b/lemmy_apub/src/http/post.rs @@ -15,7 +15,7 @@ pub struct PostQuery { post_id: String, } -/// Return the post json over HTTP. +/// Return the ActivityPub json representation of a local post over HTTP. pub async fn get_apub_post( info: web::Path, context: web::Data, diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs index 9158eede3..85a5f22cc 100644 --- a/lemmy_apub/src/http/user.rs +++ b/lemmy_apub/src/http/user.rs @@ -11,7 +11,7 @@ pub struct UserQuery { user_name: String, } -/// Return the user json over HTTP. +/// Return the ActivityPub json representation of a local user over HTTP. pub async fn get_apub_user_http( info: web::Path, context: web::Data, diff --git a/lemmy_apub/src/inbox/community_inbox.rs b/lemmy_apub/src/inbox/community_inbox.rs index 2c36c6840..11d09972b 100644 --- a/lemmy_apub/src/inbox/community_inbox.rs +++ b/lemmy_apub/src/inbox/community_inbox.rs @@ -25,6 +25,7 @@ use log::info; use serde::{Deserialize, Serialize}; use std::fmt::Debug; +/// Allowed activities for community inbox. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub enum ValidTypes { @@ -90,7 +91,7 @@ pub async fn community_inbox( res } -/// Handle a follow request from a remote user, adding it to the local database and returning an +/// Handle a follow request from a remote user, adding the user as follower and returning an /// Accept activity. async fn handle_follow( activity: AnyBase, @@ -117,6 +118,7 @@ async fn handle_follow( Ok(HttpResponse::Ok().finish()) } +/// Handle `Undo/Follow` from a user, removing the user from followers list. async fn handle_undo_follow( activity: AnyBase, user: User_, diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs index d70568061..f3a1177eb 100644 --- a/lemmy_apub/src/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -23,6 +23,7 @@ use log::debug; use serde::{Deserialize, Serialize}; use std::fmt::Debug; +/// Allowed activity types for shared inbox. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub enum ValidTypes { @@ -40,7 +41,7 @@ pub enum ValidTypes { // but it might still work due to the anybase conversion pub type AcceptedActivities = ActorAndObject; -/// Handler for all incoming receive to user inboxes. +/// Handler for all incoming requests to shared inbox. pub async fn shared_inbox( request: HttpRequest, input: web::Json, diff --git a/lemmy_apub/src/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs index 2ee6d6edf..ebb11a56e 100644 --- a/lemmy_apub/src/inbox/user_inbox.rs +++ b/lemmy_apub/src/inbox/user_inbox.rs @@ -30,6 +30,7 @@ use log::debug; use serde::{Deserialize, Serialize}; use std::fmt::Debug; +/// Allowed activities for user inbox. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub enum ValidTypes { @@ -42,7 +43,7 @@ pub enum ValidTypes { pub type AcceptedActivities = ActorAndObject; -/// Handler for all incoming receive to user inboxes. +/// Handler for all incoming activities to user inboxes. pub async fn user_inbox( request: HttpRequest, input: web::Json, diff --git a/lemmy_apub/src/lib.rs b/lemmy_apub/src/lib.rs index 4c8f96220..c93d6477c 100644 --- a/lemmy_apub/src/lib.rs +++ b/lemmy_apub/src/lib.rs @@ -29,13 +29,24 @@ use lemmy_websocket::LemmyContext; use serde::Serialize; use url::{ParseError, Url}; +/// Activitystreams type for community type GroupExt = Ext2, GroupExtension, PublicKeyExtension>; +/// Activitystreams type for user type PersonExt = Ext1, PublicKeyExtension>; +/// Activitystreams type for post type PageExt = Ext1; pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; -// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list. +/// Checks if the ID is allowed for sending or receiving. +/// +/// In particular, it checks for: +/// - federation being enabled (if its disabled, only local URLs are allowed) +/// - the correct scheme (either http or https) +/// - URL being in the allowlist (if it is active) +/// - URL not being in the blocklist (if it is active) +/// +/// Note that only one of allowlist and blacklist can be enabled, not both. fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { let settings = Settings::get(); let domain = apub_id.domain().context(location_info!())?.to_string(); @@ -90,10 +101,11 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { } } +/// Trait for converting an object or actor into the respective ActivityPub type. #[async_trait::async_trait(?Send)] pub trait ToApub { - type Response; - async fn to_apub(&self, pool: &DbPool) -> Result; + type ApubType; + async fn to_apub(&self, pool: &DbPool) -> Result; fn to_tombstone(&self) -> Result; } @@ -104,9 +116,8 @@ pub trait FromApub { /// /// * `apub` The object to read from /// * `context` LemmyContext which holds DB pool, HTTP client etc - /// * `expected_domain` If present, ensure that the apub object comes from the same domain as - /// this URL - /// + /// * `expected_domain` If present, ensure that the domains of this and of the apub object ID are + /// identical async fn from_apub( apub: &Self::ApubType, context: &LemmyContext, @@ -116,6 +127,8 @@ pub trait FromApub { Self: Sized; } +/// Common functions for ActivityPub objects, which are implemented by most (but not all) objects +/// and actors in Lemmy. #[async_trait::async_trait(?Send)] pub trait ApubObjectType { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>; @@ -138,6 +151,8 @@ pub trait ApubLikeableType { -> Result<(), LemmyError>; } +/// Common methods provided by ActivityPub actors (community and user). Not all methods are +/// implemented by all actors. #[async_trait::async_trait(?Send)] pub trait ActorType { fn actor_id_str(&self) -> String; @@ -149,9 +164,6 @@ pub trait ActorType { /// numeric id in the database, used for insert_activity fn user_id(&self) -> i32; - // These two have default impls, since currently a community can't follow anything, - // and a user can't be followed (yet) - #[allow(unused_variables)] async fn send_follow( &self, follow_actor_id: &Url, @@ -163,7 +175,6 @@ pub trait ActorType { context: &LemmyContext, ) -> Result<(), LemmyError>; - #[allow(unused_variables)] async fn send_accept_follow( &self, follow: Follow, @@ -234,6 +245,8 @@ pub trait ActorType { } } +/// Store a sent or received activity in the database, for logging purposes. These records are not +/// persistent. pub async fn insert_activity( user_id: i32, data: T, diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs index 537ab958e..efd2064d6 100644 --- a/lemmy_apub/src/objects/comment.rs +++ b/lemmy_apub/src/objects/comment.rs @@ -32,7 +32,7 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for Comment { - type Response = Note; + type ApubType = Note; async fn to_apub(&self, pool: &DbPool) -> Result { let mut comment = Note::new(); @@ -82,7 +82,9 @@ impl ToApub for Comment { impl FromApub for CommentForm { type ApubType = Note; - /// Parse an ActivityPub note received from another instance into a Lemmy comment + /// Converts a `Note` to `CommentForm`. + /// + /// If the parent community, post and comment(s) are not known locally, these are also fetched. async fn from_apub( note: &Note, context: &LemmyContext, diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs index 6a4b1ee5c..f6289f267 100644 --- a/lemmy_apub/src/objects/community.rs +++ b/lemmy_apub/src/objects/community.rs @@ -32,9 +32,8 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for Community { - type Response = GroupExt; + type ApubType = GroupExt; - // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. async fn to_apub(&self, pool: &DbPool) -> Result { // The attributed to, is an ordered vector with the creator actor_ids first, // then the rest of the moderators @@ -108,7 +107,6 @@ impl ToApub for Community { impl FromApub for CommunityForm { type ApubType = GroupExt; - /// Parse an ActivityPub group received from another instance into a Lemmy community. async fn from_apub( group: &GroupExt, context: &LemmyContext, diff --git a/lemmy_apub/src/objects/post.rs b/lemmy_apub/src/objects/post.rs index 157dcbe27..8796146d5 100644 --- a/lemmy_apub/src/objects/post.rs +++ b/lemmy_apub/src/objects/post.rs @@ -31,7 +31,7 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for Post { - type Response = PageExt; + type ApubType = PageExt; // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. async fn to_apub(&self, pool: &DbPool) -> Result { @@ -94,7 +94,9 @@ impl ToApub for Post { impl FromApub for PostForm { type ApubType = PageExt; - /// Parse an ActivityPub page received from another instance into a Lemmy post. + /// Converts a `PageExt` to `PostForm`. + /// + /// If the post's community or creator are not known locally, these are also fetched. async fn from_apub( page: &PageExt, context: &LemmyContext, diff --git a/lemmy_apub/src/objects/private_message.rs b/lemmy_apub/src/objects/private_message.rs index 413ab22f4..119dfb56c 100644 --- a/lemmy_apub/src/objects/private_message.rs +++ b/lemmy_apub/src/objects/private_message.rs @@ -23,7 +23,7 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for PrivateMessage { - type Response = Note; + type ApubType = Note; async fn to_apub(&self, pool: &DbPool) -> Result { let mut private_message = Note::new(); @@ -58,7 +58,6 @@ impl ToApub for PrivateMessage { impl FromApub for PrivateMessageForm { type ApubType = Note; - /// Parse an ActivityPub note received from another instance into a Lemmy Private message async fn from_apub( note: &Note, context: &LemmyContext, diff --git a/lemmy_apub/src/objects/user.rs b/lemmy_apub/src/objects/user.rs index 944f7c1da..b1f660b17 100644 --- a/lemmy_apub/src/objects/user.rs +++ b/lemmy_apub/src/objects/user.rs @@ -21,11 +21,9 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for User_ { - type Response = PersonExt; + type ApubType = PersonExt; - // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. async fn to_apub(&self, _pool: &DbPool) -> Result { - // TODO go through all these to_string and to_owned() let mut person = Person::new(); person .set_context(activitystreams::context()) @@ -73,7 +71,7 @@ impl ToApub for User_ { #[async_trait::async_trait(?Send)] impl FromApub for UserForm { type ApubType = PersonExt; - /// Parse an ActivityPub person received from another instance into a Lemmy user. + async fn from_apub( person: &PersonExt, _context: &LemmyContext,