From ff265c7ebc2a9f5653922efa26262337e4f04fd4 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 28 Jul 2021 00:18:50 +0200 Subject: [PATCH 1/3] Rewrite apub post (de)serialization using structs (ref #1657) --- crates/api/src/post.rs | 10 +- crates/api_crud/src/post/create.rs | 11 +- crates/api_crud/src/post/update.rs | 6 +- crates/apub/src/activities/comment/create.rs | 12 +- crates/apub/src/activities/comment/update.rs | 12 +- .../apub/src/activities/community/add_mod.rs | 2 +- .../apub/src/activities/community/announce.rs | 39 +++ .../src/activities/community/block_user.rs | 2 +- crates/apub/src/activities/community/mod.rs | 27 +- .../activities/community/undo_block_user.rs | 2 +- .../apub/src/activities/community/update.rs | 2 +- crates/apub/src/activities/deletion/delete.rs | 5 +- .../src/activities/deletion/undo_delete.rs | 5 +- crates/apub/src/activities/mod.rs | 56 +++- crates/apub/src/activities/post/create.rs | 81 ++++- crates/apub/src/activities/post/update.rs | 92 +++--- crates/apub/src/activities/removal/remove.rs | 4 +- .../src/activities/removal/undo_remove.rs | 2 +- crates/apub/src/activities/send/comment.rs | 2 +- crates/apub/src/activities/send/community.rs | 2 +- crates/apub/src/activities/send/mod.rs | 19 -- crates/apub/src/activities/send/person.rs | 2 +- crates/apub/src/activities/send/post.rs | 63 +--- .../src/activities/send/private_message.rs | 2 +- crates/apub/src/activities/voting/dislike.rs | 2 +- crates/apub/src/activities/voting/like.rs | 2 +- .../src/activities/voting/undo_dislike.rs | 2 +- .../apub/src/activities/voting/undo_like.rs | 2 +- crates/apub/src/activity_queue.rs | 77 ++++- crates/apub/src/extensions/mod.rs | 1 - crates/apub/src/extensions/page_extension.rs | 36 --- crates/apub/src/fetcher/objects.rs | 5 +- crates/apub/src/fetcher/search.rs | 5 +- crates/apub/src/lib.rs | 7 +- crates/apub/src/objects/post.rs | 305 ++++++++---------- crates/apub_lib/src/lib.rs | 2 +- 36 files changed, 517 insertions(+), 389 deletions(-) delete mode 100644 crates/apub/src/extensions/page_extension.rs diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs index 4a55b7e05..84e0c849e 100644 --- a/crates/api/src/post.rs +++ b/crates/api/src/post.rs @@ -9,7 +9,7 @@ use lemmy_api_common::{ mark_post_as_read, post::*, }; -use lemmy_apub::{ApubLikeableType, ApubObjectType}; +use lemmy_apub::{activities::post::update::UpdatePost, ApubLikeableType}; use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable}; use lemmy_db_schema::source::{moderator::*, post::*}; use lemmy_db_views::post_view::PostView; @@ -140,9 +140,7 @@ impl Perform for LockPost { blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??; // apub updates - updated_post - .send_update(&local_user_view.person, context) - .await?; + UpdatePost::send(&updated_post, &local_user_view.person, context).await?; // Refetch the post let post_id = data.post_id; @@ -214,9 +212,7 @@ impl Perform for StickyPost { // Apub updates // TODO stickied should pry work like locked for ease of use - updated_post - .send_update(&local_user_view.person, context) - .await?; + UpdatePost::send(&updated_post, &local_user_view.person, context).await?; // Refetch the post let post_id = data.post_id; diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index dc99f3ecf..89f71d939 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ mark_post_as_read, post::*, }; -use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType}; +use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, EndpointType}; use lemmy_db_queries::{source::post::Post_, Crud, Likeable}; use lemmy_db_schema::source::post::*; use lemmy_db_views::post_view::PostView; @@ -82,9 +82,12 @@ impl PerformCrud for CreatePost { .await? .map_err(|_| ApiError::err("couldnt_create_post"))?; - updated_post - .send_create(&local_user_view.person, context) - .await?; + lemmy_apub::activities::post::create::CreatePost::send( + &updated_post, + &local_user_view.person, + context, + ) + .await?; // They like their own post by default let person_id = local_user_view.person.id; diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 5166f2127..6b2780376 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -1,7 +1,7 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*}; -use lemmy_apub::ApubObjectType; +use lemmy_apub::activities::post::update::UpdatePost; use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable}; use lemmy_db_schema::{naive_now, source::post::*}; use lemmy_db_views::post_view::PostView; @@ -89,9 +89,7 @@ impl PerformCrud for EditPost { }; // Send apub update - updated_post - .send_update(&local_user_view.person, context) - .await?; + UpdatePost::send(&updated_post, &local_user_view.person, context).await?; let post_id = data.post_id; let mut post_view = blocking(context.pool(), move |conn| { diff --git a/crates/apub/src/activities/comment/create.rs b/crates/apub/src/activities/comment/create.rs index 84dab1071..b38553359 100644 --- a/crates/apub/src/activities/comment/create.rs +++ b/crates/apub/src/activities/comment/create.rs @@ -1,10 +1,12 @@ use crate::{ activities::{ comment::{get_notif_recipients, send_websocket_message}, + extract_community, verify_activity, verify_person_in_community, }, objects::FromApub, + ActorType, NoteExt, }; use activitystreams::{activity::kind::CreateType, base::BaseExt}; @@ -33,8 +35,16 @@ impl ActivityHandler for CreateComment { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { + let community = extract_community(&self.cc, context, request_counter).await?; + verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community( + &self.common.actor, + &community.actor_id(), + context, + request_counter, + ) + .await?; verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?; // TODO: should add a check that the correct community is in cc (probably needs changes to // comment deserialization) diff --git a/crates/apub/src/activities/comment/update.rs b/crates/apub/src/activities/comment/update.rs index 142656f50..5e785229f 100644 --- a/crates/apub/src/activities/comment/update.rs +++ b/crates/apub/src/activities/comment/update.rs @@ -1,10 +1,12 @@ use crate::{ activities::{ comment::{get_notif_recipients, send_websocket_message}, + extract_community, verify_activity, verify_person_in_community, }, objects::FromApub, + ActorType, NoteExt, }; use activitystreams::{activity::kind::UpdateType, base::BaseExt}; @@ -33,8 +35,16 @@ impl ActivityHandler for UpdateComment { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { + let community = extract_community(&self.cc, context, request_counter).await?; + verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community( + &self.common.actor, + &community.actor_id(), + context, + request_counter, + ) + .await?; verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?; Ok(()) } diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index 2785856c1..fd22b978a 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -38,7 +38,7 @@ impl ActivityHandler for AddMod { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?; Ok(()) diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 301ccc648..0bdb8bc06 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -4,12 +4,14 @@ use crate::{ community::{ add_mod::AddMod, block_user::BlockUserFromCommunity, + list_community_follower_inboxes, undo_block_user::UndoBlockUserFromCommunity, }, deletion::{ delete::DeletePostCommentOrCommunity, undo_delete::UndoDeletePostCommentOrCommunity, }, + generate_activity_id, post::{create::CreatePost, update::UpdatePost}, removal::{ remove::RemovePostCommentCommunityOrMod, @@ -24,11 +26,16 @@ use crate::{ undo_like::UndoLikePostOrComment, }, }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, http::is_activity_already_known, insert_activity, + ActorType, + CommunityType, }; use activitystreams::activity::kind::AnnounceType; use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -66,6 +73,38 @@ pub struct AnnounceActivity { common: ActivityCommonFields, } +impl AnnounceActivity { + pub async fn send( + object: AnnouncableActivities, + community: &Community, + additional_inboxes: Vec, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let announce = AnnounceActivity { + to: PublicUrl::Public, + object, + cc: vec![community.followers_url()], + kind: AnnounceType::Announce, + common: ActivityCommonFields { + context: lemmy_context()?.into(), + id: generate_activity_id(AnnounceType::Announce)?, + actor: community.actor_id(), + unparsed: Default::default(), + }, + }; + let inboxes = list_community_follower_inboxes(community, additional_inboxes, context).await?; + send_activity_new( + context, + &announce, + &announce.common.id, + community, + inboxes, + false, + ) + .await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for AnnounceActivity { async fn verify( diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index c20652b02..1c5f0eaf5 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -36,7 +36,7 @@ impl ActivityHandler for BlockUserFromCommunity { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; Ok(()) } diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index 81152d926..62b39c6e8 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -1,8 +1,11 @@ +use crate::{check_is_apub_id_valid, CommunityType}; +use itertools::Itertools; use lemmy_api_common::{blocking, community::CommunityResponse}; -use lemmy_db_schema::CommunityId; +use lemmy_db_schema::{source::community::Community, CommunityId}; use lemmy_db_views_actor::community_view::CommunityView; -use lemmy_utils::LemmyError; +use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext}; +use url::Url; pub mod add_mod; pub mod announce; @@ -33,3 +36,23 @@ pub(crate) async fn send_websocket_message< Ok(()) } + +async fn list_community_follower_inboxes( + community: &Community, + additional_inboxes: Vec, + context: &LemmyContext, +) -> Result, LemmyError> { + Ok( + vec![ + community.get_follower_inboxes(context.pool()).await?, + additional_inboxes, + ] + .iter() + .flatten() + .unique() + .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname())) + .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok()) + .map(|inbox| inbox.to_owned()) + .collect(), + ) +} diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index 025c498a6..d44fe2669 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -36,7 +36,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; self.object.verify(context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index cfee29da7..ce1854969 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -39,7 +39,7 @@ impl ActivityHandler for UpdateCommunity { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; Ok(()) } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index f0f4185a7..f7e7fe5c2 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -64,7 +64,8 @@ impl ActivityHandler for DeletePostCommentOrCommunity { } // deleting a post or comment else { - verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter) + .await?; let object_creator = get_post_or_comment_actor_id(&self.object, context, request_counter).await?; verify_urls_match(&self.common.actor, &object_creator)?; @@ -83,7 +84,7 @@ impl ActivityHandler for DeletePostCommentOrCommunity { if let Ok(community) = object_community { if community.local { // repeat these checks just to be sure - verify_person_in_community(&self.common().actor, &self.cc, context, request_counter) + verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter) .await?; verify_mod_action(&self.common.actor, self.object.clone(), context).await?; let mod_ = diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 1de5ca587..94e44d7ac 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -54,7 +54,8 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity { } // restoring a post or comment else { - verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter) + .await?; verify_urls_match(&self.common.actor, &self.object.common().actor)?; } Ok(()) @@ -71,7 +72,7 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity { if let Ok(community) = object_community { if community.local { // repeat these checks just to be sure - verify_person_in_community(&self.common().actor, &self.cc, context, request_counter) + verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter) .await?; verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?; let mod_ = diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index d9f3dcdde..af133167f 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -13,9 +13,10 @@ use lemmy_db_schema::{ DbUrl, }; use lemmy_db_views_actor::community_view::CommunityView; -use lemmy_utils::LemmyError; +use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; -use url::Url; +use url::{ParseError, Url}; +use uuid::Uuid; pub mod comment; pub mod community; @@ -41,27 +42,34 @@ async fn verify_person( Ok(()) } -/// Fetches the person and community to verify their type, then checks if person is banned from site -/// or community. -async fn verify_person_in_community( - person_id: &Url, +pub(crate) async fn extract_community( cc: &[Url], context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?; let mut cc_iter = cc.iter(); - let community: Community = loop { + loop { if let Some(cid) = cc_iter.next() { if let Ok(c) = get_or_fetch_and_upsert_community(cid, context, request_counter).await { - break c; + break Ok(c); } } else { return Err(anyhow!("No community found in cc").into()); } - }; - check_community_or_site_ban(&person, community.id, context.pool()).await?; - Ok(community) + } +} + +/// Fetches the person and community to verify their type, then checks if person is banned from site +/// or community. +async fn verify_person_in_community( + person_id: &Url, + community_id: &Url, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> { + let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?; + let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?; + check_community_or_site_ban(&person, community.id, context.pool()).await } /// Simply check that the url actually refers to a valid group. @@ -80,13 +88,16 @@ fn verify_activity(common: &ActivityCommonFields) -> Result<(), LemmyError> { Ok(()) } -async fn verify_mod_action( +/// Verify that the actor is a community mod. This check is only run if the community is local, +/// because in case of remote communities, admins can also perform mod actions. As admin status +/// is not federated, we cant verify their actions remotely. +pub(crate) async fn verify_mod_action( actor_id: &Url, - activity_cc: Url, + community: Url, context: &LemmyContext, ) -> Result<(), LemmyError> { let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &activity_cc.into()) + Community::read_from_apub_id(conn, &community.into()) }) .await??; @@ -120,3 +131,18 @@ fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<() } Ok(()) } + +/// 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, +{ + let id = format!( + "{}/activities/{}/{}", + Settings::get().get_protocol_and_hostname(), + kind.to_string().to_lowercase(), + Uuid::new_v4() + ); + Url::parse(&id) +} diff --git a/crates/apub/src/activities/post/create.rs b/crates/apub/src/activities/post/create.rs index a2ccf4ce5..909a6148d 100644 --- a/crates/apub/src/activities/post/create.rs +++ b/crates/apub/src/activities/post/create.rs @@ -1,13 +1,30 @@ use crate::{ - activities::{post::send_websocket_message, verify_activity, verify_person_in_community}, + activities::{ + community::announce::AnnouncableActivities, + extract_community, + generate_activity_id, + post::send_websocket_message, + verify_activity, + verify_person_in_community, + }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, - objects::FromApub, + objects::{post::Page, FromApub, ToApub}, ActorType, - PageExt, }; -use activitystreams::{activity::kind::CreateType, base::BaseExt}; -use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl}; -use lemmy_db_schema::source::post::Post; +use activitystreams::activity::kind::CreateType; +use anyhow::anyhow; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + verify_domains_match, + verify_urls_match, + ActivityCommonFields, + ActivityHandler, + PublicUrl, +}; +use lemmy_db_queries::Crud; +use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; use url::Url; @@ -16,14 +33,40 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct CreatePost { to: PublicUrl, - object: PageExt, - cc: Vec, - #[serde(rename = "type")] - kind: CreateType, + object: Page, + cc: [Url; 1], + r#type: CreateType, #[serde(flatten)] common: ActivityCommonFields, } +impl CreatePost { + pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> { + let community_id = post.community_id; + let community = blocking(context.pool(), move |conn| { + Community::read(conn, community_id) + }) + .await??; + + let id = generate_activity_id(CreateType::Create)?; + let create = CreatePost { + to: PublicUrl::Public, + object: post.to_apub(context.pool()).await?, + cc: [community.actor_id()], + r#type: Default::default(), + common: ActivityCommonFields { + context: lemmy_context()?.into(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + + let activity = AnnouncableActivities::CreatePost(create); + send_to_community_new(activity, &id, actor, &community, vec![], context).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for CreatePost { async fn verify( @@ -31,9 +74,23 @@ impl ActivityHandler for CreatePost { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { + let community = extract_community(&self.cc, context, request_counter).await?; + let community_id = &community.actor_id(); + verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; - verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?; + verify_person_in_community(&self.common.actor, community_id, context, request_counter).await?; + verify_domains_match(&self.common.actor, &self.object.id)?; + verify_urls_match(&self.common.actor, &self.object.attributed_to)?; + // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. + // However, when fetching a remote post we generate a new create activity with the current + // locked/stickied value, so this check may fail. So only check if its a local community, + // because then we will definitely receive all create and update activities separately. + let is_stickied_or_locked = + self.object.stickied == Some(true) || self.object.comments_enabled == Some(false); + if community.local && is_stickied_or_locked { + return Err(anyhow!("New post cannot be stickied or locked").into()); + } + self.object.verify(context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/post/update.rs b/crates/apub/src/activities/post/update.rs index 13456dcd8..a8cec9e73 100644 --- a/crates/apub/src/activities/post/update.rs +++ b/crates/apub/src/activities/post/update.rs @@ -1,24 +1,24 @@ use crate::{ activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, post::send_websocket_message, verify_activity, verify_mod_action, verify_person_in_community, }, - objects::{FromApub, FromApubToForm}, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, + fetcher::community::get_or_fetch_and_upsert_community, + objects::{post::Page, FromApub, ToApub}, ActorType, - PageExt, }; -use activitystreams::{activity::kind::UpdateType, base::BaseExt}; -use anyhow::Context; +use activitystreams::activity::kind::UpdateType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl}; -use lemmy_db_queries::ApubObject; -use lemmy_db_schema::{ - source::post::{Post, PostForm}, - DbUrl, -}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_db_queries::Crud; +use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; +use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; use url::Url; @@ -26,14 +26,39 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct UpdatePost { to: PublicUrl, - object: PageExt, - cc: Vec, - #[serde(rename = "type")] - kind: UpdateType, + object: Page, + cc: [Url; 1], + r#type: UpdateType, #[serde(flatten)] common: ActivityCommonFields, } +impl UpdatePost { + pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> { + let community_id = post.community_id; + let community = blocking(context.pool(), move |conn| { + Community::read(conn, community_id) + }) + .await??; + + let id = generate_activity_id(UpdateType::Update)?; + let update = UpdatePost { + to: PublicUrl::Public, + object: post.to_apub(context.pool()).await?, + cc: [community.actor_id()], + r#type: Default::default(), + common: ActivityCommonFields { + context: lemmy_context()?.into(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let activity = AnnouncableActivities::UpdatePost(update); + send_to_community_new(activity, &id, actor, &community, vec![], context).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for UpdatePost { async fn verify( @@ -41,34 +66,19 @@ impl ActivityHandler for UpdatePost { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { + let community_id = get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter) + .await? + .actor_id(); + let is_mod_action = self.object.is_mod_action(context.pool()).await?; + verify_activity(self.common())?; - let community = - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; - - let temp_post = PostForm::from_apub( - &self.object, - context, - self.common.actor.clone(), - request_counter, - true, - ) - .await?; - let post_id: DbUrl = temp_post.ap_id.context(location_info!())?; - let old_post = blocking(context.pool(), move |conn| { - Post::read_from_apub_id(conn, &post_id) - }) - .await??; - let stickied = temp_post.stickied.context(location_info!())?; - let locked = temp_post.locked.context(location_info!())?; - // community mod changed locked/sticky status - if (stickied != old_post.stickied) || (locked != old_post.locked) { - verify_mod_action(&self.common.actor, community.actor_id(), context).await?; + verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?; + if is_mod_action { + verify_mod_action(&self.common.actor, community_id, context).await?; + } else { + verify_urls_match(&self.common.actor, &self.object.attributed_to)?; } - // user edited their own post - else { - verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?; - } - + self.object.verify(context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/removal/remove.rs b/crates/apub/src/activities/removal/remove.rs index a30f23327..7e62ab7bd 100644 --- a/crates/apub/src/activities/removal/remove.rs +++ b/crates/apub/src/activities/removal/remove.rs @@ -64,13 +64,13 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod { } // removing community mod else if let Some(target) = &self.target { - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; verify_add_remove_moderator_target(target, self.cc[0].clone())?; } // removing a post or comment else { - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; } Ok(()) diff --git a/crates/apub/src/activities/removal/undo_remove.rs b/crates/apub/src/activities/removal/undo_remove.rs index 997d527d8..ca77a31da 100644 --- a/crates/apub/src/activities/removal/undo_remove.rs +++ b/crates/apub/src/activities/removal/undo_remove.rs @@ -52,7 +52,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity { } // removing a post or comment else { - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; } self.object.verify(context, request_counter).await?; diff --git a/crates/apub/src/activities/send/comment.rs b/crates/apub/src/activities/send/comment.rs index b93f9e1c9..be24dd07a 100644 --- a/crates/apub/src/activities/send/comment.rs +++ b/crates/apub/src/activities/send/comment.rs @@ -1,5 +1,5 @@ use crate::{ - activities::send::generate_activity_id, + activities::generate_activity_id, activity_queue::{send_comment_mentions, send_to_community}, extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index bf7514797..96cb058a4 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -1,5 +1,5 @@ use crate::{ - activities::send::generate_activity_id, + activities::generate_activity_id, activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers}, check_is_apub_id_valid, extensions::context::lemmy_context, diff --git a/crates/apub/src/activities/send/mod.rs b/crates/apub/src/activities/send/mod.rs index 10dd8a263..65135bdda 100644 --- a/crates/apub/src/activities/send/mod.rs +++ b/crates/apub/src/activities/send/mod.rs @@ -1,24 +1,5 @@ -use lemmy_utils::settings::structs::Settings; -use url::{ParseError, Url}; -use uuid::Uuid; - pub(crate) mod comment; pub(crate) mod community; pub(crate) mod person; pub(crate) mod post; pub(crate) mod private_message; - -/// 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, -{ - let id = format!( - "{}/activities/{}/{}", - Settings::get().get_protocol_and_hostname(), - kind.to_string().to_lowercase(), - Uuid::new_v4() - ); - Url::parse(&id) -} diff --git a/crates/apub/src/activities/send/person.rs b/crates/apub/src/activities/send/person.rs index b1fc0cd2b..a5e792512 100644 --- a/crates/apub/src/activities/send/person.rs +++ b/crates/apub/src/activities/send/person.rs @@ -1,5 +1,5 @@ use crate::{ - activities::send::generate_activity_id, + activities::generate_activity_id, activity_queue::send_activity_single_dest, extensions::context::lemmy_context, ActorType, diff --git a/crates/apub/src/activities/send/post.rs b/crates/apub/src/activities/send/post.rs index c51d6f2d6..db96621b0 100644 --- a/crates/apub/src/activities/send/post.rs +++ b/crates/apub/src/activities/send/post.rs @@ -1,22 +1,19 @@ use crate::{ - activities::send::generate_activity_id, + activities::generate_activity_id, activity_queue::send_to_community, extensions::context::lemmy_context, - objects::ToApub, ActorType, ApubLikeableType, ApubObjectType, }; use activitystreams::{ activity::{ - kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType}, - Create, + kind::{DeleteType, DislikeType, LikeType, RemoveType, UndoType}, Delete, Dislike, Like, Remove, Undo, - Update, }, prelude::*, public, @@ -29,52 +26,20 @@ use lemmy_websocket::LemmyContext; #[async_trait::async_trait(?Send)] impl ApubObjectType for Post { - /// Send out information about a newly created post, to the followers of the community. - async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - - let community_id = self.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut create = Create::new( - creator.actor_id.to_owned().into_inner(), - page.into_any_base()?, - ); - create - .set_many_contexts(lemmy_context()?) - .set_id(generate_activity_id(CreateType::Create)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(create, creator, &community, None, context).await?; - Ok(()) + async fn send_create( + &self, + _creator: &Person, + _context: &LemmyContext, + ) -> Result<(), LemmyError> { + unimplemented!() } - /// Send out information about an edited post, to the followers of the community. - async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - - let community_id = self.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut update = Update::new( - creator.actor_id.to_owned().into_inner(), - page.into_any_base()?, - ); - update - .set_many_contexts(lemmy_context()?) - .set_id(generate_activity_id(UpdateType::Update)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(update, creator, &community, None, context).await?; - Ok(()) + async fn send_update( + &self, + _creator: &Person, + _context: &LemmyContext, + ) -> Result<(), LemmyError> { + unimplemented!() } async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/send/private_message.rs b/crates/apub/src/activities/send/private_message.rs index e5a30585b..d208b25d4 100644 --- a/crates/apub/src/activities/send/private_message.rs +++ b/crates/apub/src/activities/send/private_message.rs @@ -1,5 +1,5 @@ use crate::{ - activities::send::generate_activity_id, + activities::generate_activity_id, activity_queue::send_activity_single_dest, extensions::context::lemmy_context, objects::ToApub, diff --git a/crates/apub/src/activities/voting/dislike.rs b/crates/apub/src/activities/voting/dislike.rs index 18d72f394..b34b2d10b 100644 --- a/crates/apub/src/activities/voting/dislike.rs +++ b/crates/apub/src/activities/voting/dislike.rs @@ -29,7 +29,7 @@ impl ActivityHandler for DislikePostOrComment { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/voting/like.rs b/crates/apub/src/activities/voting/like.rs index ca899d3d3..50a4d44d4 100644 --- a/crates/apub/src/activities/voting/like.rs +++ b/crates/apub/src/activities/voting/like.rs @@ -29,7 +29,7 @@ impl ActivityHandler for LikePostOrComment { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/voting/undo_dislike.rs b/crates/apub/src/activities/voting/undo_dislike.rs index 11871e791..5ba3b47f9 100644 --- a/crates/apub/src/activities/voting/undo_dislike.rs +++ b/crates/apub/src/activities/voting/undo_dislike.rs @@ -29,7 +29,7 @@ impl ActivityHandler for UndoDislikePostOrComment { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_urls_match(&self.common.actor, &self.object.common().actor)?; self.object.verify(context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/voting/undo_like.rs b/crates/apub/src/activities/voting/undo_like.rs index 07c3c4709..2de03f4ba 100644 --- a/crates/apub/src/activities/voting/undo_like.rs +++ b/crates/apub/src/activities/voting/undo_like.rs @@ -29,7 +29,7 @@ impl ActivityHandler for UndoLikePostOrComment { request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?; + verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; verify_urls_match(&self.common.actor, &self.object.common().actor)?; self.object.verify(context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activity_queue.rs b/crates/apub/src/activity_queue.rs index 22b88d148..f4f8ce90a 100644 --- a/crates/apub/src/activity_queue.rs +++ b/crates/apub/src/activity_queue.rs @@ -1,4 +1,5 @@ use crate::{ + activities::community::announce::{AnnouncableActivities, AnnounceActivity}, check_is_apub_id_valid, extensions::signatures::sign_and_send, insert_activity, @@ -24,7 +25,7 @@ use itertools::Itertools; use lemmy_db_schema::source::{community::Community, person::Person}; use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; -use log::{debug, warn}; +use log::{debug, info, warn}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin}; @@ -171,6 +172,80 @@ where Ok(()) } +pub(crate) async fn send_to_community_new( + activity: AnnouncableActivities, + activity_id: &Url, + actor: &dyn ActorType, + community: &Community, + additional_inboxes: Vec, + context: &LemmyContext, +) -> Result<(), LemmyError> { + // if this is a local community, we need to do an announce from the community instead + if community.local { + insert_activity(activity_id, activity.clone(), true, false, context.pool()).await?; + AnnounceActivity::send(activity, community, additional_inboxes, context).await?; + } else { + let mut inboxes = additional_inboxes; + inboxes.push(community.get_shared_inbox_or_inbox_url()); + send_activity_new(context, &activity, activity_id, actor, inboxes, false).await?; + } + + Ok(()) +} + +pub(crate) async fn send_activity_new( + context: &LemmyContext, + activity: &T, + activity_id: &Url, + actor: &dyn ActorType, + inboxes: Vec, + sensitive: bool, +) -> Result<(), LemmyError> +where + T: Serialize, +{ + if !Settings::get().federation().enabled || inboxes.is_empty() { + return Ok(()); + } + + info!("Sending activity {}", activity_id.to_string()); + + // Don't send anything to ourselves + // TODO: this should be a debug assert + let hostname = Settings::get().get_hostname_without_port()?; + let inboxes: Vec<&Url> = inboxes + .iter() + .filter(|i| i.domain().expect("valid inbox url") != hostname) + .collect(); + + let serialised_activity = serde_json::to_string(&activity)?; + + insert_activity( + activity_id, + serialised_activity.clone(), + true, + sensitive, + context.pool(), + ) + .await?; + + for i in inboxes { + let message = SendActivityTask { + activity: serialised_activity.to_owned(), + inbox: i.to_owned(), + actor_id: actor.actor_id(), + private_key: actor.private_key().context(location_info!())?, + }; + if env::var("LEMMY_TEST_SEND_SYNC").is_ok() { + do_send(message, &Client::default()).await?; + } else { + context.activity_queue.queue::(message)?; + } + } + + Ok(()) +} + /// Create new `SendActivityTasks`, which will deliver the given activity to inboxes, as well as /// handling signing and retrying failed deliveres. /// diff --git a/crates/apub/src/extensions/mod.rs b/crates/apub/src/extensions/mod.rs index 19e37894d..781e89e6e 100644 --- a/crates/apub/src/extensions/mod.rs +++ b/crates/apub/src/extensions/mod.rs @@ -1,5 +1,4 @@ pub mod context; pub(crate) mod group_extension; -pub(crate) mod page_extension; pub(crate) mod person_extension; pub mod signatures; diff --git a/crates/apub/src/extensions/page_extension.rs b/crates/apub/src/extensions/page_extension.rs deleted file mode 100644 index 752fa2b4b..000000000 --- a/crates/apub/src/extensions/page_extension.rs +++ /dev/null @@ -1,36 +0,0 @@ -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 { - pub comments_enabled: Option, - pub sensitive: Option, - pub stickied: Option, -} - -impl UnparsedExtension for PageExtension -where - U: UnparsedMutExt, -{ - type Error = serde_json::Error; - - fn try_from_unparsed(unparsed_mut: &mut U) -> Result { - Ok(PageExtension { - comments_enabled: unparsed_mut.remove("commentsEnabled")?, - sensitive: unparsed_mut.remove("sensitive")?, - stickied: unparsed_mut.remove("stickied")?, - }) - } - - fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> { - unparsed_mut.insert("commentsEnabled", self.comments_enabled)?; - unparsed_mut.insert("sensitive", self.sensitive)?; - unparsed_mut.insert("stickied", self.stickied)?; - Ok(()) - } -} diff --git a/crates/apub/src/fetcher/objects.rs b/crates/apub/src/fetcher/objects.rs index af8a59f76..fd94f6499 100644 --- a/crates/apub/src/fetcher/objects.rs +++ b/crates/apub/src/fetcher/objects.rs @@ -1,8 +1,7 @@ use crate::{ fetcher::fetch::fetch_remote_object, - objects::FromApub, + objects::{post::Page, FromApub}, NoteExt, - PageExt, PostOrComment, }; use anyhow::anyhow; @@ -35,7 +34,7 @@ pub async fn get_or_fetch_and_insert_post( Err(NotFound {}) => { debug!("Fetching and creating remote post: {}", post_ap_id); let page = - fetch_remote_object::(context.client(), post_ap_id, recursion_counter).await?; + fetch_remote_object::(context.client(), post_ap_id, recursion_counter).await?; let post = Post::from_apub( &page, context, diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 5a09fd430..69076c151 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -6,11 +6,10 @@ use crate::{ is_deleted, }, find_object_by_id, - objects::FromApub, + objects::{post::Page, FromApub}, GroupExt, NoteExt, Object, - PageExt, PersonExt, }; use activitystreams::base::BaseExt; @@ -46,7 +45,7 @@ use url::Url; enum SearchAcceptedObjects { Person(Box), Group(Box), - Page(Box), + Page(Box), Comment(Box), } diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 1b1e3d37f..94da33cc6 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -11,7 +11,6 @@ pub mod objects; use crate::{ extensions::{ group_extension::GroupExtension, - page_extension::PageExtension, person_extension::PersonExtension, signatures::{PublicKey, PublicKeyExtension}, }, @@ -21,9 +20,9 @@ use activitystreams::{ activity::Follow, actor, base::AnyBase, - object::{ApObject, AsObject, Note, ObjectExt, Page}, + object::{ApObject, AsObject, Note, ObjectExt}, }; -use activitystreams_ext::{Ext1, Ext2}; +use activitystreams_ext::Ext2; use anyhow::{anyhow, Context}; use diesel::NotFound; use lemmy_api_common::blocking; @@ -54,8 +53,6 @@ pub type GroupExt = type PersonExt = Ext2>>, PersonExtension, PublicKeyExtension>; pub type SiteExt = actor::ApActor>; -/// Activitystreams type for post -pub type PageExt = Ext1, PageExtension>; pub type NoteExt = ApObject; #[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)] diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 19bfe8aab..ace6b9131 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -1,30 +1,23 @@ use crate::{ - check_is_apub_id_valid, - extensions::{context::lemmy_context, page_extension::PageExtension}, + activities::extract_community, + extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, - get_community_from_to_or_cc, - objects::{ - check_object_domain, - check_object_for_community_or_site_ban, - create_tombstone, - get_object_from_apub, - get_source_markdown_value, - set_content_and_source, - FromApub, - FromApubToForm, - ToApub, - }, - PageExt, + objects::{create_tombstone, FromApub, ToApub}, }; use activitystreams::{ - object::{kind::PageType, ApObject, Image, Page, Tombstone}, - prelude::*, + base::AnyBase, + object::{ + kind::{ImageType, PageType}, + Tombstone, + }, + primitives::OneOrMany, public, + unparsed::Unparsed, }; -use activitystreams_ext::Ext1; -use anyhow::Context; +use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; -use lemmy_db_queries::{Crud, DbPool}; +use lemmy_apub_lib::verify_domains_match; +use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_schema::{ self, source::{ @@ -34,9 +27,8 @@ use lemmy_db_schema::{ }, }; use lemmy_utils::{ - location_info, request::fetch_iframely_and_pictrs_data, - utils::{check_slurs, convert_datetime, remove_slurs}, + utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs}, LemmyError, }; use lemmy_websocket::LemmyContext; @@ -44,56 +36,44 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for Post { - type ApubType = PageExt; + type ApubType = Page; // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. - async fn to_apub(&self, pool: &DbPool) -> Result { - let mut page = ApObject::new(Page::new()); - + async fn to_apub(&self, pool: &DbPool) -> Result { let creator_id = self.creator_id; let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; - let community_id = self.community_id; let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; - page - // Not needed when the Post is embedded in a collection (like for community outbox) - // TODO: need to set proper context defining sensitive/commentsEnabled fields - // https://git.asonix.dog/Aardwolf/activitystreams/issues/5 - .set_many_contexts(lemmy_context()?) - .set_id(self.ap_id.to_owned().into_inner()) - .set_name(self.name.to_owned()) - // `summary` field for compatibility with lemmy v0.9.9 and older, - // TODO: remove this after some time - .set_summary(self.name.to_owned()) - .set_published(convert_datetime(self.published)) - .set_many_tos(vec![community.actor_id.into_inner(), public()]) - .set_attributed_to(creator.actor_id.into_inner()); + let source = self.body.clone().map(|body| Source { + content: body, + media_type: MediaTypeMarkdown::Markdown, + }); + let image = self.thumbnail_url.clone().map(|thumb| ImageObject { + content: ImageType::Image, + url: thumb.into(), + }); - if let Some(body) = &self.body { - set_content_and_source(&mut page, body)?; - } - - if let Some(url) = &self.url { - page.set_url::(url.to_owned().into()); - } - - if let Some(thumbnail_url) = &self.thumbnail_url { - let mut image = Image::new(); - image.set_url::(thumbnail_url.to_owned().into()); - page.set_image(image.into_any_base()?); - } - - if let Some(u) = self.updated { - page.set_updated(convert_datetime(u)); - } - - let ext = PageExtension { + let page = Page { + context: lemmy_context()?.into(), + r#type: PageType::Page, + id: self.ap_id.clone().into(), + attributed_to: creator.actor_id.into(), + to: [community.actor_id.into(), public()], + name: self.name.clone(), + content: self.body.as_ref().map(|b| markdown_to_html(b)), + media_type: MediaTypeHtml::Markdown, + source, + url: self.url.clone().map(|u| u.into()), + image, comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), stickied: Some(self.stickied), + published: convert_datetime(self.published), + updated: self.updated.map(convert_datetime), + unparsed: Default::default(), }; - Ok(Ext1::new(page, ext)) + Ok(page) } fn to_tombstone(&self) -> Result { @@ -106,138 +86,133 @@ impl ToApub for Post { } } -#[async_trait::async_trait(?Send)] -impl FromApub for Post { - type ApubType = PageExt; +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum MediaTypeMarkdown { + #[serde(rename = "text/markdown")] + Markdown, +} - /// Converts a `PageExt` to `PostForm`. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum MediaTypeHtml { + #[serde(rename = "text/html")] + Markdown, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Source { + content: String, + media_type: MediaTypeMarkdown, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ImageObject { + content: ImageType, + url: Url, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Page { + #[serde(rename = "@context")] + context: OneOrMany, + r#type: PageType, + pub(crate) id: Url, + pub(crate) attributed_to: Url, + to: [Url; 2], + name: String, + content: Option, + media_type: MediaTypeHtml, + source: Option, + url: Option, + image: Option, + pub(crate) comments_enabled: Option, + sensitive: Option, + pub(crate) stickied: Option, + published: DateTime, + updated: Option>, + + // unparsed fields + #[serde(flatten)] + unparsed: Unparsed, +} + +impl Page { + /// Only mods can change the post's stickied/locked status. So if either of these is changed from + /// the current value, it is a mod action and needs to be verified as such. /// - /// If the post's community or creator are not known locally, these are also fetched. - async fn from_apub( - page: &PageExt, - context: &LemmyContext, - expected_domain: Url, - request_counter: &mut i32, - mod_action_allowed: bool, - ) -> Result { - let post: Post = get_object_from_apub( - page, - context, - expected_domain, - request_counter, - mod_action_allowed, - ) + /// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]]. + pub(crate) async fn is_mod_action(&self, pool: &DbPool) -> Result { + let post_id = self.id.clone(); + let old_post = blocking(pool, move |conn| { + Post::read_from_apub_id(conn, &post_id.into()) + }) .await?; - check_object_for_community_or_site_ban(page, post.community_id, context, request_counter) - .await?; - Ok(post) + + let is_mod_action = if let Ok(old_post) = old_post { + self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked) + } else { + false + }; + Ok(is_mod_action) + } + + pub(crate) async fn verify( + &self, + _context: &LemmyContext, + _request_counter: &mut i32, + ) -> Result<(), LemmyError> { + check_slurs(&self.name)?; + verify_domains_match(&self.attributed_to, &self.id)?; + Ok(()) } } #[async_trait::async_trait(?Send)] -impl FromApubToForm for PostForm { +impl FromApub for Post { + type ApubType = Page; + async fn from_apub( - page: &PageExt, + page: &Page, context: &LemmyContext, - expected_domain: Url, + _expected_domain: Url, request_counter: &mut i32, - mod_action_allowed: bool, - ) -> Result { - let community = get_community_from_to_or_cc(page, context, request_counter).await?; - let ap_id = if mod_action_allowed { - let id = page.id_unchecked().context(location_info!())?; - check_is_apub_id_valid(id, community.local)?; - id.to_owned().into() - } else { - check_object_domain(page, expected_domain, community.local)? - }; - let ext = &page.ext_one; - let creator_actor_id = page - .inner - .attributed_to() - .as_ref() - .context(location_info!())? - .as_single_xsd_any_uri() - .context(location_info!())?; - + _mod_action_allowed: bool, + ) -> Result { let creator = - get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?; - - let thumbnail_url: Option = match &page.inner.image() { - Some(any_image) => Image::from_any_base( - any_image - .to_owned() - .as_one() - .context(location_info!())? - .to_owned(), - )? - .context(location_info!())? - .url() - .context(location_info!())? - .as_single_xsd_any_uri() - .map(|url| url.to_owned()), - None => None, - }; - let url = page - .inner - .url() - .map(|u| u.as_single_xsd_any_uri()) - .flatten() - .map(|u| u.to_owned()); + get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?; + let community = extract_community(&page.to, context, request_counter).await?; + let thumbnail_url: Option = page.image.clone().map(|i| i.url); let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = - if let Some(url) = &url { + if let Some(url) = &page.url { fetch_iframely_and_pictrs_data(context.client(), Some(url)).await } else { (None, None, None, thumbnail_url) }; - let name = page - .inner - .name() - // The following is for compatibility with lemmy v0.9.9 and older - // TODO: remove it after some time (along with the map above) - .or_else(|| page.inner.summary()) - .context(location_info!())? - .as_single_xsd_string() - .context(location_info!())? - .to_string(); - let body = get_source_markdown_value(page)?; - - // TODO: expected_domain is wrong in this case, because it simply takes the domain of the actor - // maybe we need to take id_unchecked() if the activity is from community to user? - // why did this work before? -> i dont think it did? - // -> try to make expected_domain optional and set it null if it is a mod action - - check_slurs(&name)?; - let body_slurs_removed = body.map(|b| remove_slurs(&b)); - Ok(PostForm { - name, - url: url.map(|u| u.into()), + let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content)); + let form = PostForm { + name: page.name.clone(), + url: page.url.clone().map(|u| u.into()), body: body_slurs_removed, creator_id: creator.id, community_id: community.id, removed: None, - locked: ext.comments_enabled.map(|e| !e), - published: page - .inner - .published() - .as_ref() - .map(|u| u.to_owned().naive_local()), - updated: page - .inner - .updated() - .as_ref() - .map(|u| u.to_owned().naive_local()), + locked: page.comments_enabled.map(|e| !e), + published: Some(page.published.naive_local()), + updated: page.updated.map(|u| u.naive_local()), deleted: None, - nsfw: ext.sensitive, - stickied: ext.stickied, + nsfw: page.sensitive, + stickied: page.stickied, embed_title: iframely_title, embed_description: iframely_description, embed_html: iframely_html, thumbnail_url: pictrs_thumbnail.map(|u| u.into()), - ap_id: Some(ap_id), + ap_id: Some(page.id.clone().into()), local: Some(false), - }) + }; + Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??) } } diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 66bba9f43..815645721 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -20,7 +20,7 @@ pub enum PublicUrl { pub struct ActivityCommonFields { #[serde(rename = "@context")] pub context: OneOrMany, - id: Url, + pub id: Url, pub actor: Url, // unparsed fields From 57b6ecaf40b72ce07c9851a22399bfe606fd9bd8 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 29 Jul 2021 10:58:29 +0200 Subject: [PATCH 2/3] Simplify lemmy_context() function (dont return errors) --- .../apub/src/activities/community/announce.rs | 2 +- crates/apub/src/activities/post/create.rs | 2 +- crates/apub/src/activities/post/update.rs | 2 +- crates/apub/src/activities/send/comment.rs | 24 +-- crates/apub/src/activities/send/community.rs | 34 ++--- crates/apub/src/activities/send/person.rs | 6 +- crates/apub/src/activities/send/post.rs | 20 +-- .../src/activities/send/private_message.rs | 10 +- crates/apub/src/extensions/context.rs | 12 +- crates/apub/src/http/community.rs | 8 +- crates/apub/src/http/person.rs | 4 +- crates/apub/src/objects/comment.rs | 2 +- crates/apub/src/objects/community.rs | 2 +- crates/apub/src/objects/mod.rs | 19 +++ crates/apub/src/objects/person.rs | 2 +- crates/apub/src/objects/post.rs | 139 ++++++++---------- crates/apub/src/objects/private_message.rs | 2 +- 17 files changed, 145 insertions(+), 145 deletions(-) diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 0bdb8bc06..04721edac 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -86,7 +86,7 @@ impl AnnounceActivity { cc: vec![community.followers_url()], kind: AnnounceType::Announce, common: ActivityCommonFields { - context: lemmy_context()?.into(), + context: lemmy_context(), id: generate_activity_id(AnnounceType::Announce)?, actor: community.actor_id(), unparsed: Default::default(), diff --git a/crates/apub/src/activities/post/create.rs b/crates/apub/src/activities/post/create.rs index 909a6148d..270b712b3 100644 --- a/crates/apub/src/activities/post/create.rs +++ b/crates/apub/src/activities/post/create.rs @@ -55,7 +55,7 @@ impl CreatePost { cc: [community.actor_id()], r#type: Default::default(), common: ActivityCommonFields { - context: lemmy_context()?.into(), + context: lemmy_context(), id: id.clone(), actor: actor.actor_id(), unparsed: Default::default(), diff --git a/crates/apub/src/activities/post/update.rs b/crates/apub/src/activities/post/update.rs index a8cec9e73..c9fbb548f 100644 --- a/crates/apub/src/activities/post/update.rs +++ b/crates/apub/src/activities/post/update.rs @@ -48,7 +48,7 @@ impl UpdatePost { cc: [community.actor_id()], r#type: Default::default(), common: ActivityCommonFields { - context: lemmy_context()?.into(), + context: lemmy_context(), id: id.clone(), actor: actor.actor_id(), unparsed: Default::default(), diff --git a/crates/apub/src/activities/send/comment.rs b/crates/apub/src/activities/send/comment.rs index be24dd07a..47fc2a3e0 100644 --- a/crates/apub/src/activities/send/comment.rs +++ b/crates/apub/src/activities/send/comment.rs @@ -64,7 +64,7 @@ impl ApubObjectType for Comment { note.into_any_base()?, ); create - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(CreateType::Create)?) .set_to(public()) .set_many_ccs(maa.ccs.to_owned()) @@ -97,7 +97,7 @@ impl ApubObjectType for Comment { note.into_any_base()?, ); update - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UpdateType::Update)?) .set_to(public()) .set_many_ccs(maa.ccs.to_owned()) @@ -124,7 +124,7 @@ impl ApubObjectType for Comment { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -153,7 +153,7 @@ impl ApubObjectType for Comment { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -164,7 +164,7 @@ impl ApubObjectType for Comment { delete.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -188,7 +188,7 @@ impl ApubObjectType for Comment { self.ap_id.to_owned().into_inner(), ); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -217,7 +217,7 @@ impl ApubObjectType for Comment { self.ap_id.to_owned().into_inner(), ); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -228,7 +228,7 @@ impl ApubObjectType for Comment { remove.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -255,7 +255,7 @@ impl ApubLikeableType for Comment { self.ap_id.to_owned().into_inner(), ); like - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(LikeType::Like)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -279,7 +279,7 @@ impl ApubLikeableType for Comment { self.ap_id.to_owned().into_inner(), ); dislike - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DislikeType::Dislike)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -307,7 +307,7 @@ impl ApubLikeableType for Comment { self.ap_id.to_owned().into_inner(), ); like - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DislikeType::Dislike)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -318,7 +318,7 @@ impl ApubLikeableType for Comment { like.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index 96cb058a4..fbc61fc05 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -98,7 +98,7 @@ impl CommunityType for Community { follow.into_any_base()?, ); accept - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(AcceptType::Accept)?) .set_to(person.actor_id()); @@ -117,7 +117,7 @@ impl CommunityType for Community { self.to_apub(context.pool()).await?.into_any_base()?, ); update - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UpdateType::Update)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); @@ -134,7 +134,7 @@ impl CommunityType for Community { if self.local { let mut delete = Delete::new(self.actor_id(), self.actor_id()); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); @@ -145,7 +145,7 @@ impl CommunityType for Community { else { let mut delete = Delete::new(mod_.actor_id(), self.actor_id()); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); @@ -163,14 +163,14 @@ impl CommunityType for Community { if self.local { let mut delete = Delete::new(self.actor_id(), self.actor_id()); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); @@ -181,14 +181,14 @@ impl CommunityType for Community { else { let mut delete = Delete::new(mod_.actor_id(), self.actor_id()); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); let mut undo = Undo::new(mod_.actor_id(), delete.into_any_base()?); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); @@ -202,7 +202,7 @@ impl CommunityType for Community { async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> { let mut remove = Remove::new(self.actor_id(), self.actor_id()); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); @@ -215,7 +215,7 @@ impl CommunityType for Community { async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> { let mut remove = Remove::new(self.actor_id(), self.actor_id()); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); @@ -223,7 +223,7 @@ impl CommunityType for Community { // Undo that fake activity let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(LikeType::Like)?) .set_to(public()) .set_many_ccs(vec![self.followers_url()]); @@ -267,7 +267,7 @@ impl CommunityType for Community { } let mut announce = Announce::new(self.actor_id(), activity); announce - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(AnnounceType::Announce)?) .set_to(public()) .set_many_ccs(ccs); @@ -306,7 +306,7 @@ impl CommunityType for Community { ) -> Result<(), LemmyError> { let mut add = Add::new(actor.actor_id(), added_mod.actor_id()); add - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(AddType::Add)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]) @@ -324,7 +324,7 @@ impl CommunityType for Community { ) -> Result<(), LemmyError> { let mut remove = Remove::new(actor.actor_id(), removed_mod.actor_id()); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]) @@ -342,7 +342,7 @@ impl CommunityType for Community { ) -> Result<(), LemmyError> { let mut block = Block::new(actor.actor_id(), blocked_user.actor_id()); block - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(BlockType::Block)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); @@ -359,7 +359,7 @@ impl CommunityType for Community { ) -> Result<(), LemmyError> { let mut block = Block::new(actor.actor_id(), unblocked_user.actor_id()); block - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(BlockType::Block)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); @@ -367,7 +367,7 @@ impl CommunityType for Community { // Undo that fake activity let mut undo = Undo::new(actor.actor_id(), block.into_any_base()?); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![self.actor_id()]); diff --git a/crates/apub/src/activities/send/person.rs b/crates/apub/src/activities/send/person.rs index a5e792512..b7b43c433 100644 --- a/crates/apub/src/activities/send/person.rs +++ b/crates/apub/src/activities/send/person.rs @@ -78,7 +78,7 @@ impl UserType for Person { let mut follow = Follow::new(self.actor_id(), community.actor_id()); follow - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(FollowType::Follow)?) .set_to(community.actor_id()); @@ -99,7 +99,7 @@ impl UserType for Person { let mut follow = Follow::new(self.actor_id(), community.actor_id()); follow - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(FollowType::Follow)?) .set_to(community.actor_id()); @@ -109,7 +109,7 @@ impl UserType for Person { follow.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(community.actor_id()); diff --git a/crates/apub/src/activities/send/post.rs b/crates/apub/src/activities/send/post.rs index db96621b0..677e7845b 100644 --- a/crates/apub/src/activities/send/post.rs +++ b/crates/apub/src/activities/send/post.rs @@ -54,7 +54,7 @@ impl ApubObjectType for Post { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -79,7 +79,7 @@ impl ApubObjectType for Post { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -90,7 +90,7 @@ impl ApubObjectType for Post { delete.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -111,7 +111,7 @@ impl ApubObjectType for Post { self.ap_id.to_owned().into_inner(), ); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -136,7 +136,7 @@ impl ApubObjectType for Post { self.ap_id.to_owned().into_inner(), ); remove - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(RemoveType::Remove)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -147,7 +147,7 @@ impl ApubObjectType for Post { remove.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -171,7 +171,7 @@ impl ApubLikeableType for Post { self.ap_id.to_owned().into_inner(), ); like - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(LikeType::Like)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -192,7 +192,7 @@ impl ApubLikeableType for Post { self.ap_id.to_owned().into_inner(), ); dislike - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DislikeType::Dislike)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -217,7 +217,7 @@ impl ApubLikeableType for Post { self.ap_id.to_owned().into_inner(), ); like - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(LikeType::Like)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); @@ -228,7 +228,7 @@ impl ApubLikeableType for Post { like.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(public()) .set_many_ccs(vec![community.actor_id()]); diff --git a/crates/apub/src/activities/send/private_message.rs b/crates/apub/src/activities/send/private_message.rs index d208b25d4..67d0afcd3 100644 --- a/crates/apub/src/activities/send/private_message.rs +++ b/crates/apub/src/activities/send/private_message.rs @@ -38,7 +38,7 @@ impl ApubObjectType for PrivateMessage { ); create - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(CreateType::Create)?) .set_to(recipient.actor_id()); @@ -59,7 +59,7 @@ impl ApubObjectType for PrivateMessage { note.into_any_base()?, ); update - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UpdateType::Update)?) .set_to(recipient.actor_id()); @@ -77,7 +77,7 @@ impl ApubObjectType for PrivateMessage { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(recipient.actor_id()); @@ -99,7 +99,7 @@ impl ApubObjectType for PrivateMessage { self.ap_id.to_owned().into_inner(), ); delete - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(DeleteType::Delete)?) .set_to(recipient.actor_id()); @@ -109,7 +109,7 @@ impl ApubObjectType for PrivateMessage { delete.into_any_base()?, ); undo - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(generate_activity_id(UndoType::Undo)?) .set_to(recipient.actor_id()); diff --git a/crates/apub/src/extensions/context.rs b/crates/apub/src/extensions/context.rs index 08491dcbf..1d6f83bd7 100644 --- a/crates/apub/src/extensions/context.rs +++ b/crates/apub/src/extensions/context.rs @@ -1,9 +1,8 @@ -use activitystreams::{base::AnyBase, context}; -use lemmy_utils::LemmyError; +use activitystreams::{base::AnyBase, context, primitives::OneOrMany}; use serde_json::json; use url::Url; -pub fn lemmy_context() -> Result, LemmyError> { +pub fn lemmy_context() -> OneOrMany { let context_ext = AnyBase::from_arbitrary_json(json!( { "sc": "http://schema.org#", @@ -19,10 +18,11 @@ pub fn lemmy_context() -> Result, LemmyError> { "type": "sc:Text", "id": "as:alsoKnownAs" }, - }))?; - Ok(vec![ + })) + .expect("parse context"); + OneOrMany::from(vec![ AnyBase::from(context()), context_ext, - AnyBase::from(Url::parse("https://w3id.org/security/v1")?), + AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")), ]) } diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 3173bca12..6b264fad6 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -81,7 +81,7 @@ pub(crate) async fn get_apub_community_followers( let mut collection = UnorderedCollection::new(); collection - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(community.followers_url.into()) .set_total_items(community_followers.len() as u64); Ok(create_apub_response(&collection)) @@ -112,7 +112,7 @@ pub(crate) async fn get_apub_community_outbox( let mut collection = OrderedCollection::new(); collection .set_many_items(activities) - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(community.get_outbox_url()?) .set_total_items(len as u64); Ok(create_apub_response(&collection)) @@ -130,7 +130,7 @@ pub(crate) async fn get_apub_community_inbox( let mut collection = OrderedCollection::new(); collection .set_id(community.inbox_url.into()) - .set_many_contexts(lemmy_context()?); + .set_many_contexts(lemmy_context()); Ok(create_apub_response(&collection)) } @@ -162,6 +162,6 @@ pub(crate) async fn get_apub_community_moderators( .set_id(generate_moderators_url(&community.actor_id)?.into()) .set_total_items(moderators.len() as u64) .set_many_items(moderators) - .set_many_contexts(lemmy_context()?); + .set_many_contexts(lemmy_context()); Ok(create_apub_response(&collection)) } diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index 2c96aafd8..42f25bb62 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -71,7 +71,7 @@ pub(crate) async fn get_apub_person_outbox( let mut collection = OrderedCollection::new(); collection .set_many_items(Vec::::new()) - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(person.get_outbox_url()?) .set_total_items(0_u64); Ok(create_apub_response(&collection)) @@ -89,6 +89,6 @@ pub(crate) async fn get_apub_person_inbox( let mut collection = OrderedCollection::new(); collection .set_id(person.inbox_url.into()) - .set_many_contexts(lemmy_context()?); + .set_many_contexts(lemmy_context()); Ok(create_apub_response(&collection)) } diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 7b181eff4..8501ea2a7 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -69,7 +69,7 @@ impl ToApub for Comment { comment // Not needed when the Post is embedded in a collection (like for community outbox) - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(self.ap_id.to_owned().into_inner()) .set_published(convert_datetime(self.published)) // NOTE: included community id for compatibility with lemmy v0.9.9 diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 34a15793b..f9948588d 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -55,7 +55,7 @@ impl ToApub for Community { let mut group = ApObject::new(Group::new()); group - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(self.actor_id.to_owned().into()) .set_name(self.title.to_owned()) .set_published(convert_datetime(self.published)) diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index c87fc043c..6a52673e6 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -70,6 +70,25 @@ pub trait FromApubToForm { Self: Sized; } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum MediaTypeMarkdown { + #[serde(rename = "text/markdown")] + Markdown, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum MediaTypeHtml { + #[serde(rename = "text/html")] + Markdown, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Source { + content: String, + media_type: MediaTypeMarkdown, +} + /// Updated is actually the deletion time fn create_tombstone( deleted: bool, diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index a508f4e08..29935ec07 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -50,7 +50,7 @@ impl ToApub for DbPerson { let mut person = ApObject::new(actor); person - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(self.actor_id.to_owned().into_inner()) .set_published(convert_datetime(self.published)); diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index ace6b9131..7c79ff88b 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -2,7 +2,7 @@ use crate::{ activities::extract_community, extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, - objects::{create_tombstone, FromApub, ToApub}, + objects::{create_tombstone, FromApub, MediaTypeHtml, MediaTypeMarkdown, Source, ToApub}, }; use activitystreams::{ base::AnyBase, @@ -34,84 +34,6 @@ use lemmy_utils::{ use lemmy_websocket::LemmyContext; use url::Url; -#[async_trait::async_trait(?Send)] -impl ToApub for Post { - type ApubType = Page; - - // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. - async fn to_apub(&self, pool: &DbPool) -> Result { - let creator_id = self.creator_id; - let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; - let community_id = self.community_id; - let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; - - let source = self.body.clone().map(|body| Source { - content: body, - media_type: MediaTypeMarkdown::Markdown, - }); - let image = self.thumbnail_url.clone().map(|thumb| ImageObject { - content: ImageType::Image, - url: thumb.into(), - }); - - let page = Page { - context: lemmy_context()?.into(), - r#type: PageType::Page, - id: self.ap_id.clone().into(), - attributed_to: creator.actor_id.into(), - to: [community.actor_id.into(), public()], - name: self.name.clone(), - content: self.body.as_ref().map(|b| markdown_to_html(b)), - media_type: MediaTypeHtml::Markdown, - source, - url: self.url.clone().map(|u| u.into()), - image, - comments_enabled: Some(!self.locked), - sensitive: Some(self.nsfw), - stickied: Some(self.stickied), - published: convert_datetime(self.published), - updated: self.updated.map(convert_datetime), - unparsed: Default::default(), - }; - Ok(page) - } - - fn to_tombstone(&self) -> Result { - create_tombstone( - self.deleted, - self.ap_id.to_owned().into(), - self.updated, - PageType::Page, - ) - } -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum MediaTypeMarkdown { - #[serde(rename = "text/markdown")] - Markdown, -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum MediaTypeHtml { - #[serde(rename = "text/html")] - Markdown, -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Source { - content: String, - media_type: MediaTypeMarkdown, -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ImageObject { - content: ImageType, - url: Url, -} - #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Page { @@ -138,6 +60,13 @@ pub struct Page { unparsed: Unparsed, } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ImageObject { + content: ImageType, + url: Url, +} + impl Page { /// Only mods can change the post's stickied/locked status. So if either of these is changed from /// the current value, it is a mod action and needs to be verified as such. @@ -169,6 +98,58 @@ impl Page { } } +#[async_trait::async_trait(?Send)] +impl ToApub for Post { + type ApubType = Page; + + // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. + async fn to_apub(&self, pool: &DbPool) -> Result { + let creator_id = self.creator_id; + let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; + let community_id = self.community_id; + let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; + + let source = self.body.clone().map(|body| Source { + content: body, + media_type: MediaTypeMarkdown::Markdown, + }); + let image = self.thumbnail_url.clone().map(|thumb| ImageObject { + content: ImageType::Image, + url: thumb.into(), + }); + + let page = Page { + context: lemmy_context(), + r#type: PageType::Page, + id: self.ap_id.clone().into(), + attributed_to: creator.actor_id.into(), + to: [community.actor_id.into(), public()], + name: self.name.clone(), + content: self.body.as_ref().map(|b| markdown_to_html(b)), + media_type: MediaTypeHtml::Markdown, + source, + url: self.url.clone().map(|u| u.into()), + image, + comments_enabled: Some(!self.locked), + sensitive: Some(self.nsfw), + stickied: Some(self.stickied), + published: convert_datetime(self.published), + updated: self.updated.map(convert_datetime), + unparsed: Default::default(), + }; + Ok(page) + } + + fn to_tombstone(&self) -> Result { + create_tombstone( + self.deleted, + self.ap_id.to_owned().into(), + self.updated, + PageType::Page, + ) + } +} + #[async_trait::async_trait(?Send)] impl FromApub for Post { type ApubType = Page; diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 6bb4820f4..624123ecf 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -42,7 +42,7 @@ impl ToApub for PrivateMessage { let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??; private_message - .set_many_contexts(lemmy_context()?) + .set_many_contexts(lemmy_context()) .set_id(self.ap_id.to_owned().into_inner()) .set_published(convert_datetime(self.published)) .set_to(recipient.actor_id.into_inner()) From 433ab1e78b9da5c8f75cc9734dd5ce715cf0cc8b Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 30 Jul 2021 16:35:32 +0200 Subject: [PATCH 3/3] Add docs for MediaType, PublicUrl values --- Cargo.lock | 1 + crates/api_crud/src/post/create.rs | 14 ++--- crates/apub/src/activities/comment/create.rs | 7 ++- crates/apub/src/activities/comment/remove.rs | 14 ++++- .../src/activities/comment/undo_remove.rs | 18 ++++-- crates/apub/src/activities/comment/update.rs | 7 ++- .../apub/src/activities/community/add_mod.rs | 2 +- .../apub/src/activities/community/announce.rs | 2 +- .../src/activities/community/block_user.rs | 2 +- .../activities/community/undo_block_user.rs | 2 +- .../apub/src/activities/community/update.rs | 2 +- crates/apub/src/activities/deletion/delete.rs | 2 +- .../src/activities/deletion/undo_delete.rs | 2 +- crates/apub/src/activities/post/create.rs | 2 +- crates/apub/src/activities/post/update.rs | 2 +- crates/apub/src/activities/removal/remove.rs | 2 +- .../src/activities/removal/undo_remove.rs | 2 +- crates/apub/src/activities/voting/dislike.rs | 2 +- crates/apub/src/activities/voting/like.rs | 2 +- .../src/activities/voting/undo_dislike.rs | 2 +- .../apub/src/activities/voting/undo_like.rs | 2 +- crates/apub/src/objects/mod.rs | 13 +--- crates/apub/src/objects/post.rs | 9 ++- crates/apub_lib/Cargo.toml | 1 + crates/apub_lib/src/lib.rs | 8 +-- crates/apub_lib/src/values/mod.rs | 61 +++++++++++++++++++ 26 files changed, 130 insertions(+), 53 deletions(-) create mode 100644 crates/apub_lib/src/values/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 53e3e0071..ffa6f79c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1760,6 +1760,7 @@ dependencies = [ "lemmy_utils", "lemmy_websocket", "serde", + "serde_json", "url", ] diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 89f71d939..b2034a367 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -7,7 +7,12 @@ use lemmy_api_common::{ mark_post_as_read, post::*, }; -use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, EndpointType}; +use lemmy_apub::{ + activities::post::create::CreatePost as CreateApubPost, + generate_apub_endpoint, + ApubLikeableType, + EndpointType, +}; use lemmy_db_queries::{source::post::Post_, Crud, Likeable}; use lemmy_db_schema::source::post::*; use lemmy_db_views::post_view::PostView; @@ -82,12 +87,7 @@ impl PerformCrud for CreatePost { .await? .map_err(|_| ApiError::err("couldnt_create_post"))?; - lemmy_apub::activities::post::create::CreatePost::send( - &updated_post, - &local_user_view.person, - context, - ) - .await?; + CreateApubPost::send(&updated_post, &local_user_view.person, context).await?; // They like their own post by default let person_id = local_user_view.person.id; diff --git a/crates/apub/src/activities/comment/create.rs b/crates/apub/src/activities/comment/create.rs index b38553359..0e9472194 100644 --- a/crates/apub/src/activities/comment/create.rs +++ b/crates/apub/src/activities/comment/create.rs @@ -10,7 +10,12 @@ use crate::{ NoteExt, }; use activitystreams::{activity::kind::CreateType, base::BaseExt}; -use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{ + values::PublicUrl, + verify_domains_match_opt, + ActivityCommonFields, + ActivityHandler, +}; use lemmy_db_schema::source::comment::Comment; use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; diff --git a/crates/apub/src/activities/comment/remove.rs b/crates/apub/src/activities/comment/remove.rs index d60e3f804..5702f9fa7 100644 --- a/crates/apub/src/activities/comment/remove.rs +++ b/crates/apub/src/activities/comment/remove.rs @@ -1,8 +1,16 @@ -use crate::activities::{comment::send_websocket_message, verify_mod_action}; +use crate::{ + activities::{comment::send_websocket_message, verify_mod_action}, + check_is_apub_id_valid, + fetcher::objects::get_or_fetch_and_insert_comment, +}; use activitystreams::activity::kind::RemoveType; use lemmy_api_common::blocking; -use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment}; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl}; +use lemmy_apub_lib::{ + values::PublicUrl, + verify_domains_match, + ActivityCommonFields, + ActivityHandlerNew, +}; use lemmy_db_queries::source::comment::Comment_; use lemmy_db_schema::source::comment::Comment; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/comment/undo_remove.rs b/crates/apub/src/activities/comment/undo_remove.rs index f3ebdf385..07aa67119 100644 --- a/crates/apub/src/activities/comment/undo_remove.rs +++ b/crates/apub/src/activities/comment/undo_remove.rs @@ -1,11 +1,19 @@ -use crate::activities::{ - comment::{remove::RemoveComment, send_websocket_message}, - verify_mod_action, +use crate::{ + activities::{ + comment::{remove::RemoveComment, send_websocket_message}, + verify_mod_action, + }, + check_is_apub_id_valid, + fetcher::objects::get_or_fetch_and_insert_comment, }; use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; -use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment}; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl}; +use lemmy_apub_lib::{ + values::PublicUrl, + verify_domains_match, + ActivityCommonFields, + ActivityHandlerNew, +}; use lemmy_db_queries::source::comment::Comment_; use lemmy_db_schema::source::comment::Comment; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/comment/update.rs b/crates/apub/src/activities/comment/update.rs index 5e785229f..c0d014813 100644 --- a/crates/apub/src/activities/comment/update.rs +++ b/crates/apub/src/activities/comment/update.rs @@ -10,7 +10,12 @@ use crate::{ NoteExt, }; use activitystreams::{activity::kind::UpdateType, base::BaseExt}; -use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{ + values::PublicUrl, + verify_domains_match_opt, + ActivityCommonFields, + ActivityHandler, +}; use lemmy_db_schema::source::comment::Comment; use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index fd22b978a..0dcd9818b 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -10,7 +10,7 @@ use crate::{ }; use activitystreams::{activity::kind::AddType, base::AnyBase}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::{source::community::CommunityModerator_, Joinable}; use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 04721edac..bc72d80fe 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -34,7 +34,7 @@ use crate::{ CommunityType, }; use activitystreams::activity::kind::AnnounceType; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index 1c5f0eaf5..34909b2e6 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -4,7 +4,7 @@ use crate::{ }; use activitystreams::activity::kind::BlockType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::{Bannable, Followable}; use lemmy_db_schema::source::community::{ CommunityFollower, diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index d44fe2669..53b665db1 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -9,7 +9,7 @@ use crate::{ }; use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::Bannable; use lemmy_db_schema::source::community::{CommunityPersonBan, CommunityPersonBanForm}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index ce1854969..fd0bf2d45 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -10,7 +10,7 @@ use crate::{ }; use activitystreams::activity::kind::UpdateType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::{ApubObject, Crud}; use lemmy_db_schema::source::community::{Community, CommunityForm}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index f7e7fe5c2..cabfcce86 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -18,7 +18,7 @@ use crate::{ }; use activitystreams::activity::kind::DeleteType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::{ source::{comment::Comment_, community::Community_, post::Post_}, Crud, diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 94e44d7ac..ea70e5f5e 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -18,7 +18,7 @@ use crate::{ }; use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_}; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/post/create.rs b/crates/apub/src/activities/post/create.rs index 270b712b3..3dd8ce554 100644 --- a/crates/apub/src/activities/post/create.rs +++ b/crates/apub/src/activities/post/create.rs @@ -17,11 +17,11 @@ use activitystreams::activity::kind::CreateType; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + values::PublicUrl, verify_domains_match, verify_urls_match, ActivityCommonFields, ActivityHandler, - PublicUrl, }; use lemmy_db_queries::Crud; use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; diff --git a/crates/apub/src/activities/post/update.rs b/crates/apub/src/activities/post/update.rs index c9fbb548f..f5cd07b9f 100644 --- a/crates/apub/src/activities/post/update.rs +++ b/crates/apub/src/activities/post/update.rs @@ -15,7 +15,7 @@ use crate::{ }; use activitystreams::activity::kind::UpdateType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::Crud; use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/removal/remove.rs b/crates/apub/src/activities/removal/remove.rs index 7e62ab7bd..053ddadf8 100644 --- a/crates/apub/src/activities/removal/remove.rs +++ b/crates/apub/src/activities/removal/remove.rs @@ -19,7 +19,7 @@ use crate::{ use activitystreams::{activity::kind::RemoveType, base::AnyBase}; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::{ source::{comment::Comment_, community::Community_, post::Post_}, Joinable, diff --git a/crates/apub/src/activities/removal/undo_remove.rs b/crates/apub/src/activities/removal/undo_remove.rs index ca77a31da..db4518b27 100644 --- a/crates/apub/src/activities/removal/undo_remove.rs +++ b/crates/apub/src/activities/removal/undo_remove.rs @@ -17,7 +17,7 @@ use crate::{ use activitystreams::activity::kind::UndoType; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_}; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; use lemmy_utils::LemmyError; diff --git a/crates/apub/src/activities/voting/dislike.rs b/crates/apub/src/activities/voting/dislike.rs index b34b2d10b..54b673008 100644 --- a/crates/apub/src/activities/voting/dislike.rs +++ b/crates/apub/src/activities/voting/dislike.rs @@ -4,7 +4,7 @@ use crate::activities::{ voting::receive_like_or_dislike, }; use activitystreams::activity::kind::DislikeType; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; diff --git a/crates/apub/src/activities/voting/like.rs b/crates/apub/src/activities/voting/like.rs index 50a4d44d4..90f29c427 100644 --- a/crates/apub/src/activities/voting/like.rs +++ b/crates/apub/src/activities/voting/like.rs @@ -4,7 +4,7 @@ use crate::activities::{ voting::receive_like_or_dislike, }; use activitystreams::activity::kind::LikeType; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; diff --git a/crates/apub/src/activities/voting/undo_dislike.rs b/crates/apub/src/activities/voting/undo_dislike.rs index 5ba3b47f9..13e3e1f76 100644 --- a/crates/apub/src/activities/voting/undo_dislike.rs +++ b/crates/apub/src/activities/voting/undo_dislike.rs @@ -4,7 +4,7 @@ use crate::activities::{ voting::{dislike::DislikePostOrComment, receive_undo_like_or_dislike}, }; use activitystreams::activity::kind::UndoType; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; diff --git a/crates/apub/src/activities/voting/undo_like.rs b/crates/apub/src/activities/voting/undo_like.rs index 2de03f4ba..ab15da70b 100644 --- a/crates/apub/src/activities/voting/undo_like.rs +++ b/crates/apub/src/activities/voting/undo_like.rs @@ -4,7 +4,7 @@ use crate::activities::{ voting::{like::LikePostOrComment, receive_undo_like_or_dislike}, }; use activitystreams::activity::kind::UndoType; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index 6a52673e6..7191a4a1b 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -12,6 +12,7 @@ use activitystreams::{ use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; use lemmy_api_common::blocking; +use lemmy_apub_lib::values::MediaTypeMarkdown; use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_schema::{CommunityId, DbUrl}; use lemmy_utils::{ @@ -70,18 +71,6 @@ pub trait FromApubToForm { Self: Sized; } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum MediaTypeMarkdown { - #[serde(rename = "text/markdown")] - Markdown, -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum MediaTypeHtml { - #[serde(rename = "text/html")] - Markdown, -} - #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Source { diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 7c79ff88b..a7a6cfe93 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -2,7 +2,7 @@ use crate::{ activities::extract_community, extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, - objects::{create_tombstone, FromApub, MediaTypeHtml, MediaTypeMarkdown, Source, ToApub}, + objects::{create_tombstone, FromApub, Source, ToApub}, }; use activitystreams::{ base::AnyBase, @@ -16,7 +16,10 @@ use activitystreams::{ }; use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; -use lemmy_apub_lib::verify_domains_match; +use lemmy_apub_lib::{ + values::{MediaTypeHtml, MediaTypeMarkdown}, + verify_domains_match, +}; use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_schema::{ self, @@ -126,7 +129,7 @@ impl ToApub for Post { to: [community.actor_id.into(), public()], name: self.name.clone(), content: self.body.as_ref().map(|b| markdown_to_html(b)), - media_type: MediaTypeHtml::Markdown, + media_type: MediaTypeHtml::Html, source, url: self.url.clone().map(|u| u.into()), image, diff --git a/crates/apub_lib/Cargo.toml b/crates/apub_lib/Cargo.toml index 327670b50..6f8c84170 100644 --- a/crates/apub_lib/Cargo.toml +++ b/crates/apub_lib/Cargo.toml @@ -12,3 +12,4 @@ activitystreams-ext = "0.1.0-alpha.2" serde = { version = "1.0.123", features = ["derive"] } async-trait = "0.1.42" url = { version = "2.2.1", features = ["serde"] } +serde_json = { version = "1.0.64", features = ["preserve_order"] } diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 815645721..13fc4025d 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -1,3 +1,5 @@ +pub mod values; + use activitystreams::{ base::AnyBase, error::DomainError, @@ -9,12 +11,6 @@ use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] -pub enum PublicUrl { - #[serde(rename = "https://www.w3.org/ns/activitystreams#Public")] - Public, -} - #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ActivityCommonFields { diff --git a/crates/apub_lib/src/values/mod.rs b/crates/apub_lib/src/values/mod.rs new file mode 100644 index 000000000..2c9f65ffe --- /dev/null +++ b/crates/apub_lib/src/values/mod.rs @@ -0,0 +1,61 @@ +//! The enums here serve to limit a json string value to a single, hardcoded value which can be +//! verified at compilation time. When using it as the type of a struct field, the struct can only +//! be constructed or deserialized if the field has the exact same value. +//! +//! If we used String as the field type, any value would be accepted, and we would have to check +//! manually at runtime that it contains the expected value. +//! +//! The enums in [`activitystreams::activity::kind`] work in the same way, and can be used to +//! distinguish different activity types. +//! +//! In the example below, `MyObject` can only be constructed or +//! deserialized if `media_type` is `text/markdown`, but not if it is `text/html`. +//! +//! ``` +//! use lemmy_apub_lib::values::MediaTypeMarkdown; +//! use serde_json::from_str; +//! use serde::{Deserialize, Serialize}; +//! +//! #[derive(Deserialize, Serialize)] +//! struct MyObject { +//! content: String, +//! media_type: MediaTypeMarkdown, +//! } +//! +//! let markdown_json = r#"{"content": "**test**", "media_type": "text/markdown"}"#; +//! let from_markdown = from_str::(markdown_json); +//! assert!(from_markdown.is_ok()); +//! +//! let markdown_html = r#"{"content": "test", "media_type": "text/html"}"#; +//! let from_html = from_str::(markdown_html); +//! assert!(from_html.is_err()); +//! ``` + +use serde::{Deserialize, Serialize}; + +/// The identifier used to address activities to the public. +/// +/// +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum PublicUrl { + #[serde(rename = "https://www.w3.org/ns/activitystreams#Public")] + Public, +} + +/// Media type for markdown text. +/// +/// +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MediaTypeMarkdown { + #[serde(rename = "text/markdown")] + Markdown, +} + +/// Media type for HTML text/ +/// +/// +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MediaTypeHtml { + #[serde(rename = "text/html")] + Html, +}