diff --git a/Cargo.lock b/Cargo.lock index d41277520..8fb762939 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1869,6 +1869,7 @@ dependencies = [ "async-trait", "background-jobs", "base64 0.13.0", + "diesel", "http", "http-signature-normalization-actix", "http-signature-normalization-reqwest", @@ -1904,6 +1905,7 @@ dependencies = [ "diesel-derive-newtype", "diesel_migrations", "lazy_static", + "lemmy_apub_lib", "lemmy_utils", "log", "regex", diff --git a/crates/api/src/comment_report.rs b/crates/api/src/comment_report.rs index 82ecc1f44..f1bf7023c 100644 --- a/crates/api/src/comment_report.rs +++ b/crates/api/src/comment_report.rs @@ -1,5 +1,5 @@ +use crate::Perform; use actix_web::web::Data; - use lemmy_api_common::{ blocking, check_community_ban, @@ -7,7 +7,8 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, is_mod_or_admin, }; -use lemmy_apub::{fetcher::object_id::ObjectId, protocol::activities::community::report::Report}; +use lemmy_apub::protocol::activities::community::report::Report; +use lemmy_apub_lib::object_id::ObjectId; use lemmy_db_schema::{source::comment_report::*, traits::Reportable}; use lemmy_db_views::{ comment_report_view::{CommentReportQueryBuilder, CommentReportView}, @@ -16,8 +17,6 @@ use lemmy_db_views::{ use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; -use crate::Perform; - /// Creates a comment report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreateCommentReport { diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs index 3564f1352..f22104eb7 100644 --- a/crates/api/src/post.rs +++ b/crates/api/src/post.rs @@ -73,7 +73,7 @@ impl Perform for CreatePostLike { .await??; let community_id = post.community_id; - let object = PostOrComment::Post(Box::new(post)); + let object = PostOrComment::Post(post); // Only add the like if the score isnt 0 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); @@ -169,7 +169,7 @@ impl Perform for LockPost { // apub updates CreateOrUpdatePost::send( - &updated_post, + updated_post, &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, @@ -242,7 +242,7 @@ impl Perform for StickyPost { // Apub updates // TODO stickied should pry work like locked for ease of use CreateOrUpdatePost::send( - &updated_post, + updated_post, &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, diff --git a/crates/api/src/post_report.rs b/crates/api/src/post_report.rs index 98b2f1c11..0e47f592d 100644 --- a/crates/api/src/post_report.rs +++ b/crates/api/src/post_report.rs @@ -1,5 +1,5 @@ +use crate::Perform; use actix_web::web::Data; - use lemmy_api_common::{ blocking, check_community_ban, @@ -13,7 +13,8 @@ use lemmy_api_common::{ ResolvePostReport, }, }; -use lemmy_apub::{fetcher::object_id::ObjectId, protocol::activities::community::report::Report}; +use lemmy_apub::protocol::activities::community::report::Report; +use lemmy_apub_lib::object_id::ObjectId; use lemmy_db_schema::{ source::post_report::{PostReport, PostReportForm}, traits::Reportable, @@ -25,8 +26,6 @@ use lemmy_db_views::{ use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; -use crate::Perform; - /// Creates a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreatePostReport { diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index ab093ea19..4c7bb34f0 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -1,5 +1,5 @@ +use crate::PerformCrud; use actix_web::web::Data; - use lemmy_api_common::{ blocking, check_community_ban, @@ -13,6 +13,7 @@ use lemmy_api_common::{ use lemmy_apub::{ fetcher::post_or_comment::PostOrComment, generate_local_apub_endpoint, + objects::comment::ApubComment, protocol::activities::{ create_or_update::comment::CreateOrUpdateComment, voting::vote::{Vote, VoteType}, @@ -40,8 +41,6 @@ use lemmy_websocket::{ UserOperationCrud, }; -use crate::PerformCrud; - #[async_trait::async_trait(?Send)] impl PerformCrud for CreateComment { type Response = CommentResponse; @@ -121,14 +120,6 @@ impl PerformCrud for CreateComment { .await? .map_err(|e| ApiError::err("couldnt_create_comment", e))?; - CreateOrUpdateComment::send( - &updated_comment.clone().into(), - &local_user_view.person.clone().into(), - CreateOrUpdateType::Create, - context, - ) - .await?; - // Scan the comment for user mentions, add those rows let post_id = post.id; let mentions = scrape_text_for_mentions(&comment_form.content); @@ -155,7 +146,15 @@ impl PerformCrud for CreateComment { .await? .map_err(|e| ApiError::err("couldnt_like_comment", e))?; - let object = PostOrComment::Comment(updated_comment.into()); + let apub_comment: ApubComment = updated_comment.into(); + CreateOrUpdateComment::send( + apub_comment.clone(), + &local_user_view.person.clone().into(), + CreateOrUpdateType::Create, + context, + ) + .await?; + let object = PostOrComment::Comment(apub_comment); Vote::send( &object, &local_user_view.person.clone().into(), diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 70f15dfb2..4802b1d50 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -72,15 +72,6 @@ impl PerformCrud for EditComment { .await? .map_err(|e| ApiError::err("couldnt_update_comment", e))?; - // Send the apub update - CreateOrUpdateComment::send( - &updated_comment.clone().into(), - &local_user_view.person.clone().into(), - CreateOrUpdateType::Update, - context, - ) - .await?; - // Do the mentions / recipients let updated_comment_content = updated_comment.content.to_owned(); let mentions = scrape_text_for_mentions(&updated_comment_content); @@ -94,6 +85,15 @@ impl PerformCrud for EditComment { ) .await?; + // Send the apub update + CreateOrUpdateComment::send( + updated_comment.into(), + &local_user_view.person.into(), + CreateOrUpdateType::Update, + context, + ) + .await?; + send_comment_ws_message( data.comment_id, UserOperationCrud::EditComment, diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 523ccf00d..b91cab433 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -7,7 +7,6 @@ use lemmy_api_common::{ is_admin, }; use lemmy_apub::{ - fetcher::object_id::ObjectId, generate_followers_url, generate_inbox_url, generate_local_apub_endpoint, @@ -15,6 +14,7 @@ use lemmy_apub::{ objects::community::ApubCommunity, EndpointType, }; +use lemmy_apub_lib::object_id::ObjectId; use lemmy_db_schema::{ diesel_option_overwrite_to_url, source::{ diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs index feaf110fb..47c0058c9 100644 --- a/crates/api_crud/src/community/read.rs +++ b/crates/api_crud/src/community/read.rs @@ -1,12 +1,8 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt}; -use lemmy_apub::{ - fetcher::object_id::ObjectId, - get_actor_id_from_name, - objects::community::ApubCommunity, -}; -use lemmy_apub_lib::webfinger::WebfingerType; +use lemmy_apub::{get_actor_id_from_name, objects::community::ApubCommunity}; +use lemmy_apub_lib::{object_id::ObjectId, webfinger::WebfingerType}; use lemmy_db_schema::{ from_opt_str_to_opt_enum, traits::DeleteableOrRemoveable, diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 4764b0254..d1fd89818 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -72,7 +72,7 @@ impl PerformCrud for EditCommunity { .map_err(|e| ApiError::err("couldnt_update_community", e))?; UpdateCommunity::send( - &updated_community.into(), + updated_community.into(), &local_user_view.person.into(), context, ) diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 99b67d2ea..1147c1a32 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -1,7 +1,5 @@ +use crate::PerformCrud; use actix_web::web::Data; -use log::warn; -use webmention::{Webmention, WebmentionError}; - use lemmy_api_common::{ blocking, check_community_ban, @@ -14,6 +12,7 @@ use lemmy_api_common::{ use lemmy_apub::{ fetcher::post_or_comment::PostOrComment, generate_local_apub_endpoint, + objects::post::ApubPost, protocol::activities::{ create_or_update::post::CreateOrUpdatePost, voting::vote::{Vote, VoteType}, @@ -33,8 +32,9 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; - -use crate::PerformCrud; +use log::warn; +use url::Url; +use webmention::{Webmention, WebmentionError}; #[async_trait::async_trait(?Send)] impl PerformCrud for CreatePost { @@ -110,14 +110,6 @@ impl PerformCrud for CreatePost { .await? .map_err(|e| ApiError::err("couldnt_create_post", e))?; - CreateOrUpdatePost::send( - &updated_post.clone().into(), - &local_user_view.person.clone().into(), - CreateOrUpdateType::Create, - context, - ) - .await?; - // They like their own post by default let person_id = local_user_view.person.id; let post_id = inserted_post.id; @@ -136,10 +128,8 @@ impl PerformCrud for CreatePost { mark_post_as_read(person_id, post_id, context.pool()).await?; if let Some(url) = &updated_post.url { - let mut webmention = Webmention::new( - updated_post.ap_id.clone().into_inner(), - url.clone().into_inner(), - )?; + let mut webmention = + Webmention::new::(updated_post.ap_id.clone().into(), url.clone().into())?; webmention.set_checked(true); match webmention.send().await { Ok(_) => {} @@ -148,7 +138,15 @@ impl PerformCrud for CreatePost { } } - let object = PostOrComment::Post(Box::new(updated_post.into())); + let apub_post: ApubPost = updated_post.into(); + CreateOrUpdatePost::send( + apub_post.clone(), + &local_user_view.person.clone().into(), + CreateOrUpdateType::Create, + context, + ) + .await?; + let object = PostOrComment::Post(apub_post); Vote::send( &object, &local_user_view.person.clone().into(), diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 0a982d688..2cfc67d41 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -109,7 +109,7 @@ impl PerformCrud for EditPost { // Send apub update CreateOrUpdatePost::send( - &updated_post.into(), + updated_post.into(), &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 705d781e2..0c6378269 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -83,7 +83,7 @@ impl PerformCrud for CreatePrivateMessage { .map_err(|e| ApiError::err("couldnt_create_private_message", e))?; CreateOrUpdatePrivateMessage::send( - &updated_private_message.into(), + updated_private_message.into(), &local_user_view.person.into(), CreateOrUpdateType::Create, context, diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 8114556c1..95cc5cb34 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -47,7 +47,7 @@ impl PerformCrud for EditPrivateMessage { // Send the apub update CreateOrUpdatePrivateMessage::send( - &updated_private_message.into(), + updated_private_message.into(), &local_user_view.person.into(), CreateOrUpdateType::Update, context, diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index b649c8fbb..41db9f99b 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -1,12 +1,8 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*}; -use lemmy_apub::{ - fetcher::object_id::ObjectId, - get_actor_id_from_name, - objects::person::ApubPerson, -}; -use lemmy_apub_lib::webfinger::WebfingerType; +use lemmy_apub::{get_actor_id_from_name, objects::person::ApubPerson}; +use lemmy_apub_lib::{object_id::ObjectId, webfinger::WebfingerType}; use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType}; use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder}; use lemmy_db_views_actor::{ diff --git a/crates/apub/assets/lemmy/context.json b/crates/apub/assets/lemmy/context.json new file mode 100644 index 000000000..88f3a6586 --- /dev/null +++ b/crates/apub/assets/lemmy/context.json @@ -0,0 +1,19 @@ +[ + "https://www.w3.org/ns/activitystreams", + { + "stickied": "as:stickied", + "pt": "https://join-lemmy.org#", + "sc": "http://schema.org#", + "matrixUserId": { + "type": "sc:Text", + "id": "as:alsoKnownAs" + }, + "sensitive": "as:sensitive", + "comments_enabled": { + "type": "sc:Boolean", + "id": "pt:commentsEnabled" + }, + "moderators": "as:moderators" + }, + "https://w3id.org/security/v1" +] \ No newline at end of file diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index a9c1a9ee8..7ac1e4e59 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -1,18 +1,3 @@ -use activitystreams::public; - -use lemmy_api_common::{blocking, check_post_deleted_or_removed}; -use lemmy_apub_lib::{ - data::Data, - traits::{ActivityHandler, ActorType, ApubObject}, - verify::verify_domains_match, -}; -use lemmy_db_schema::{ - source::{community::Community, post::Post}, - traits::Crud, -}; -use lemmy_utils::LemmyError; -use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud}; - use crate::{ activities::{ check_community_deleted_or_removed, @@ -24,14 +9,27 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}, protocol::activities::{create_or_update::comment::CreateOrUpdateComment, CreateOrUpdateType}, }; +use activitystreams::public; +use lemmy_api_common::{blocking, check_post_deleted_or_removed}; +use lemmy_apub_lib::{ + data::Data, + object_id::ObjectId, + traits::{ActivityHandler, ActorType, ApubObject}, + verify::verify_domains_match, +}; +use lemmy_db_schema::{ + source::{community::Community, post::Post}, + traits::Crud, +}; +use lemmy_utils::LemmyError; +use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud}; impl CreateOrUpdateComment { pub async fn send( - comment: &ApubComment, + comment: ApubComment, actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, @@ -50,12 +48,12 @@ impl CreateOrUpdateComment { kind.clone(), &context.settings().get_protocol_and_hostname(), )?; - let maa = collect_non_local_mentions(comment, &community, context).await?; + let maa = collect_non_local_mentions(&comment, &community, context).await?; let create_or_update = CreateOrUpdateComment { actor: ObjectId::new(actor.actor_id()), to: vec![public()], - object: comment.to_apub(context).await?, + object: comment.into_apub(context).await?, cc: maa.ccs, tag: maa.tags, kind, @@ -77,19 +75,17 @@ impl ActivityHandler for CreateOrUpdateComment { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; + verify_is_public(&self.to, &self.cc)?; let post = self.object.get_parents(context, request_counter).await?.0; let community = self.get_community(context, request_counter).await?; - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; - verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; + verify_domains_match(self.actor.inner(), self.object.id.inner())?; check_community_deleted_or_removed(&community)?; check_post_deleted_or_removed(&post)?; - // TODO: should add a check that the correct community is in cc (probably needs changes to - // comment deserialization) - self.object.verify(context, request_counter).await?; + ApubComment::verify(&self.object, self.actor.inner(), context, request_counter).await?; Ok(()) } @@ -98,8 +94,7 @@ impl ActivityHandler for CreateOrUpdateComment { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let comment = - ApubComment::from_apub(&self.object, context, self.actor.inner(), request_counter).await?; + let comment = ApubComment::from_apub(self.object, context, request_counter).await?; let recipients = get_notif_recipients(&self.actor, &comment, context, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreateComment, diff --git a/crates/apub/src/activities/comment/mod.rs b/crates/apub/src/activities/comment/mod.rs index 9eb2bb447..1a3f7d3a0 100644 --- a/crates/apub/src/activities/comment/mod.rs +++ b/crates/apub/src/activities/comment/mod.rs @@ -1,7 +1,4 @@ -use crate::{ - fetcher::object_id::ObjectId, - objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}, -}; +use crate::objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}; use activitystreams::{ base::BaseExt, link::{LinkExt, Mention}, @@ -9,7 +6,7 @@ use activitystreams::{ use anyhow::anyhow; use itertools::Itertools; use lemmy_api_common::blocking; -use lemmy_apub_lib::{traits::ActorType, webfinger::WebfingerResponse}; +use lemmy_apub_lib::{object_id::ObjectId, traits::ActorType, webfinger::WebfingerResponse}; use lemmy_db_schema::{ newtypes::LocalUserId, source::{comment::Comment, person::Person, post::Post}, diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index b18e50ada..f8026cce6 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -9,7 +9,6 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, generate_moderators_url, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::community::add_mod::AddMod, @@ -18,6 +17,7 @@ use activitystreams::{activity::kind::AddType, public}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -64,8 +64,8 @@ impl ActivityHandler for AddMod { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?; diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index f560b09f6..6830bd139 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -1,8 +1,7 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public}, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, - http::is_activity_already_known, + http::{is_activity_already_known, ActivityCommonFields}, insert_activity, objects::community::ApubCommunity, protocol::activities::community::announce::AnnounceActivity, @@ -10,7 +9,8 @@ use crate::{ use activitystreams::{activity::kind::AnnounceType, public}; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -36,7 +36,7 @@ impl AnnounceActivity { actor: ObjectId::new(community.actor_id()), to: vec![public()], object, - cc: vec![community.followers_url.clone().into_inner()], + cc: vec![community.followers_url.clone().into()], kind: AnnounceType::Announce, id: generate_activity_id( &AnnounceType::Announce, @@ -59,8 +59,8 @@ impl ActivityHandler for AnnounceActivity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -70,11 +70,15 @@ impl ActivityHandler for AnnounceActivity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - if is_activity_already_known(context.pool(), self.object.id_unchecked()).await? { + // TODO: this is pretty ugly, but i cant think of a much better way + let object = serde_json::to_string(&self.object)?; + let object_data: ActivityCommonFields = serde_json::from_str(&object)?; + + if is_activity_already_known(context.pool(), &object_data.id).await? { return Ok(()); } insert_activity( - self.object.id_unchecked(), + &object_data.id, self.object.clone(), false, true, diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index dfe6c4c92..4be0660f8 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -8,7 +8,6 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::community::block_user::BlockUserFromCommunity, }; @@ -16,6 +15,7 @@ use activitystreams::{activity::kind::BlockType, public}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -75,8 +75,8 @@ impl ActivityHandler for BlockUserFromCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?; diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index 96e692d58..b63c8b656 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -1,12 +1,11 @@ use crate::{ activities::send_lemmy_activity, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, insert_activity, objects::community::ApubCommunity, protocol::activities::community::announce::AnnounceActivity, }; -use lemmy_apub_lib::traits::ActorType; +use lemmy_apub_lib::{object_id::ObjectId, traits::ActorType}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs index 02ff3c064..a644c19d7 100644 --- a/crates/apub/src/activities/community/remove_mod.rs +++ b/crates/apub/src/activities/community/remove_mod.rs @@ -9,7 +9,6 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, generate_moderators_url, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::community::remove_mod::RemoveMod, @@ -18,6 +17,7 @@ use activitystreams::{activity::kind::RemoveType, public}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -63,8 +63,8 @@ impl ActivityHandler for RemoveMod { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?; diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index 1e7cc5fad..f0be3e0a0 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -1,8 +1,19 @@ +use crate::{ + activities::{ + generate_activity_id, + send_lemmy_activity, + verify_activity, + verify_person_in_community, + }, + objects::{community::ApubCommunity, person::ApubPerson}, + protocol::activities::community::report::Report, + PostOrComment, +}; use activitystreams::activity::kind::FlagType; - use lemmy_api_common::{blocking, comment::CommentReportResponse, post::PostReportResponse}; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -16,19 +27,6 @@ use lemmy_db_views::{comment_report_view::CommentReportView, post_report_view::P use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; -use crate::{ - activities::{ - generate_activity_id, - send_lemmy_activity, - verify_activity, - verify_person_in_community, - }, - fetcher::object_id::ObjectId, - objects::{community::ApubCommunity, person::ApubPerson}, - protocol::activities::community::report::Report, - PostOrComment, -}; - impl Report { pub async fn send( object_id: ObjectId, @@ -72,7 +70,7 @@ impl ActivityHandler for Report { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.to[0].dereference(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index 2bda94441..f309fe2b8 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -8,7 +8,6 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::community::{ block_user::BlockUserFromCommunity, @@ -19,6 +18,7 @@ use activitystreams::{activity::kind::UndoType, public}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -65,8 +65,8 @@ impl ActivityHandler for UndoBlockUserFromCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?; diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index 28de0db00..cc82c9e31 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -8,14 +8,14 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, - protocol::{activities::community::update::UpdateCommunity, objects::group::Group}, + protocol::activities::community::update::UpdateCommunity, }; use activitystreams::{activity::kind::UpdateType, public}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType, ApubObject}, }; use lemmy_db_schema::{ @@ -27,7 +27,7 @@ use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperati impl UpdateCommunity { pub async fn send( - community: &ApubCommunity, + community: ApubCommunity, actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { @@ -38,15 +38,15 @@ impl UpdateCommunity { let update = UpdateCommunity { actor: ObjectId::new(actor.actor_id()), to: vec![public()], - object: community.to_apub(context).await?, + object: Box::new(community.clone().into_apub(context).await?), cc: vec![community.actor_id()], kind: UpdateType::Update, id: id.clone(), unparsed: Default::default(), }; - let activity = AnnouncableActivities::UpdateCommunity(Box::new(update)); - send_to_community(activity, &id, actor, community, vec![], context).await + let activity = AnnouncableActivities::UpdateCommunity(update); + send_to_community(activity, &id, actor, &community, vec![], context).await } } @@ -58,11 +58,18 @@ impl ActivityHandler for UpdateCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?; + ApubCommunity::verify( + &self.object, + &community.actor_id.clone().into(), + context, + request_counter, + ) + .await?; Ok(()) } @@ -73,12 +80,7 @@ impl ActivityHandler for UpdateCommunity { ) -> Result<(), LemmyError> { let community = self.get_community(context, request_counter).await?; - let updated_community = Group::from_apub_to_form( - &self.object, - &community.actor_id.clone().into(), - &context.settings(), - ) - .await?; + let updated_community = self.object.into_form(); let cf = CommunityForm { name: updated_community.name, title: updated_community.title, diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index a4112a0af..54e9c3941 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -1,10 +1,21 @@ +use crate::{ + activities::{ + community::{announce::GetCommunity, send_to_community}, + deletion::{receive_delete_action, verify_delete_activity, DeletableObjects}, + generate_activity_id, + verify_activity, + verify_is_public, + }, + activity_lists::AnnouncableActivities, + objects::{community::ApubCommunity, person::ApubPerson}, + protocol::activities::deletion::delete::Delete, +}; use activitystreams::{activity::kind::DeleteType, public}; use anyhow::anyhow; -use url::Url; - use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, }; use lemmy_db_schema::{ @@ -29,20 +40,7 @@ use lemmy_websocket::{ LemmyContext, UserOperationCrud, }; - -use crate::{ - activities::{ - community::{announce::GetCommunity, send_to_community}, - deletion::{receive_delete_action, verify_delete_activity, DeletableObjects}, - generate_activity_id, - verify_activity, - verify_is_public, - }, - activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, - objects::{community::ApubCommunity, person::ApubPerson}, - protocol::activities::deletion::delete::Delete, -}; +use url::Url; #[async_trait::async_trait(?Send)] impl ActivityHandler for Delete { @@ -52,12 +50,12 @@ impl ActivityHandler for Delete { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_delete_activity( &self.object, - self, + &self.actor, &community, self.summary.is_some(), context, diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index 3e9a7a3a2..1d5a3836f 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -1,8 +1,12 @@ -use url::Url; - +use crate::{ + activities::{verify_mod_action, verify_person_in_community}, + objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, + protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete}, +}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ - traits::{ActivityFields, ActorType, ApubObject}, + object_id::ObjectId, + traits::{ActorType, ApubObject}, verify::verify_domains_match, }; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; @@ -12,13 +16,7 @@ use lemmy_websocket::{ LemmyContext, UserOperationCrud, }; - -use crate::{ - activities::{verify_mod_action, verify_person_in_community}, - fetcher::object_id::ObjectId, - objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete}, -}; +use url::Url; pub mod delete; pub mod undo_delete; @@ -55,9 +53,9 @@ pub async fn send_apub_remove( } pub enum DeletableObjects { - Community(Box), - Comment(Box), - Post(Box), + Community(ApubCommunity), + Comment(ApubComment), + Post(ApubPost), } impl DeletableObjects { @@ -66,13 +64,13 @@ impl DeletableObjects { context: &LemmyContext, ) -> Result { if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? { - return Ok(DeletableObjects::Community(Box::new(c))); + return Ok(DeletableObjects::Community(c)); } if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? { - return Ok(DeletableObjects::Post(Box::new(p))); + return Ok(DeletableObjects::Post(p)); } if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? { - return Ok(DeletableObjects::Comment(Box::new(c))); + return Ok(DeletableObjects::Comment(c)); } Err(diesel::NotFound.into()) } @@ -80,27 +78,26 @@ impl DeletableObjects { pub(in crate::activities) async fn verify_delete_activity( object: &Url, - activity: &dyn ActivityFields, + actor: &ObjectId, community: &ApubCommunity, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let object = DeletableObjects::read_from_db(object, context).await?; - let actor = ObjectId::new(activity.actor().clone()); match object { DeletableObjects::Community(community) => { if community.local { // can only do this check for local community, in remote case it would try to fetch the // deleted community (which fails) - verify_person_in_community(&actor, &community, context, request_counter).await?; + verify_person_in_community(actor, &community, context, request_counter).await?; } // community deletion is always a mod (or admin) action - verify_mod_action(&actor, &community, context, request_counter).await?; + verify_mod_action(actor, &community, context, request_counter).await?; } DeletableObjects::Post(p) => { verify_delete_activity_post_or_comment( - activity, + actor, &p.ap_id.clone().into(), community, is_mod_action, @@ -111,7 +108,7 @@ pub(in crate::activities) async fn verify_delete_activity( } DeletableObjects::Comment(c) => { verify_delete_activity_post_or_comment( - activity, + actor, &c.ap_id.clone().into(), community, is_mod_action, @@ -125,20 +122,19 @@ pub(in crate::activities) async fn verify_delete_activity( } async fn verify_delete_activity_post_or_comment( - activity: &dyn ActivityFields, + actor: &ObjectId, object_id: &Url, community: &ApubCommunity, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = ObjectId::new(activity.actor().clone()); - verify_person_in_community(&actor, community, context, request_counter).await?; + verify_person_in_community(actor, community, context, request_counter).await?; if is_mod_action { - verify_mod_action(&actor, community, context, request_counter).await?; + verify_mod_action(actor, community, context, request_counter).await?; } else { // domain of post ap_id and post.creator ap_id are identical, so we just check the former - verify_domains_match(activity.actor(), object_id)?; + verify_domains_match(actor.inner(), object_id)?; } Ok(()) } diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 2de4aefd3..edd893bc3 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -1,20 +1,3 @@ -use activitystreams::{activity::kind::UndoType, public}; -use anyhow::anyhow; -use url::Url; - -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - data::Data, - traits::{ActivityHandler, ActorType}, -}; -use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; -use lemmy_utils::LemmyError; -use lemmy_websocket::{ - send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message}, - LemmyContext, - UserOperationCrud, -}; - use crate::{ activities::{ community::{announce::GetCommunity, send_to_community}, @@ -24,10 +7,25 @@ use crate::{ verify_is_public, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete}, }; +use activitystreams::{activity::kind::UndoType, public}; +use anyhow::anyhow; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + data::Data, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, +}; +use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; +use lemmy_utils::LemmyError; +use lemmy_websocket::{ + send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message}, + LemmyContext, + UserOperationCrud, +}; +use url::Url; #[async_trait::async_trait(?Send)] impl ActivityHandler for UndoDelete { @@ -37,13 +35,13 @@ impl ActivityHandler for UndoDelete { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; self.object.verify(context, request_counter).await?; let community = self.get_community(context, request_counter).await?; verify_delete_activity( &self.object.object, - self, + &self.actor, &community, self.object.summary.is_some(), context, diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index 984d622a8..44d6009a2 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -1,13 +1,13 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity}, - fetcher::object_id::ObjectId, protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity}, }; use activitystreams::activity::kind::AcceptType; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, verify::verify_urls_match, }; use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable}; @@ -51,9 +51,9 @@ impl ActivityHandler for AcceptFollowCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; - verify_urls_match(self.to[0].inner(), self.object.actor())?; - verify_urls_match(self.actor(), self.object.to[0].inner())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; + verify_urls_match(self.to[0].inner(), self.object.actor.inner())?; + verify_urls_match(self.actor.inner(), self.object.to[0].inner())?; self.object.verify(context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index e048907fe..22f3db4ba 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -6,7 +6,6 @@ use crate::{ verify_person, verify_person_in_community, }, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity}, }; @@ -14,6 +13,7 @@ use activitystreams::activity::kind::FollowType; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, verify::verify_urls_match, }; @@ -71,7 +71,7 @@ impl ActivityHandler for FollowCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_urls_match(self.to[0].inner(), self.object.inner())?; verify_person(&self.actor, context, request_counter).await?; let community = self.to[0].dereference(context, request_counter).await?; diff --git a/crates/apub/src/activities/following/undo_follow.rs b/crates/apub/src/activities/following/undo_follow.rs index c3fd78b51..ac25fcd5c 100644 --- a/crates/apub/src/activities/following/undo_follow.rs +++ b/crates/apub/src/activities/following/undo_follow.rs @@ -1,6 +1,5 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity}, }; @@ -8,7 +7,8 @@ use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, verify::verify_urls_match, }; use lemmy_db_schema::{ @@ -49,9 +49,9 @@ impl ActivityHandler for UndoFollowCommunity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_urls_match(self.to[0].inner(), self.object.object.inner())?; - verify_urls_match(self.actor(), self.object.actor())?; + verify_urls_match(self.actor.inner(), self.object.actor.inner())?; verify_person(&self.actor, context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index bc6cfb512..a6c753764 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -1,7 +1,6 @@ use crate::{ check_is_apub_id_valid, context::WithContext, - fetcher::object_id::ObjectId, generate_moderators_url, insert_activity, objects::{community::ApubCommunity, person::ApubPerson}, @@ -11,7 +10,8 @@ use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{ activity_queue::send_activity, - traits::{ActivityFields, ActorType}, + object_id::ObjectId, + traits::ActorType, verify::verify_domains_match, }; use lemmy_db_schema::source::community::Community; @@ -71,9 +71,9 @@ pub(crate) async fn verify_person_in_community( Ok(()) } -fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result<(), LemmyError> { - check_is_apub_id_valid(activity.actor(), false, settings)?; - verify_domains_match(activity.id_unchecked(), activity.actor())?; +fn verify_activity(id: &Url, actor: &Url, settings: &Settings) -> Result<(), LemmyError> { + check_is_apub_id_valid(actor, false, settings)?; + verify_domains_match(id, actor)?; Ok(()) } @@ -110,14 +110,14 @@ fn verify_add_remove_moderator_target( target: &Url, community: &ApubCommunity, ) -> Result<(), LemmyError> { - if target != &generate_moderators_url(&community.actor_id)?.into_inner() { + if target != &generate_moderators_url(&community.actor_id)?.into() { return Err(anyhow!("Unkown target url").into()); } Ok(()) } -pub(crate) fn verify_is_public(to: &[Url]) -> Result<(), LemmyError> { - if !to.contains(&public()) { +pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> { + if !to.contains(&public()) && !cc.contains(&public()) { return Err(anyhow!("Object is not public").into()); } Ok(()) diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index 41590493c..05a7f38d0 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -1,16 +1,3 @@ -use activitystreams::public; -use anyhow::anyhow; - -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - data::Data, - traits::{ActivityFields, ActivityHandler, ActorType, ApubObject}, - verify::{verify_domains_match, verify_urls_match}, -}; -use lemmy_db_schema::{source::community::Community, traits::Crud}; -use lemmy_utils::LemmyError; -use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; - use crate::{ activities::{ check_community_deleted_or_removed, @@ -22,14 +9,25 @@ use crate::{ verify_person_in_community, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType}, }; +use activitystreams::public; +use anyhow::anyhow; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + data::Data, + object_id::ObjectId, + traits::{ActivityHandler, ActorType, ApubObject}, + verify::{verify_domains_match, verify_urls_match}, +}; +use lemmy_db_schema::{source::community::Community, traits::Crud}; +use lemmy_utils::LemmyError; +use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; impl CreateOrUpdatePost { pub(crate) async fn new( - post: &ApubPost, + post: ApubPost, actor: &ApubPerson, community: &ApubCommunity, kind: CreateOrUpdateType, @@ -42,7 +40,7 @@ impl CreateOrUpdatePost { Ok(CreateOrUpdatePost { actor: ObjectId::new(actor.actor_id()), to: vec![public()], - object: post.to_apub(context).await?, + object: post.into_apub(context).await?, cc: vec![community.actor_id()], kind, id: id.clone(), @@ -50,7 +48,7 @@ impl CreateOrUpdatePost { }) } pub async fn send( - post: &ApubPost, + post: ApubPost, actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, @@ -63,7 +61,7 @@ impl CreateOrUpdatePost { .into(); let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?; let id = create_or_update.id.clone(); - let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update)); + let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update); send_to_community(activity, &id, actor, &community, vec![], context).await } } @@ -76,16 +74,16 @@ impl ActivityHandler for CreateOrUpdatePost { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; check_community_deleted_or_removed(&community)?; match self.kind { CreateOrUpdateType::Create => { - verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; - verify_urls_match(self.actor(), self.object.attributed_to.inner())?; + verify_domains_match(self.actor.inner(), self.object.id.inner())?; + verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?; // 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, @@ -101,12 +99,12 @@ impl ActivityHandler for CreateOrUpdatePost { if is_mod_action { verify_mod_action(&self.actor, &community, context, request_counter).await?; } else { - verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; - verify_urls_match(self.actor(), self.object.attributed_to.inner())?; + verify_domains_match(self.actor.inner(), self.object.id.inner())?; + verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?; } } } - self.object.verify(context, request_counter).await?; + ApubPost::verify(&self.object, self.actor.inner(), context, request_counter).await?; Ok(()) } @@ -115,9 +113,7 @@ impl ActivityHandler for CreateOrUpdatePost { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = self.actor.dereference(context, request_counter).await?; - let post = - ApubPost::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?; + let post = ApubPost::from_apub(self.object, context, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePost, diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs index cfd7c8bcf..e3d72f54a 100644 --- a/crates/apub/src/activities/private_message/create_or_update.rs +++ b/crates/apub/src/activities/private_message/create_or_update.rs @@ -1,6 +1,5 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, - fetcher::object_id::ObjectId, objects::{person::ApubPerson, private_message::ApubPrivateMessage}, protocol::activities::{ private_message::create_or_update::CreateOrUpdatePrivateMessage, @@ -10,6 +9,7 @@ use crate::{ use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType, ApubObject}, verify::verify_domains_match, }; @@ -19,7 +19,7 @@ use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud} impl CreateOrUpdatePrivateMessage { pub async fn send( - private_message: &ApubPrivateMessage, + private_message: ApubPrivateMessage, actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, @@ -38,7 +38,7 @@ impl CreateOrUpdatePrivateMessage { id: id.clone(), actor: ObjectId::new(actor.actor_id()), to: [ObjectId::new(recipient.actor_id())], - object: private_message.to_apub(context).await?, + object: private_message.into_apub(context).await?, kind, unparsed: Default::default(), }; @@ -54,10 +54,10 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_person(&self.actor, context, request_counter).await?; - verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; - self.object.verify(context, request_counter).await?; + verify_domains_match(self.actor.inner(), self.object.id.inner())?; + ApubPrivateMessage::verify(&self.object, self.actor.inner(), context, request_counter).await?; Ok(()) } @@ -67,8 +67,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { request_counter: &mut i32, ) -> Result<(), LemmyError> { let private_message = - ApubPrivateMessage::from_apub(&self.object, context, self.actor.inner(), request_counter) - .await?; + ApubPrivateMessage::from_apub(self.object, context, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage, diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index da3b6472d..46675442f 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -1,6 +1,5 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, - fetcher::object_id::ObjectId, objects::{person::ApubPerson, private_message::ApubPrivateMessage}, protocol::activities::private_message::delete::DeletePrivateMessage, }; @@ -8,6 +7,7 @@ use activitystreams::activity::kind::DeleteType; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, traits::{ActivityHandler, ActorType}, verify::verify_domains_match, }; @@ -62,7 +62,7 @@ impl ActivityHandler for DeletePrivateMessage { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_person(&self.actor, context, request_counter).await?; verify_domains_match(self.actor.inner(), self.object.inner())?; Ok(()) diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index bba9e0f22..fbb8660c3 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -1,6 +1,5 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, - fetcher::object_id::ObjectId, objects::{person::ApubPerson, private_message::ApubPrivateMessage}, protocol::activities::private_message::{ delete::DeletePrivateMessage, @@ -11,7 +10,8 @@ use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, verify::{verify_domains_match, verify_urls_match}, }; use lemmy_db_schema::{ @@ -59,10 +59,10 @@ impl ActivityHandler for UndoDeletePrivateMessage { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self, &context.settings())?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; verify_person(&self.actor, context, request_counter).await?; - verify_urls_match(self.actor(), self.object.actor())?; - verify_domains_match(self.actor(), self.object.object.inner())?; + verify_urls_match(self.actor.inner(), self.object.actor.inner())?; + verify_domains_match(self.actor.inner(), self.object.object.inner())?; self.object.verify(context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index e95d25179..c066d731c 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -1,17 +1,3 @@ -use std::ops::Deref; - -use activitystreams::{activity::kind::UndoType, public}; - -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, - verify::verify_urls_match, -}; -use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; - use crate::{ activities::{ community::{announce::GetCommunity, send_to_community}, @@ -22,7 +8,6 @@ use crate::{ voting::{undo_vote_comment, undo_vote_post}, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::voting::{ undo_vote::UndoVote, @@ -30,6 +15,17 @@ use crate::{ }, PostOrComment, }; +use activitystreams::{activity::kind::UndoType, public}; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + data::Data, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, + verify::verify_urls_match, +}; +use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; impl UndoVote { pub async fn send( @@ -72,11 +68,11 @@ impl ActivityHandler for UndoVote { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; - verify_urls_match(self.actor(), self.object.actor())?; + verify_urls_match(self.actor.inner(), self.object.actor.inner())?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -93,7 +89,7 @@ impl ActivityHandler for UndoVote { .dereference(context, request_counter) .await?; match object { - PostOrComment::Post(p) => undo_vote_post(actor, p.deref(), context).await, + PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await, PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await, } } diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 01df4b93e..304c512a7 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -1,20 +1,3 @@ -use std::ops::Deref; - -use activitystreams::public; - -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - data::Data, - traits::{ActivityHandler, ActorType}, -}; -use lemmy_db_schema::{ - newtypes::CommunityId, - source::{community::Community, post::Post}, - traits::Crud, -}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; - use crate::{ activities::{ community::{announce::GetCommunity, send_to_community}, @@ -25,11 +8,24 @@ use crate::{ voting::{vote_comment, vote_post}, }, activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::voting::vote::{Vote, VoteType}, PostOrComment, }; +use activitystreams::public; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + data::Data, + object_id::ObjectId, + traits::{ActivityHandler, ActorType}, +}; +use lemmy_db_schema::{ + newtypes::CommunityId, + source::{community::Community, post::Post}, + traits::Crud, +}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; impl Vote { pub(in crate::activities::voting) fn new( @@ -78,8 +74,8 @@ impl ActivityHandler for Vote { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_is_public(&self.to)?; - verify_activity(self, &context.settings())?; + verify_is_public(&self.to, &self.cc)?; + verify_activity(&self.id, self.actor.inner(), &context.settings())?; let community = self.get_community(context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?; Ok(()) @@ -93,7 +89,7 @@ impl ActivityHandler for Vote { let actor = self.actor.dereference(context, request_counter).await?; let object = self.object.dereference(context, request_counter).await?; match object { - PostOrComment::Post(p) => vote_post(&self.kind, actor, p.deref(), context).await, + PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await, PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await, } } diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 1197af85f..2b695111f 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -26,32 +26,32 @@ use crate::{ voting::{undo_vote::UndoVote, vote::Vote}, }, }; -use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler}; +use lemmy_apub_lib::traits::ActivityHandler; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] #[serde(untagged)] #[activity_handler(LemmyContext)] pub enum SharedInboxActivities { GroupInboxActivities(GroupInboxActivities), // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt. - PersonInboxActivities(PersonInboxActivities), + PersonInboxActivities(Box), } -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] #[serde(untagged)] #[activity_handler(LemmyContext)] pub enum GroupInboxActivities { FollowCommunity(FollowCommunity), UndoFollowCommunity(UndoFollowCommunity), - AnnouncableActivities(AnnouncableActivities), + AnnouncableActivities(Box), Report(Report), } -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] #[serde(untagged)] #[activity_handler(LemmyContext)] pub enum PersonInboxActivities { @@ -61,20 +61,20 @@ pub enum PersonInboxActivities { CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage), DeletePrivateMessage(DeletePrivateMessage), UndoDeletePrivateMessage(UndoDeletePrivateMessage), - AnnounceActivity(Box), + AnnounceActivity(AnnounceActivity), } -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] #[serde(untagged)] #[activity_handler(LemmyContext)] pub enum AnnouncableActivities { CreateOrUpdateComment(CreateOrUpdateComment), - CreateOrUpdatePost(Box), + CreateOrUpdatePost(CreateOrUpdatePost), Vote(Vote), UndoVote(UndoVote), Delete(Delete), UndoDelete(UndoDelete), - UpdateCommunity(Box), + UpdateCommunity(UpdateCommunity), BlockUserFromCommunity(BlockUserFromCommunity), UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), AddMod(AddMod), diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index d97affe2c..695f7aca1 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -1,13 +1,12 @@ use crate::{ collections::CommunityContext, - fetcher::object_id::ObjectId, generate_moderators_url, objects::person::ApubPerson, protocol::collections::group_moderators::GroupModerators, }; use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{traits::ApubObject, verify::verify_domains_match}; +use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject, verify::verify_domains_match}; use lemmy_db_schema::{ source::community::{CommunityModerator, CommunityModeratorForm}, traits::Joinable, @@ -50,11 +49,11 @@ impl ApubObject for ApubCommunityModerators { unimplemented!() } - async fn to_apub(&self, data: &Self::DataType) -> Result { + async fn into_apub(self, data: &Self::DataType) -> Result { let ordered_items = self .0 - .iter() - .map(|m| ObjectId::::new(m.moderator.actor_id.clone().into_inner())) + .into_iter() + .map(|m| ObjectId::::new(m.moderator.actor_id)) .collect(); Ok(GroupModerators { r#type: OrderedCollectionType::OrderedCollection, @@ -67,13 +66,21 @@ impl ApubObject for ApubCommunityModerators { unimplemented!() } - async fn from_apub( - apub: &Self::ApubType, - data: &Self::DataType, + async fn verify( + group_moderators: &GroupModerators, expected_domain: &Url, + _context: &CommunityContext, + _request_counter: &mut i32, + ) -> Result<(), LemmyError> { + verify_domains_match(&group_moderators.id, expected_domain)?; + Ok(()) + } + + async fn from_apub( + apub: Self::ApubType, + data: &Self::DataType, request_counter: &mut i32, ) -> Result { - verify_domains_match(expected_domain, &apub.id)?; let community_id = data.0.id; let current_moderators = blocking(data.1.pool(), move |conn| { CommunityModeratorView::for_community(conn, community_id) @@ -81,7 +88,7 @@ impl ApubObject for ApubCommunityModerators { .await??; // Remove old mods from database which arent in the moderators collection anymore for mod_user in ¤t_moderators { - let mod_id = ObjectId::new(mod_user.moderator.actor_id.clone().into_inner()); + let mod_id = ObjectId::new(mod_user.moderator.actor_id.clone()); if !apub.ordered_items.contains(&mod_id) { let community_moderator_form = CommunityModeratorForm { community_id: mod_user.community.id, @@ -95,12 +102,11 @@ impl ApubObject for ApubCommunityModerators { } // Add new mods to database which have been added to moderators collection - for mod_id in &apub.ordered_items { - let mod_id = ObjectId::new(mod_id.clone()); + for mod_id in apub.ordered_items { + let mod_id = ObjectId::new(mod_id); let mod_user: ApubPerson = mod_id.dereference(&data.1, request_counter).await?; if !current_moderators - .clone() .iter() .map(|c| c.moderator.actor_id.clone()) .any(|x| x == mod_user.actor_id) @@ -167,7 +173,10 @@ mod tests { 0: community, 1: context, }; - ApubCommunityModerators::from_apub(&json, &community_context, &url, &mut request_counter) + ApubCommunityModerators::verify(&json, &url, &community_context, &mut request_counter) + .await + .unwrap(); + ApubCommunityModerators::from_apub(json, &community_context, &mut request_counter) .await .unwrap(); assert_eq!(request_counter, 0); diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs index 451c3fa94..3632f61a7 100644 --- a/crates/apub/src/collections/community_outbox.rs +++ b/crates/apub/src/collections/community_outbox.rs @@ -1,7 +1,14 @@ +use crate::{ + collections::CommunityContext, + generate_outbox_url, + objects::{person::ApubPerson, post::ApubPost}, + protocol::{ + activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType}, + collections::group_outbox::GroupOutbox, + }, +}; use activitystreams::collection::kind::OrderedCollectionType; use chrono::NaiveDateTime; -use url::Url; - use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, @@ -13,16 +20,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_utils::LemmyError; - -use crate::{ - collections::CommunityContext, - generate_outbox_url, - objects::{person::ApubPerson, post::ApubPost}, - protocol::{ - activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType}, - collections::group_outbox::GroupOutbox, - }, -}; +use url::Url; #[derive(Clone, Debug)] pub(crate) struct ApubCommunityOutbox(Vec); @@ -62,9 +60,9 @@ impl ApubObject for ApubCommunityOutbox { Ok(()) } - async fn to_apub(&self, data: &Self::DataType) -> Result { + async fn into_apub(self, data: &Self::DataType) -> Result { let mut ordered_items = vec![]; - for post in &self.0 { + for post in self.0 { let actor = post.creator_id; let actor: ApubPerson = blocking(data.1.pool(), move |conn| Person::read(conn, actor)) .await?? @@ -87,14 +85,22 @@ impl ApubObject for ApubCommunityOutbox { unimplemented!() } - async fn from_apub( - apub: &Self::ApubType, - data: &Self::DataType, + async fn verify( + group_outbox: &GroupOutbox, expected_domain: &Url, + _context: &CommunityContext, + _request_counter: &mut i32, + ) -> Result<(), LemmyError> { + verify_domains_match(expected_domain, &group_outbox.id)?; + Ok(()) + } + + async fn from_apub( + apub: Self::ApubType, + data: &Self::DataType, request_counter: &mut i32, ) -> Result { - verify_domains_match(expected_domain, &apub.id)?; - let mut outbox_activities = apub.ordered_items.clone(); + let mut outbox_activities = apub.ordered_items; if outbox_activities.len() > 20 { outbox_activities = outbox_activities[0..20].to_vec(); } diff --git a/crates/apub/src/context.rs b/crates/apub/src/context.rs index cf4b704fc..55486872f 100644 --- a/crates/apub/src/context.rs +++ b/crates/apub/src/context.rs @@ -1,33 +1,9 @@ -use activitystreams::{base::AnyBase, context, primitives::OneOrMany}; +use activitystreams::{base::AnyBase, primitives::OneOrMany}; use serde::{Deserialize, Serialize}; -use serde_json::json; -use url::Url; lazy_static! { - static ref CONTEXT: OneOrMany = { - let context_ext = AnyBase::from_arbitrary_json(json!( - { - "sc": "http://schema.org#", - "sensitive": "as:sensitive", - "stickied": "as:stickied", - "pt": "https://join-lemmy.org#", - "comments_enabled": { - "type": "sc:Boolean", - "id": "pt:commentsEnabled" - }, - "moderators": "as:moderators", - "matrixUserId": { - "type": "sc:Text", - "id": "as:alsoKnownAs" - }, - })) - .expect("parse context"); - OneOrMany::from(vec![ - AnyBase::from(context()), - context_ext, - AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")), - ]) - }; + static ref CONTEXT: OneOrMany = + serde_json::from_str(include_str!("../assets/lemmy/context.json")).expect("parse context"); } #[derive(Serialize, Deserialize)] diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index db39d2282..d41ee4f71 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -1,56 +1,3 @@ -pub mod object_id; pub mod post_or_comment; pub mod search; - -use crate::{ - fetcher::object_id::ObjectId, - objects::{community::ApubCommunity, person::ApubPerson}, -}; -use chrono::NaiveDateTime; -use lemmy_apub_lib::traits::ActorType; -use lemmy_db_schema::naive_now; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; -use url::Url; - -static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60; -static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10; - -/// Get a remote actor from its apub ID (either a person or a community). Thin wrapper around -/// `get_or_fetch_and_upsert_person()` and `get_or_fetch_and_upsert_community()`. -/// -/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. -/// Otherwise it is fetched from the remote instance, stored and returned. -pub(crate) async fn get_or_fetch_and_upsert_actor( - apub_id: Url, - context: &LemmyContext, - recursion_counter: &mut i32, -) -> Result, LemmyError> { - let community_id = ObjectId::::new(apub_id.clone()); - let community = community_id.dereference(context, recursion_counter).await; - let actor: Box = match community { - Ok(c) => Box::new(c), - Err(_) => { - let person_id = ObjectId::new(apub_id); - let person: ApubPerson = person_id.dereference(context, recursion_counter).await?; - Box::new(person) - } - }; - Ok(actor) -} - -/// Determines when a remote actor should be refetched from its instance. In release builds, this is -/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds -/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`. -/// -/// TODO it won't pick up new avatars, summaries etc until a day after. -/// Actors need an "update" activity pushed to other servers to fix this. -fn should_refetch_object(last_refreshed: NaiveDateTime) -> bool { - let update_interval = if cfg!(debug_assertions) { - // avoid infinite loop when fetching community outbox - chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG) - } else { - chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS) - }; - last_refreshed.lt(&(naive_now() - update_interval)) -} +pub mod user_or_community; diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index c0bc46a81..2ca84bee1 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -1,33 +1,25 @@ -use chrono::NaiveDateTime; -use serde::Deserialize; -use url::Url; - -use lemmy_apub_lib::traits::ApubObject; -use lemmy_db_schema::source::{comment::CommentForm, post::PostForm}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; - use crate::{ objects::{comment::ApubComment, post::ApubPost}, protocol::objects::{note::Note, page::Page}, }; +use chrono::NaiveDateTime; +use lemmy_apub_lib::traits::ApubObject; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use serde::Deserialize; +use url::Url; #[derive(Clone, Debug)] pub enum PostOrComment { - Post(Box), + Post(ApubPost), Comment(ApubComment), } -pub enum PostOrCommentForm { - PostForm(Box), - CommentForm(CommentForm), -} - #[derive(Deserialize)] #[serde(untagged)] pub enum PageOrNote { - Page(Box), - Note(Box), + Page(Page), + Note(Note), } #[async_trait::async_trait(?Send)] @@ -44,13 +36,10 @@ impl ApubObject for PostOrComment { async fn read_from_apub_id( object_id: Url, data: &Self::DataType, - ) -> Result, LemmyError> - where - Self: Sized, - { + ) -> Result, LemmyError> { let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?; Ok(match post { - Some(o) => Some(PostOrComment::Post(Box::new(o))), + Some(o) => Some(PostOrComment::Post(o)), None => ApubComment::read_from_apub_id(object_id, data) .await? .map(PostOrComment::Comment), @@ -64,7 +53,7 @@ impl ApubObject for PostOrComment { } } - async fn to_apub(&self, _data: &Self::DataType) -> Result { + async fn into_apub(self, _data: &Self::DataType) -> Result { unimplemented!() } @@ -72,22 +61,30 @@ impl ApubObject for PostOrComment { unimplemented!() } - async fn from_apub( - apub: &PageOrNote, - context: &LemmyContext, + async fn verify( + apub: &Self::ApubType, expected_domain: &Url, + data: &Self::DataType, request_counter: &mut i32, - ) -> Result - where - Self: Sized, - { + ) -> Result<(), LemmyError> { + match apub { + PageOrNote::Page(a) => ApubPost::verify(a, expected_domain, data, request_counter).await, + PageOrNote::Note(a) => ApubComment::verify(a, expected_domain, data, request_counter).await, + } + } + + async fn from_apub( + apub: PageOrNote, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result { Ok(match apub { - PageOrNote::Page(p) => PostOrComment::Post(Box::new( - ApubPost::from_apub(p, context, expected_domain, request_counter).await?, - )), - PageOrNote::Note(n) => PostOrComment::Comment( - ApubComment::from_apub(n, context, expected_domain, request_counter).await?, - ), + PageOrNote::Page(p) => { + PostOrComment::Post(ApubPost::from_apub(p, context, request_counter).await?) + } + PageOrNote::Note(n) => { + PostOrComment::Comment(ApubComment::from_apub(n, context, request_counter).await?) + } }) } } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 51699eb04..1c03a10bd 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -1,11 +1,13 @@ +use crate::{ + objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, + protocol::objects::{group::Group, note::Note, page::Page, person::Person}, +}; use anyhow::anyhow; use chrono::NaiveDateTime; use itertools::Itertools; -use serde::Deserialize; -use url::Url; - use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::ApubObject, webfinger::{webfinger_resolve_actor, WebfingerType}, }; @@ -15,12 +17,8 @@ use lemmy_db_schema::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; - -use crate::{ - fetcher::object_id::ObjectId, - objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::objects::{group::Group, note::Note, page::Page, person::Person}, -}; +use serde::Deserialize; +use url::Url; /// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// @@ -157,7 +155,7 @@ impl ApubObject for SearchableObjects { } } - async fn to_apub(&self, _data: &Self::DataType) -> Result { + async fn into_apub(self, _data: &Self::DataType) -> Result { unimplemented!() } @@ -165,19 +163,40 @@ impl ApubObject for SearchableObjects { unimplemented!() } - async fn from_apub( + async fn verify( apub: &Self::ApubType, + expected_domain: &Url, + data: &Self::DataType, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + match apub { + SearchableApubTypes::Group(a) => { + ApubCommunity::verify(a, expected_domain, data, request_counter).await + } + SearchableApubTypes::Person(a) => { + ApubPerson::verify(a, expected_domain, data, request_counter).await + } + SearchableApubTypes::Page(a) => { + ApubPost::verify(a, expected_domain, data, request_counter).await + } + SearchableApubTypes::Note(a) => { + ApubComment::verify(a, expected_domain, data, request_counter).await + } + } + } + + async fn from_apub( + apub: Self::ApubType, context: &LemmyContext, - ed: &Url, rc: &mut i32, ) -> Result { use SearchableApubTypes as SAT; use SearchableObjects as SO; Ok(match apub { - SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, context, ed, rc).await?), - SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, ed, rc).await?), - SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, ed, rc).await?), - SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, ed, rc).await?), + SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, context, rc).await?), + SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, rc).await?), + SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, rc).await?), + SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, rc).await?), }) } } diff --git a/crates/apub/src/fetcher/user_or_community.rs b/crates/apub/src/fetcher/user_or_community.rs new file mode 100644 index 000000000..e5bc49ba8 --- /dev/null +++ b/crates/apub/src/fetcher/user_or_community.rs @@ -0,0 +1,120 @@ +use crate::{ + objects::{community::ApubCommunity, person::ApubPerson}, + protocol::objects::{group::Group, person::Person}, +}; +use activitystreams::{chrono::NaiveDateTime, url::Url}; +use lemmy_apub_lib::traits::{ActorType, ApubObject}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use serde::Deserialize; + +#[derive(Clone, Debug)] +pub enum UserOrCommunity { + User(ApubPerson), + Community(ApubCommunity), +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum PersonOrGroup { + Person(Person), + Group(Group), +} + +#[async_trait::async_trait(?Send)] +impl ApubObject for UserOrCommunity { + type DataType = LemmyContext; + type ApubType = PersonOrGroup; + type TombstoneType = (); + + fn last_refreshed_at(&self) -> Option { + Some(match self { + UserOrCommunity::User(p) => p.last_refreshed_at, + UserOrCommunity::Community(p) => p.last_refreshed_at, + }) + } + + async fn read_from_apub_id( + object_id: Url, + data: &Self::DataType, + ) -> Result, LemmyError> { + let person = ApubPerson::read_from_apub_id(object_id.clone(), data).await?; + Ok(match person { + Some(o) => Some(UserOrCommunity::User(o)), + None => ApubCommunity::read_from_apub_id(object_id, data) + .await? + .map(UserOrCommunity::Community), + }) + } + + async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> { + match self { + UserOrCommunity::User(p) => p.delete(data).await, + UserOrCommunity::Community(p) => p.delete(data).await, + } + } + + async fn into_apub(self, _data: &Self::DataType) -> Result { + unimplemented!() + } + + fn to_tombstone(&self) -> Result { + unimplemented!() + } + + async fn verify( + apub: &Self::ApubType, + expected_domain: &Url, + data: &Self::DataType, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + match apub { + PersonOrGroup::Person(a) => { + ApubPerson::verify(a, expected_domain, data, request_counter).await + } + PersonOrGroup::Group(a) => { + ApubCommunity::verify(a, expected_domain, data, request_counter).await + } + } + } + + async fn from_apub( + apub: Self::ApubType, + data: &Self::DataType, + request_counter: &mut i32, + ) -> Result { + Ok(match apub { + PersonOrGroup::Person(p) => { + UserOrCommunity::User(ApubPerson::from_apub(p, data, request_counter).await?) + } + PersonOrGroup::Group(p) => { + UserOrCommunity::Community(ApubCommunity::from_apub(p, data, request_counter).await?) + } + }) + } +} + +impl ActorType for UserOrCommunity { + fn actor_id(&self) -> Url { + todo!() + } + + fn public_key(&self) -> Option { + match self { + UserOrCommunity::User(p) => p.public_key(), + UserOrCommunity::Community(p) => p.public_key(), + } + } + + fn private_key(&self) -> Option { + todo!() + } + + fn inbox_url(&self) -> Url { + todo!() + } + + fn shared_inbox_url(&self) -> Option { + todo!() + } +} diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index 3086f2fd9..f62ff36e2 100644 --- a/crates/apub/src/http/comment.rs +++ b/crates/apub/src/http/comment.rs @@ -30,7 +30,7 @@ pub(crate) async fn get_apub_comment( } if !comment.deleted { - Ok(create_apub_response(&comment.to_apub(&**context).await?)) + Ok(create_apub_response(&comment.into_apub(&**context).await?)) } else { Ok(create_apub_tombstone_response(&comment.to_tombstone()?)) } diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 4110d2b20..1854da98b 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -7,13 +7,13 @@ use crate::{ CommunityContext, }, context::WithContext, - fetcher::object_id::ObjectId, generate_outbox_url, http::{ create_apub_response, create_apub_tombstone_response, payload_to_string, receive_activity, + ActivityCommonFields, }, objects::community::ApubCommunity, protocol::{ @@ -23,7 +23,7 @@ use crate::{ }; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use lemmy_api_common::blocking; -use lemmy_apub_lib::traits::{ActivityFields, ApubObject}; +use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject}; use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -47,7 +47,7 @@ pub(crate) async fn get_apub_community_http( .into(); if !community.deleted { - let apub = community.to_apub(&**context).await?; + let apub = community.into_apub(&**context).await?; Ok(create_apub_response(&apub)) } else { @@ -64,26 +64,28 @@ pub async fn community_inbox( ) -> Result { let unparsed = payload_to_string(payload).await?; info!("Received community inbox activity {}", unparsed); + let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?; let activity = serde_json::from_str::>(&unparsed)?; - receive_group_inbox(activity.inner(), request, &context).await?; + receive_group_inbox(activity.inner(), activity_data, request, &context).await?; Ok(HttpResponse::Ok().finish()) } pub(in crate::http) async fn receive_group_inbox( activity: GroupInboxActivities, + activity_data: ActivityCommonFields, request: HttpRequest, context: &LemmyContext, ) -> Result { - let res = receive_activity(request, activity.clone(), context).await; + let actor_id = ObjectId::new(activity_data.actor.clone()); + let res = receive_activity(request, activity.clone(), activity_data, context).await; if let GroupInboxActivities::AnnouncableActivities(announcable) = activity { let community = announcable.get_community(context, &mut 0).await?; - let actor_id = ObjectId::new(announcable.actor().clone()); verify_person_in_community(&actor_id, &community, context, &mut 0).await?; if community.local { - AnnounceActivity::send(announcable, &community, vec![], context).await?; + AnnounceActivity::send(*announcable, &community, vec![], context).await?; } } @@ -113,10 +115,10 @@ pub(crate) async fn get_apub_community_outbox( Community::read_from_name(conn, &info.community_name) }) .await??; - let id = ObjectId::new(generate_outbox_url(&community.actor_id)?.into_inner()); + let id = ObjectId::new(generate_outbox_url(&community.actor_id)?); let outbox_data = CommunityContext(community.into(), context.get_ref().clone()); let outbox: ApubCommunityOutbox = id.dereference(&outbox_data, &mut 0).await?; - Ok(create_apub_response(&outbox.to_apub(&outbox_data).await?)) + Ok(create_apub_response(&outbox.into_apub(&outbox_data).await?)) } pub(crate) async fn get_apub_community_moderators( @@ -128,10 +130,10 @@ pub(crate) async fn get_apub_community_moderators( }) .await?? .into(); - let id = ObjectId::new(generate_outbox_url(&community.actor_id)?.into_inner()); + let id = ObjectId::new(generate_outbox_url(&community.actor_id)?); let outbox_data = CommunityContext(community, context.get_ref().clone()); let moderators: ApubCommunityModerators = id.dereference(&outbox_data, &mut 0).await?; Ok(create_apub_response( - &moderators.to_apub(&outbox_data).await?, + &moderators.into_apub(&outbox_data).await?, )) } diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 9c61c2747..b3288b0d9 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -2,7 +2,7 @@ use crate::{ activity_lists::SharedInboxActivities, check_is_apub_id_valid, context::WithContext, - fetcher::get_or_fetch_and_upsert_actor, + fetcher::user_or_community::UserOrCommunity, http::{community::receive_group_inbox, person::receive_person_inbox}, insert_activity, }; @@ -19,8 +19,9 @@ use http::StatusCode; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, + object_id::ObjectId, signatures::verify_signature, - traits::{ActivityFields, ActivityHandler}, + traits::{ActivityHandler, ActorType}, APUB_JSON_CONTENT_TYPE, }; use lemmy_db_schema::{source::activity::Activity, DbPool}; @@ -44,13 +45,14 @@ pub async fn shared_inbox( ) -> Result { let unparsed = payload_to_string(payload).await?; info!("Received shared inbox activity {}", unparsed); + let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?; let activity = serde_json::from_str::>(&unparsed)?; match activity.inner() { SharedInboxActivities::GroupInboxActivities(g) => { - receive_group_inbox(g, request, &context).await + receive_group_inbox(g, activity_data, request, &context).await } SharedInboxActivities::PersonInboxActivities(p) => { - receive_person_inbox(p, request, &context).await + receive_person_inbox(*p, activity_data, request, &context).await } } } @@ -65,15 +67,22 @@ async fn payload_to_string(mut payload: Payload) -> Result { Ok(unparsed) } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ActivityCommonFields { + pub(crate) id: Url, + pub(crate) actor: Url, +} + // TODO: move most of this code to library async fn receive_activity<'a, T>( request: HttpRequest, activity: T, + activity_data: ActivityCommonFields, context: &LemmyContext, ) -> Result where T: ActivityHandler - + ActivityFields + Clone + Deserialize<'a> + Serialize @@ -81,26 +90,27 @@ where + Send + 'static, { + check_is_apub_id_valid(&activity_data.actor, false, &context.settings())?; let request_counter = &mut 0; - let actor = - get_or_fetch_and_upsert_actor(activity.actor().clone(), context, request_counter).await?; + let actor = ObjectId::::new(activity_data.actor) + .dereference(context, request_counter) + .await?; verify_signature(&request, &actor.public_key().context(location_info!())?)?; // Do nothing if we received the same activity before - if is_activity_already_known(context.pool(), activity.id_unchecked()).await? { + if is_activity_already_known(context.pool(), &activity_data.id).await? { return Ok(HttpResponse::Ok().finish()); } - check_is_apub_id_valid(activity.actor(), false, &context.settings())?; - info!("Verifying activity {}", activity.id_unchecked().to_string()); + info!("Verifying activity {}", activity_data.id.to_string()); activity .verify(&Data::new(context.clone()), request_counter) .await?; - assert_activity_not_local(&activity, &context.settings().hostname)?; + assert_activity_not_local(&activity_data.id, &context.settings().hostname)?; // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen // if we receive the same activity twice in very quick succession. insert_activity( - activity.id_unchecked(), + &activity_data.id, activity.clone(), false, true, @@ -108,7 +118,7 @@ where ) .await?; - info!("Receiving activity {}", activity.id_unchecked().to_string()); + info!("Receiving activity {}", activity_data.id.to_string()); activity .receive(&Data::new(context.clone()), request_counter) .await?; @@ -183,17 +193,14 @@ pub(crate) async fn is_activity_already_known( } } -fn assert_activity_not_local( - activity: &T, - hostname: &str, -) -> Result<(), LemmyError> { - let activity_domain = activity.id_unchecked().domain().context(location_info!())?; +fn assert_activity_not_local(id: &Url, hostname: &str) -> Result<(), LemmyError> { + let activity_domain = id.domain().context(location_info!())?; if activity_domain == hostname { return Err( anyhow!( "Error: received activity which was sent by local instance: {:?}", - activity + id ) .into(), ); diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index a5ea4ad17..3a3a34038 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -6,6 +6,7 @@ use crate::{ create_apub_tombstone_response, payload_to_string, receive_activity, + ActivityCommonFields, }, objects::person::ApubPerson, protocol::collections::person_outbox::PersonOutbox, @@ -38,7 +39,7 @@ pub(crate) async fn get_apub_person_http( .into(); if !person.deleted { - let apub = person.to_apub(&context).await?; + let apub = person.into_apub(&context).await?; Ok(create_apub_response(&apub)) } else { @@ -54,16 +55,18 @@ pub async fn person_inbox( ) -> Result { let unparsed = payload_to_string(payload).await?; info!("Received person inbox activity {}", unparsed); + let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?; let activity = serde_json::from_str::>(&unparsed)?; - receive_person_inbox(activity.inner(), request, &context).await + receive_person_inbox(activity.inner(), activity_data, request, &context).await } pub(in crate::http) async fn receive_person_inbox( activity: PersonInboxActivities, + activity_data: ActivityCommonFields, request: HttpRequest, context: &LemmyContext, ) -> Result { - receive_activity(request, activity, context).await + receive_activity(request, activity, activity_data, context).await } pub(crate) async fn get_apub_person_outbox( diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs index 0459942a9..52fe002a9 100644 --- a/crates/apub/src/http/post.rs +++ b/crates/apub/src/http/post.rs @@ -30,7 +30,7 @@ pub(crate) async fn get_apub_post( } if !post.deleted { - Ok(create_apub_response(&post.to_apub(&context).await?)) + Ok(create_apub_response(&post.into_apub(&context).await?)) } else { Ok(create_apub_tombstone_response(&post.to_tombstone()?)) } diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 08a55bf9c..83895e8d3 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -1,15 +1,25 @@ -use std::ops::Deref; - +use crate::{ + activities::{verify_is_public, verify_person_in_community}, + check_is_apub_id_valid, + protocol::{ + objects::{ + note::{Note, SourceCompat}, + tombstone::Tombstone, + }, + Source, + }, + PostOrComment, +}; use activitystreams::{object::kind::NoteType, public}; use anyhow::anyhow; use chrono::NaiveDateTime; use html2md::parse_html; -use url::Url; - use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::ApubObject, values::{MediaTypeHtml, MediaTypeMarkdown}, + verify::verify_domains_match, }; use lemmy_db_schema::{ source::{ @@ -21,25 +31,12 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_utils::{ - utils::{convert_datetime, remove_slurs}, + utils::{convert_datetime, markdown_to_html, remove_slurs}, LemmyError, }; use lemmy_websocket::LemmyContext; - -use crate::{ - activities::verify_person_in_community, - check_is_apub_id_valid, - fetcher::object_id::ObjectId, - protocol::{ - objects::{ - note::{Note, SourceCompat}, - tombstone::Tombstone, - }, - Source, - }, - PostOrComment, -}; -use lemmy_utils::utils::markdown_to_html; +use std::ops::Deref; +use url::Url; #[derive(Clone, Debug)] pub struct ApubComment(Comment); @@ -90,7 +87,7 @@ impl ApubObject for ApubComment { Ok(()) } - async fn to_apub(&self, context: &LemmyContext) -> Result { + async fn into_apub(self, context: &LemmyContext) -> Result { let creator_id = self.creator_id; let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; @@ -100,16 +97,17 @@ impl ApubObject for ApubComment { let in_reply_to = if let Some(comment_id) = self.parent_id { let parent_comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??; - ObjectId::::new(parent_comment.ap_id.into_inner()) + ObjectId::::new(parent_comment.ap_id) } else { - ObjectId::::new(post.ap_id.into_inner()) + ObjectId::::new(post.ap_id) }; let note = Note { r#type: NoteType::Note, - id: self.ap_id.to_owned().into_inner(), + id: ObjectId::new(self.ap_id.clone()), attributed_to: ObjectId::new(creator.actor_id), to: vec![public()], + cc: vec![], content: markdown_to_html(&self.content), media_type: Some(MediaTypeHtml::Html), source: SourceCompat::Lemmy(Source { @@ -132,27 +130,22 @@ impl ApubObject for ApubComment { )) } - /// Converts a `Note` to `Comment`. - /// - /// If the parent community, post and comment(s) are not known locally, these are also fetched. - async fn from_apub( + async fn verify( note: &Note, - context: &LemmyContext, expected_domain: &Url, + context: &LemmyContext, request_counter: &mut i32, - ) -> Result { - let ap_id = Some(note.id(expected_domain)?.clone().into()); - let creator = note - .attributed_to - .dereference(context, request_counter) - .await?; - let (post, parent_comment_id) = note.get_parents(context, request_counter).await?; + ) -> Result<(), LemmyError> { + verify_domains_match(note.id.inner(), expected_domain)?; + verify_domains_match(note.attributed_to.inner(), note.id.inner())?; + verify_is_public(¬e.to, ¬e.cc)?; + let (post, _) = note.get_parents(context, request_counter).await?; let community_id = post.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - check_is_apub_id_valid(¬e.id, community.local, &context.settings())?; + check_is_apub_id_valid(note.id.inner(), community.local, &context.settings())?; verify_person_in_community( ¬e.attributed_to, &community.into(), @@ -163,6 +156,22 @@ impl ApubObject for ApubComment { if post.locked { return Err(anyhow!("Post is locked").into()); } + Ok(()) + } + + /// Converts a `Note` to `Comment`. + /// + /// If the parent community, post and comment(s) are not known locally, these are also fetched. + async fn from_apub( + note: Note, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result { + let creator = note + .attributed_to + .dereference(context, request_counter) + .await?; + let (post, parent_comment_id) = note.get_parents(context, request_counter).await?; let content = if let SourceCompat::Lemmy(source) = ¬e.source { source.content.clone() @@ -178,10 +187,10 @@ impl ApubObject for ApubComment { content: content_slurs_removed, removed: None, read: None, - published: note.published.map(|u| u.to_owned().naive_local()), - updated: note.updated.map(|u| u.to_owned().naive_local()), + published: note.published.map(|u| u.naive_local()), + updated: note.updated.map(|u| u.naive_local()), deleted: None, - ap_id, + ap_id: Some(note.id.into()), local: Some(false), }; let comment = blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??; @@ -208,7 +217,10 @@ pub(crate) mod tests { let person = parse_lemmy_person(context).await; let community = parse_lemmy_community(context).await; let post_json = file_to_json_object("assets/lemmy/objects/page.json"); - let post = ApubPost::from_apub(&post_json, context, url, &mut 0) + ApubPost::verify(&post_json, url, context, &mut 0) + .await + .unwrap(); + let post = ApubPost::from_apub(post_json, context, &mut 0) .await .unwrap(); (person, community, post) @@ -227,21 +239,25 @@ pub(crate) mod tests { let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap(); let data = prepare_comment_test(&url, &context).await; - let json = file_to_json_object("assets/lemmy/objects/note.json"); + let json: Note = file_to_json_object("assets/lemmy/objects/note.json"); let mut request_counter = 0; - let comment = ApubComment::from_apub(&json, &context, &url, &mut request_counter) + ApubComment::verify(&json, &url, &context, &mut request_counter) + .await + .unwrap(); + let comment = ApubComment::from_apub(json.clone(), &context, &mut request_counter) .await .unwrap(); - assert_eq!(comment.ap_id.clone().into_inner(), url); + assert_eq!(comment.ap_id, url.into()); assert_eq!(comment.content.len(), 14); assert!(!comment.local); assert_eq!(request_counter, 0); - let to_apub = comment.to_apub(&context).await.unwrap(); + let comment_id = comment.id; + let to_apub = comment.into_apub(&context).await.unwrap(); assert_json_include!(actual: json, expected: to_apub); - Comment::delete(&*context.pool().get().unwrap(), comment.id).unwrap(); + Comment::delete(&*context.pool().get().unwrap(), comment_id).unwrap(); cleanup(data, &context); } @@ -256,16 +272,22 @@ pub(crate) mod tests { Url::parse("https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2") .unwrap(); let person_json = file_to_json_object("assets/pleroma/objects/person.json"); - ApubPerson::from_apub(&person_json, &context, &pleroma_url, &mut 0) + ApubPerson::verify(&person_json, &pleroma_url, &context, &mut 0) + .await + .unwrap(); + ApubPerson::from_apub(person_json, &context, &mut 0) .await .unwrap(); let json = file_to_json_object("assets/pleroma/objects/note.json"); let mut request_counter = 0; - let comment = ApubComment::from_apub(&json, &context, &pleroma_url, &mut request_counter) + ApubComment::verify(&json, &pleroma_url, &context, &mut request_counter) + .await + .unwrap(); + let comment = ApubComment::from_apub(json, &context, &mut request_counter) .await .unwrap(); - assert_eq!(comment.ap_id.clone().into_inner(), pleroma_url); + assert_eq!(comment.ap_id, pleroma_url.into()); assert_eq!(comment.content.len(), 64); assert!(!comment.local); assert_eq!(request_counter, 0); diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 0947ebf37..f2fb45e4f 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -1,27 +1,20 @@ -use activitystreams::{ - actor::{kind::GroupType, Endpoints}, - object::kind::ImageType, -}; -use chrono::NaiveDateTime; -use itertools::Itertools; -use log::debug; -use std::ops::Deref; -use url::Url; - use crate::{ check_is_apub_id_valid, collections::{community_moderators::ApubCommunityModerators, CommunityContext}, - fetcher::object_id::ObjectId, generate_moderators_url, generate_outbox_url, protocol::{ - objects::{group::Group, tombstone::Tombstone}, + objects::{group::Group, tombstone::Tombstone, Endpoints}, ImageObject, Source, }, }; +use activitystreams::{actor::kind::GroupType, object::kind::ImageType}; +use chrono::NaiveDateTime; +use itertools::Itertools; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::{ActorType, ApubObject}, values::MediaTypeMarkdown, }; @@ -32,6 +25,9 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::LemmyContext; +use log::debug; +use std::ops::Deref; +use url::Url; #[derive(Clone, Debug)] pub struct ApubCommunity(Community); @@ -80,7 +76,7 @@ impl ApubObject for ApubCommunity { Ok(()) } - async fn to_apub(&self, _context: &LemmyContext) -> Result { + async fn into_apub(self, _context: &LemmyContext) -> Result { let source = self.description.clone().map(|bio| Source { content: bio, media_type: MediaTypeMarkdown::Markdown, @@ -96,7 +92,7 @@ impl ApubObject for ApubCommunity { let group = Group { kind: GroupType::Group, - id: self.actor_id(), + id: ObjectId::new(self.actor_id()), preferred_username: self.name.clone(), name: self.title.clone(), summary: self.description.as_ref().map(|b| markdown_to_html(b)), @@ -105,14 +101,13 @@ impl ApubObject for ApubCommunity { image, sensitive: Some(self.nsfw), moderators: Some(ObjectId::::new( - generate_moderators_url(&self.actor_id)?.into_inner(), + generate_moderators_url(&self.actor_id)?, )), inbox: self.inbox_url.clone().into(), outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?), followers: self.followers_url.clone().into(), endpoints: Endpoints { shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), - ..Default::default() }, public_key: self.get_public_key()?, published: Some(convert_datetime(self.published)), @@ -129,14 +124,22 @@ impl ApubObject for ApubCommunity { )) } + async fn verify( + group: &Group, + expected_domain: &Url, + context: &LemmyContext, + _request_counter: &mut i32, + ) -> Result<(), LemmyError> { + group.verify(expected_domain, context).await + } + /// Converts a `Group` to `Community`, inserts it into the database and updates moderators. async fn from_apub( - group: &Group, + group: Group, context: &LemmyContext, - expected_domain: &Url, request_counter: &mut i32, ) -> Result { - let form = Group::from_apub_to_form(group, expected_domain, &context.settings()).await?; + let form = Group::into_form(group.clone()); // Fetching mods and outbox is not necessary for Lemmy to work, so ignore errors. Besides, // we need to ignore these errors so that tests can work entirely offline. @@ -166,15 +169,9 @@ impl ApubObject for ApubCommunity { } impl ActorType for ApubCommunity { - fn is_local(&self) -> bool { - self.local - } fn actor_id(&self) -> Url { self.actor_id.to_owned().into() } - fn name(&self) -> String { - self.name.clone() - } fn public_key(&self) -> Option { self.public_key.to_owned() } @@ -187,7 +184,7 @@ impl ActorType for ApubCommunity { } fn shared_inbox_url(&self) -> Option { - self.shared_inbox_url.clone().map(|s| s.into_inner()) + self.shared_inbox_url.clone().map(|s| s.into()) } } @@ -207,8 +204,12 @@ impl ApubCommunity { let follower_inboxes: Vec = follows .into_iter() .filter(|f| !f.follower.local) - .map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url)) - .map(|i| i.into_inner()) + .map(|f| { + f.follower + .shared_inbox_url + .unwrap_or(f.follower.inbox_url) + .into() + }) .collect(); let inboxes = vec![follower_inboxes, additional_inboxes] .into_iter() @@ -239,7 +240,10 @@ pub(crate) mod tests { let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap(); let mut request_counter = 0; - let community = ApubCommunity::from_apub(&json, context, &url, &mut request_counter) + ApubCommunity::verify(&json, &url, context, &mut request_counter) + .await + .unwrap(); + let community = ApubCommunity::from_apub(json, context, &mut request_counter) .await .unwrap(); // this makes two requests to the (intentionally) broken outbox/moderators collections diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 18522e569..e3fcdd2ee 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -3,15 +3,19 @@ use crate::{ generate_outbox_url, objects::get_summary_from_string_or_source, protocol::{ - objects::person::{Person, UserTypes}, + objects::{ + person::{Person, UserTypes}, + Endpoints, + }, ImageObject, Source, }, }; -use activitystreams::{actor::Endpoints, object::kind::ImageType}; +use activitystreams::object::kind::ImageType; use chrono::NaiveDateTime; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::{ActorType, ApubObject}, values::MediaTypeMarkdown, verify::verify_domains_match, @@ -75,7 +79,7 @@ impl ApubObject for ApubPerson { Ok(()) } - async fn to_apub(&self, _pool: &LemmyContext) -> Result { + async fn into_apub(self, _pool: &LemmyContext) -> Result { let kind = if self.bot_account { UserTypes::Service } else { @@ -96,7 +100,7 @@ impl ApubObject for ApubPerson { let person = Person { kind, - id: self.actor_id.to_owned().into_inner(), + id: ObjectId::new(self.actor_id.clone()), preferred_username: self.name.clone(), name: self.display_name.clone(), summary: self.bio.as_ref().map(|b| markdown_to_html(b)), @@ -108,7 +112,6 @@ impl ApubObject for ApubPerson { outbox: generate_outbox_url(&self.actor_id)?.into(), endpoints: Endpoints { shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), - ..Default::default() }, public_key: self.get_public_key()?, updated: self.updated.map(convert_datetime), @@ -122,50 +125,51 @@ impl ApubObject for ApubPerson { unimplemented!() } - async fn from_apub( + async fn verify( person: &Person, - context: &LemmyContext, expected_domain: &Url, + context: &LemmyContext, _request_counter: &mut i32, - ) -> Result { - verify_domains_match(&person.id, expected_domain)?; - let actor_id = Some(person.id.clone().into()); - let name = person.preferred_username.clone(); - let display_name: Option = person.name.clone(); - let bio = get_summary_from_string_or_source(&person.summary, &person.source); - let shared_inbox = person.endpoints.shared_inbox.clone().map(|s| s.into()); - let bot_account = match person.kind { - UserTypes::Person => false, - UserTypes::Service => true, - }; + ) -> Result<(), LemmyError> { + verify_domains_match(person.id.inner(), expected_domain)?; + check_is_apub_id_valid(person.id.inner(), false, &context.settings())?; let slur_regex = &context.settings().slur_regex(); - check_slurs(&name, slur_regex)?; - check_slurs_opt(&display_name, slur_regex)?; + check_slurs(&person.preferred_username, slur_regex)?; + check_slurs_opt(&person.name, slur_regex)?; + let bio = get_summary_from_string_or_source(&person.summary, &person.source); check_slurs_opt(&bio, slur_regex)?; + Ok(()) + } - check_is_apub_id_valid(&person.id, false, &context.settings())?; - + async fn from_apub( + person: Person, + context: &LemmyContext, + _request_counter: &mut i32, + ) -> Result { let person_form = PersonForm { - name, - display_name: Some(display_name), + name: person.preferred_username, + display_name: Some(person.name), banned: None, deleted: None, - avatar: Some(person.icon.clone().map(|i| i.url.into())), - banner: Some(person.image.clone().map(|i| i.url.into())), - published: person.published.map(|u| u.clone().naive_local()), - updated: person.updated.map(|u| u.clone().naive_local()), - actor_id, - bio: Some(bio), + avatar: Some(person.icon.map(|i| i.url.into())), + banner: Some(person.image.map(|i| i.url.into())), + published: person.published.map(|u| u.naive_local()), + updated: person.updated.map(|u| u.naive_local()), + actor_id: Some(person.id.into()), + bio: Some(get_summary_from_string_or_source( + &person.summary, + &person.source, + )), local: Some(false), admin: Some(false), - bot_account: Some(bot_account), + bot_account: Some(person.kind == UserTypes::Service), private_key: None, - public_key: Some(Some(person.public_key.public_key_pem.clone())), + public_key: Some(Some(person.public_key.public_key_pem)), last_refreshed_at: Some(naive_now()), - inbox_url: Some(person.inbox.to_owned().into()), - shared_inbox_url: Some(shared_inbox), - matrix_user_id: Some(person.matrix_user_id.clone()), + inbox_url: Some(person.inbox.into()), + shared_inbox_url: Some(person.endpoints.shared_inbox.map(|s| s.into())), + matrix_user_id: Some(person.matrix_user_id), }; let person = blocking(context.pool(), move |conn| { DbPerson::upsert(conn, &person_form) @@ -176,14 +180,8 @@ impl ApubObject for ApubPerson { } impl ActorType for ApubPerson { - fn is_local(&self) -> bool { - self.local - } fn actor_id(&self) -> Url { - self.actor_id.to_owned().into_inner() - } - fn name(&self) -> String { - self.name.clone() + self.actor_id.to_owned().into() } fn public_key(&self) -> Option { @@ -199,7 +197,7 @@ impl ActorType for ApubPerson { } fn shared_inbox_url(&self) -> Option { - self.shared_inbox_url.clone().map(|s| s.into_inner()) + self.shared_inbox_url.clone().map(|s| s.into()) } } @@ -214,7 +212,10 @@ pub(crate) mod tests { let json = file_to_json_object("assets/lemmy/objects/person.json"); let url = Url::parse("https://enterprise.lemmy.ml/u/picard").unwrap(); let mut request_counter = 0; - let person = ApubPerson::from_apub(&json, context, &url, &mut request_counter) + ApubPerson::verify(&json, &url, context, &mut request_counter) + .await + .unwrap(); + let person = ApubPerson::from_apub(json, context, &mut request_counter) .await .unwrap(); assert_eq!(request_counter, 0); @@ -242,11 +243,14 @@ pub(crate) mod tests { let json = file_to_json_object("assets/pleroma/objects/person.json"); let url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap(); let mut request_counter = 0; - let person = ApubPerson::from_apub(&json, &context, &url, &mut request_counter) + ApubPerson::verify(&json, &url, &context, &mut request_counter) + .await + .unwrap(); + let person = ApubPerson::from_apub(json, &context, &mut request_counter) .await .unwrap(); - assert_eq!(person.actor_id.clone().into_inner(), url); + assert_eq!(person.actor_id, url.into()); assert_eq!(person.name, "lanodan"); assert!(person.public_key.is_some()); assert!(!person.local); diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index b835f812e..4e34fc88e 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -1,7 +1,6 @@ use crate::{ - activities::verify_person_in_community, + activities::{verify_is_public, verify_person_in_community}, check_is_apub_id_valid, - fetcher::object_id::ObjectId, protocol::{ objects::{page::Page, tombstone::Tombstone}, ImageObject, @@ -15,8 +14,10 @@ use activitystreams::{ use chrono::NaiveDateTime; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::ApubObject, values::{MediaTypeHtml, MediaTypeMarkdown}, + verify::verify_domains_match, }; use lemmy_db_schema::{ self, @@ -29,7 +30,7 @@ use lemmy_db_schema::{ }; use lemmy_utils::{ request::fetch_site_data, - utils::{convert_datetime, markdown_to_html, remove_slurs}, + utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs}, LemmyError, }; use lemmy_websocket::LemmyContext; @@ -86,7 +87,7 @@ impl ApubObject for ApubPost { } // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. - async fn to_apub(&self, context: &LemmyContext) -> Result { + async fn into_apub(self, context: &LemmyContext) -> Result { let creator_id = self.creator_id; let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; let community_id = self.community_id; @@ -106,9 +107,10 @@ impl ApubObject for ApubPost { let page = Page { r#type: PageType::Page, - id: self.ap_id.clone().into(), + id: ObjectId::new(self.ap_id.clone()), attributed_to: ObjectId::new(creator.actor_id), to: vec![community.actor_id.into(), public()], + cc: vec![], name: self.name.clone(), content: self.body.as_ref().map(|b| markdown_to_html(b)), media_type: Some(MediaTypeHtml::Html), @@ -132,29 +134,39 @@ impl ApubObject for ApubPost { )) } - async fn from_apub( + async fn verify( page: &Page, - context: &LemmyContext, expected_domain: &Url, + context: &LemmyContext, request_counter: &mut i32, - ) -> Result { + ) -> Result<(), LemmyError> { // We can't verify the domain in case of mod action, because the mod may be on a different // instance from the post author. - let ap_id = if page.is_mod_action(context).await? { - page.id_unchecked() - } else { - page.id(expected_domain)? + if !page.is_mod_action(context).await? { + verify_domains_match(page.id.inner(), expected_domain)?; }; - let ap_id = Some(ap_id.clone().into()); + + let community = page.extract_community(context, request_counter).await?; + check_is_apub_id_valid(page.id.inner(), community.local, &context.settings())?; + verify_person_in_community(&page.attributed_to, &community, context, request_counter).await?; + check_slurs(&page.name, &context.settings().slur_regex())?; + verify_domains_match(page.attributed_to.inner(), page.id.inner())?; + verify_is_public(&page.to, &page.cc)?; + Ok(()) + } + + async fn from_apub( + page: Page, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result { let creator = page .attributed_to .dereference(context, request_counter) .await?; let community = page.extract_community(context, request_counter).await?; - check_is_apub_id_valid(&page.id, community.local, &context.settings())?; - verify_person_in_community(&page.attributed_to, &community, context, request_counter).await?; - let thumbnail_url: Option = page.image.clone().map(|i| i.url); + let thumbnail_url: Option = page.image.map(|i| i.url); let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url { fetch_site_data(context.client(), &context.settings(), Some(url)).await } else { @@ -169,8 +181,8 @@ impl ApubObject for ApubPost { .as_ref() .map(|s| remove_slurs(&s.content, &context.settings().slur_regex())); let form = PostForm { - name: page.name.clone(), - url: page.url.clone().map(|u| u.into()), + name: page.name, + url: page.url.map(|u| u.into()), body: body_slurs_removed, creator_id: creator.id, community_id: community.id, @@ -185,7 +197,7 @@ impl ApubObject for ApubPost { embed_description, embed_html, thumbnail_url: pictrs_thumbnail.map(|u| u.into()), - ap_id, + ap_id: Some(page.id.into()), local: Some(false), }; let post = blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??; @@ -214,11 +226,14 @@ mod tests { let json = file_to_json_object("assets/lemmy/objects/page.json"); let url = Url::parse("https://enterprise.lemmy.ml/post/55143").unwrap(); let mut request_counter = 0; - let post = ApubPost::from_apub(&json, &context, &url, &mut request_counter) + ApubPost::verify(&json, &url, &context, &mut request_counter) + .await + .unwrap(); + let post = ApubPost::from_apub(json, &context, &mut request_counter) .await .unwrap(); - assert_eq!(post.ap_id.clone().into_inner(), url); + assert_eq!(post.ap_id, url.into()); assert_eq!(post.name, "Post title"); assert!(post.body.is_some()); assert_eq!(post.body.as_ref().unwrap().len(), 45); diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 81615fdc9..30c8e4dc5 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -1,16 +1,16 @@ -use crate::{ - fetcher::object_id::ObjectId, - protocol::{ - objects::chat_message::{ChatMessage, ChatMessageType}, - Source, - }, +use crate::protocol::{ + objects::chat_message::{ChatMessage, ChatMessageType}, + Source, }; +use anyhow::anyhow; use chrono::NaiveDateTime; use html2md::parse_html; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + object_id::ObjectId, traits::ApubObject, values::{MediaTypeHtml, MediaTypeMarkdown}, + verify::verify_domains_match, }; use lemmy_db_schema::{ source::{ @@ -71,7 +71,7 @@ impl ApubObject for ApubPrivateMessage { unimplemented!() } - async fn to_apub(&self, context: &LemmyContext) -> Result { + async fn into_apub(self, context: &LemmyContext) -> Result { let creator_id = self.creator_id; let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; @@ -81,7 +81,7 @@ impl ApubObject for ApubPrivateMessage { let note = ChatMessage { r#type: ChatMessageType::ChatMessage, - id: self.ap_id.clone().into(), + id: ObjectId::new(self.ap_id.clone()), attributed_to: ObjectId::new(creator.actor_id), to: [ObjectId::new(recipient.actor_id)], content: markdown_to_html(&self.content), @@ -101,13 +101,29 @@ impl ApubObject for ApubPrivateMessage { unimplemented!() } - async fn from_apub( + async fn verify( note: &ChatMessage, - context: &LemmyContext, expected_domain: &Url, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + verify_domains_match(note.id.inner(), expected_domain)?; + verify_domains_match(note.attributed_to.inner(), note.id.inner())?; + let person = note + .attributed_to + .dereference(context, request_counter) + .await?; + if person.banned { + return Err(anyhow!("Person is banned from site").into()); + } + Ok(()) + } + + async fn from_apub( + note: ChatMessage, + context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let ap_id = Some(note.id(expected_domain)?.clone().into()); let creator = note .attributed_to .dereference(context, request_counter) @@ -123,11 +139,11 @@ impl ApubObject for ApubPrivateMessage { creator_id: creator.id, recipient_id: recipient.id, content, - published: note.published.map(|u| u.to_owned().naive_local()), - updated: note.updated.map(|u| u.to_owned().naive_local()), + published: note.published.map(|u| u.naive_local()), + updated: note.updated.map(|u| u.naive_local()), deleted: None, read: None, - ap_id, + ap_id: Some(note.id.into()), local: Some(false), }; let pm = blocking(context.pool(), move |conn| { @@ -150,12 +166,18 @@ mod tests { async fn prepare_comment_test(url: &Url, context: &LemmyContext) -> (ApubPerson, ApubPerson) { let lemmy_person = file_to_json_object("assets/lemmy/objects/person.json"); - let person1 = ApubPerson::from_apub(&lemmy_person, context, url, &mut 0) + ApubPerson::verify(&lemmy_person, url, context, &mut 0) + .await + .unwrap(); + let person1 = ApubPerson::from_apub(lemmy_person, context, &mut 0) .await .unwrap(); let pleroma_person = file_to_json_object("assets/pleroma/objects/person.json"); let pleroma_url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap(); - let person2 = ApubPerson::from_apub(&pleroma_person, context, &pleroma_url, &mut 0) + ApubPerson::verify(&pleroma_person, &pleroma_url, context, &mut 0) + .await + .unwrap(); + let person2 = ApubPerson::from_apub(pleroma_person, context, &mut 0) .await .unwrap(); (person1, person2) @@ -172,20 +194,24 @@ mod tests { let context = init_context(); let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap(); let data = prepare_comment_test(&url, &context).await; - let json = file_to_json_object("assets/lemmy/objects/chat_message.json"); + let json: ChatMessage = file_to_json_object("assets/lemmy/objects/chat_message.json"); let mut request_counter = 0; - let pm = ApubPrivateMessage::from_apub(&json, &context, &url, &mut request_counter) + ApubPrivateMessage::verify(&json, &url, &context, &mut request_counter) + .await + .unwrap(); + let pm = ApubPrivateMessage::from_apub(json.clone(), &context, &mut request_counter) .await .unwrap(); - assert_eq!(pm.ap_id.clone().into_inner(), url); + assert_eq!(pm.ap_id.clone(), url.into()); assert_eq!(pm.content.len(), 20); assert_eq!(request_counter, 0); - let to_apub = pm.to_apub(&context).await.unwrap(); + let pm_id = pm.id; + let to_apub = pm.into_apub(&context).await.unwrap(); assert_json_include!(actual: json, expected: to_apub); - PrivateMessage::delete(&*context.pool().get().unwrap(), pm.id).unwrap(); + PrivateMessage::delete(&*context.pool().get().unwrap(), pm_id).unwrap(); cleanup(data, &context); } @@ -198,11 +224,14 @@ mod tests { let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2").unwrap(); let json = file_to_json_object("assets/pleroma/objects/chat_message.json"); let mut request_counter = 0; - let pm = ApubPrivateMessage::from_apub(&json, &context, &pleroma_url, &mut request_counter) + ApubPrivateMessage::verify(&json, &pleroma_url, &context, &mut request_counter) + .await + .unwrap(); + let pm = ApubPrivateMessage::from_apub(json, &context, &mut request_counter) .await .unwrap(); - assert_eq!(pm.ap_id.clone().into_inner(), pleroma_url); + assert_eq!(pm.ap_id, pleroma_url.into()); assert_eq!(pm.content.len(), 3); assert_eq!(request_counter, 0); diff --git a/crates/apub/src/protocol/activities/community/add_mod.rs b/crates/apub/src/protocol/activities/community/add_mod.rs index 74ec46457..75571bea8 100644 --- a/crates/apub/src/protocol/activities/community/add_mod.rs +++ b/crates/apub/src/protocol/activities/community/add_mod.rs @@ -1,10 +1,10 @@ -use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson}; +use crate::objects::person::ApubPerson; use activitystreams::{activity::kind::AddType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AddMod { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/announce.rs b/crates/apub/src/protocol/activities/community/announce.rs index 2f4e9bd26..2dd094041 100644 --- a/crates/apub/src/protocol/activities/community/announce.rs +++ b/crates/apub/src/protocol/activities/community/announce.rs @@ -1,14 +1,10 @@ -use crate::{ - activity_lists::AnnouncableActivities, - fetcher::object_id::ObjectId, - objects::community::ApubCommunity, -}; +use crate::{activity_lists::AnnouncableActivities, objects::community::ApubCommunity}; use activitystreams::{activity::kind::AnnounceType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AnnounceActivity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/block_user.rs b/crates/apub/src/protocol/activities/community/block_user.rs index 4ede06ae1..0b974f697 100644 --- a/crates/apub/src/protocol/activities/community/block_user.rs +++ b/crates/apub/src/protocol/activities/community/block_user.rs @@ -1,13 +1,10 @@ -use crate::{ - fetcher::object_id::ObjectId, - objects::{community::ApubCommunity, person::ApubPerson}, -}; +use crate::objects::{community::ApubCommunity, person::ApubPerson}; use activitystreams::{activity::kind::BlockType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BlockUserFromCommunity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/remove_mod.rs b/crates/apub/src/protocol/activities/community/remove_mod.rs index db30ddbe4..6e3e5458c 100644 --- a/crates/apub/src/protocol/activities/community/remove_mod.rs +++ b/crates/apub/src/protocol/activities/community/remove_mod.rs @@ -1,10 +1,10 @@ -use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson}; +use crate::objects::person::ApubPerson; use activitystreams::{activity::kind::RemoveType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RemoveMod { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/report.rs b/crates/apub/src/protocol/activities/community/report.rs index 5efdd792e..fbf21ec8d 100644 --- a/crates/apub/src/protocol/activities/community/report.rs +++ b/crates/apub/src/protocol/activities/community/report.rs @@ -1,13 +1,13 @@ use crate::{ - fetcher::{object_id::ObjectId, post_or_comment::PostOrComment}, + fetcher::post_or_comment::PostOrComment, objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{activity::kind::FlagType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Report { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/undo_block_user.rs b/crates/apub/src/protocol/activities/community/undo_block_user.rs index 0e89f87ef..538a7e25c 100644 --- a/crates/apub/src/protocol/activities/community/undo_block_user.rs +++ b/crates/apub/src/protocol/activities/community/undo_block_user.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::activities::community::block_user::BlockUserFromCommunity, }; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UndoBlockUserFromCommunity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/community/update.rs b/crates/apub/src/protocol/activities/community/update.rs index 4ba1ed843..9a831a1f8 100644 --- a/crates/apub/src/protocol/activities/community/update.rs +++ b/crates/apub/src/protocol/activities/community/update.rs @@ -1,22 +1,18 @@ -use crate::{ - fetcher::object_id::ObjectId, - objects::person::ApubPerson, - protocol::objects::group::Group, -}; +use crate::{objects::person::ApubPerson, protocol::objects::group::Group}; use activitystreams::{activity::kind::UpdateType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; /// This activity is received from a remote community mod, and updates the description or other /// fields of a local community. -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateCommunity { pub(crate) actor: ObjectId, pub(crate) to: Vec, // TODO: would be nice to use a separate struct here, which only contains the fields updated here - pub(crate) object: Group, + pub(crate) object: Box, pub(crate) cc: Vec, #[serde(rename = "type")] pub(crate) kind: UpdateType, diff --git a/crates/apub/src/protocol/activities/create_or_update/comment.rs b/crates/apub/src/protocol/activities/create_or_update/comment.rs index ede7417bc..6e90dedb0 100644 --- a/crates/apub/src/protocol/activities/create_or_update/comment.rs +++ b/crates/apub/src/protocol/activities/create_or_update/comment.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::{activities::CreateOrUpdateType, objects::note::Note}, }; use activitystreams::{link::Mention, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdateComment { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/create_or_update/post.rs b/crates/apub/src/protocol/activities/create_or_update/post.rs index 03b283e3c..e64e48408 100644 --- a/crates/apub/src/protocol/activities/create_or_update/post.rs +++ b/crates/apub/src/protocol/activities/create_or_update/post.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::{activities::CreateOrUpdateType, objects::page::Page}, }; use activitystreams::unparsed::Unparsed; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdatePost { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/deletion/delete.rs b/crates/apub/src/protocol/activities/deletion/delete.rs index f8e81b47a..7e2275153 100644 --- a/crates/apub/src/protocol/activities/deletion/delete.rs +++ b/crates/apub/src/protocol/activities/deletion/delete.rs @@ -1,12 +1,12 @@ -use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson}; +use crate::objects::person::ApubPerson; use activitystreams::{activity::kind::DeleteType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; #[skip_serializing_none] -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Delete { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/deletion/undo_delete.rs b/crates/apub/src/protocol/activities/deletion/undo_delete.rs index d962820b3..aa851cbc7 100644 --- a/crates/apub/src/protocol/activities/deletion/undo_delete.rs +++ b/crates/apub/src/protocol/activities/deletion/undo_delete.rs @@ -1,16 +1,10 @@ +use crate::{objects::person::ApubPerson, protocol::activities::deletion::delete::Delete}; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -use lemmy_apub_lib::traits::ActivityFields; - -use crate::{ - fetcher::object_id::ObjectId, - objects::person::ApubPerson, - protocol::activities::deletion::delete::Delete, -}; - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UndoDelete { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/following/accept.rs b/crates/apub/src/protocol/activities/following/accept.rs index 502a908c2..0c5988bc7 100644 --- a/crates/apub/src/protocol/activities/following/accept.rs +++ b/crates/apub/src/protocol/activities/following/accept.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::following::follow::FollowCommunity, }; use activitystreams::{activity::kind::AcceptType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AcceptFollowCommunity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/following/follow.rs b/crates/apub/src/protocol/activities/following/follow.rs index 9dfec2163..9d3f6a823 100644 --- a/crates/apub/src/protocol/activities/following/follow.rs +++ b/crates/apub/src/protocol/activities/following/follow.rs @@ -1,13 +1,10 @@ -use crate::{ - fetcher::object_id::ObjectId, - objects::{community::ApubCommunity, person::ApubPerson}, -}; +use crate::objects::{community::ApubCommunity, person::ApubPerson}; use activitystreams::{activity::kind::FollowType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct FollowCommunity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/following/undo_follow.rs b/crates/apub/src/protocol/activities/following/undo_follow.rs index be6a7ab89..5e9c58946 100644 --- a/crates/apub/src/protocol/activities/following/undo_follow.rs +++ b/crates/apub/src/protocol/activities/following/undo_follow.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::following::follow::FollowCommunity, }; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UndoFollowCommunity { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/private_message/create_or_update.rs b/crates/apub/src/protocol/activities/private_message/create_or_update.rs index 7632ef9fe..756bfd5e9 100644 --- a/crates/apub/src/protocol/activities/private_message/create_or_update.rs +++ b/crates/apub/src/protocol/activities/private_message/create_or_update.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::{activities::CreateOrUpdateType, objects::chat_message::ChatMessage}, }; use activitystreams::unparsed::Unparsed; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdatePrivateMessage { pub(crate) id: Url, diff --git a/crates/apub/src/protocol/activities/private_message/delete.rs b/crates/apub/src/protocol/activities/private_message/delete.rs index 499d7d1d6..f9ad29fde 100644 --- a/crates/apub/src/protocol/activities/private_message/delete.rs +++ b/crates/apub/src/protocol/activities/private_message/delete.rs @@ -1,13 +1,10 @@ -use crate::{ - fetcher::object_id::ObjectId, - objects::{person::ApubPerson, private_message::ApubPrivateMessage}, -}; +use crate::objects::{person::ApubPerson, private_message::ApubPrivateMessage}; use activitystreams::{activity::kind::DeleteType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeletePrivateMessage { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/private_message/undo_delete.rs b/crates/apub/src/protocol/activities/private_message/undo_delete.rs index 699f6eec9..951f14d4a 100644 --- a/crates/apub/src/protocol/activities/private_message/undo_delete.rs +++ b/crates/apub/src/protocol/activities/private_message/undo_delete.rs @@ -1,14 +1,13 @@ use crate::{ - fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::activities::private_message::delete::DeletePrivateMessage, }; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UndoDeletePrivateMessage { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/voting/undo_vote.rs b/crates/apub/src/protocol/activities/voting/undo_vote.rs index 0d3e66360..c6c8f0b03 100644 --- a/crates/apub/src/protocol/activities/voting/undo_vote.rs +++ b/crates/apub/src/protocol/activities/voting/undo_vote.rs @@ -1,16 +1,10 @@ +use crate::{objects::person::ApubPerson, protocol::activities::voting::vote::Vote}; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; -use lemmy_apub_lib::traits::ActivityFields; - -use crate::{ - fetcher::object_id::ObjectId, - objects::person::ApubPerson, - protocol::activities::voting::vote::Vote, -}; - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct UndoVote { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs index fdc87a3bd..977a549db 100644 --- a/crates/apub/src/protocol/activities/voting/vote.rs +++ b/crates/apub/src/protocol/activities/voting/vote.rs @@ -1,17 +1,14 @@ -use crate::{ - fetcher::{object_id::ObjectId, post_or_comment::PostOrComment}, - objects::person::ApubPerson, -}; +use crate::{fetcher::post_or_comment::PostOrComment, objects::person::ApubPerson}; use activitystreams::unparsed::Unparsed; use anyhow::anyhow; -use lemmy_apub_lib::traits::ActivityFields; +use lemmy_apub_lib::object_id::ObjectId; use lemmy_utils::LemmyError; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use strum_macros::ToString; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Vote { pub(crate) actor: ObjectId, diff --git a/crates/apub/src/protocol/collections/group_followers.rs b/crates/apub/src/protocol/collections/group_followers.rs index d3df5e22b..55b92a76c 100644 --- a/crates/apub/src/protocol/collections/group_followers.rs +++ b/crates/apub/src/protocol/collections/group_followers.rs @@ -29,7 +29,7 @@ impl GroupFollowers { .await??; Ok(GroupFollowers { - id: generate_followers_url(&community.actor_id)?.into_inner(), + id: generate_followers_url(&community.actor_id)?.into(), r#type: CollectionType::Collection, total_items: community_followers.len() as i32, items: vec![], diff --git a/crates/apub/src/protocol/collections/group_moderators.rs b/crates/apub/src/protocol/collections/group_moderators.rs index d37751a16..9b8494849 100644 --- a/crates/apub/src/protocol/collections/group_moderators.rs +++ b/crates/apub/src/protocol/collections/group_moderators.rs @@ -1,5 +1,6 @@ -use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson}; +use crate::objects::person::ApubPerson; use activitystreams::collection::kind::OrderedCollectionType; +use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use url::Url; diff --git a/crates/apub/src/protocol/collections/person_outbox.rs b/crates/apub/src/protocol/collections/person_outbox.rs index 6ec758403..c8af0173b 100644 --- a/crates/apub/src/protocol/collections/person_outbox.rs +++ b/crates/apub/src/protocol/collections/person_outbox.rs @@ -18,7 +18,7 @@ impl PersonOutbox { pub(crate) async fn new(user: Person) -> Result { Ok(PersonOutbox { r#type: OrderedCollectionType::OrderedCollection, - id: generate_outbox_url(&user.actor_id)?.into_inner(), + id: generate_outbox_url(&user.actor_id)?.into(), ordered_items: vec![], total_items: 0, }) diff --git a/crates/apub/src/protocol/objects/chat_message.rs b/crates/apub/src/protocol/objects/chat_message.rs index 038af4edf..b86929139 100644 --- a/crates/apub/src/protocol/objects/chat_message.rs +++ b/crates/apub/src/protocol/objects/chat_message.rs @@ -1,22 +1,21 @@ -use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::Source}; +use crate::{ + objects::{person::ApubPerson, private_message::ApubPrivateMessage}, + protocol::Source, +}; use activitystreams::{ chrono::{DateTime, FixedOffset}, unparsed::Unparsed, }; -use anyhow::anyhow; -use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; +use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use url::Url; #[skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChatMessage { pub(crate) r#type: ChatMessageType, - pub(crate) id: Url, + pub(crate) id: ObjectId, pub(crate) attributed_to: ObjectId, pub(crate) to: [ObjectId; 1], pub(crate) content: String, @@ -33,29 +32,3 @@ pub struct ChatMessage { pub enum ChatMessageType { ChatMessage, } - -impl ChatMessage { - pub(crate) fn id_unchecked(&self) -> &Url { - &self.id - } - pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { - verify_domains_match(&self.id, expected_domain)?; - Ok(&self.id) - } - - pub(crate) async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - verify_domains_match(self.attributed_to.inner(), &self.id)?; - let person = self - .attributed_to - .dereference(context, request_counter) - .await?; - if person.banned { - return Err(anyhow!("Person is banned from site").into()); - } - Ok(()) - } -} diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index 4da987a25..4dc1c5bd1 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -4,22 +4,18 @@ use crate::{ community_moderators::ApubCommunityModerators, community_outbox::ApubCommunityOutbox, }, - fetcher::object_id::ObjectId, - objects::get_summary_from_string_or_source, - protocol::{ImageObject, Source}, -}; -use activitystreams::{ - actor::{kind::GroupType, Endpoints}, - unparsed::Unparsed, + objects::{community::ApubCommunity, get_summary_from_string_or_source}, + protocol::{objects::Endpoints, ImageObject, Source}, }; +use activitystreams::{actor::kind::GroupType, unparsed::Unparsed}; use chrono::{DateTime, FixedOffset}; -use lemmy_apub_lib::{signatures::PublicKey, verify::verify_domains_match}; +use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey, verify::verify_domains_match}; use lemmy_db_schema::{naive_now, source::community::CommunityForm}; use lemmy_utils::{ - settings::structs::Settings, utils::{check_slurs, check_slurs_opt}, LemmyError, }; +use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -30,7 +26,7 @@ use url::Url; pub struct Group { #[serde(rename = "type")] pub(crate) kind: GroupType, - pub(crate) id: Url, + pub(crate) id: ObjectId, /// username, set at account creation and can never be changed pub(crate) preferred_username: String, /// title (can be changed at any time) @@ -47,7 +43,7 @@ pub struct Group { pub(crate) inbox: Url, pub(crate) outbox: ObjectId, pub(crate) followers: Url, - pub(crate) endpoints: Endpoints, + pub(crate) endpoints: Endpoints, pub(crate) public_key: PublicKey, pub(crate) published: Option>, pub(crate) updated: Option>, @@ -56,42 +52,42 @@ pub struct Group { } impl Group { - pub(crate) async fn from_apub_to_form( - group: &Group, + pub(crate) async fn verify( + &self, expected_domain: &Url, - settings: &Settings, - ) -> Result { - check_is_apub_id_valid(&group.id, true, settings)?; - verify_domains_match(expected_domain, &group.id)?; - let name = group.preferred_username.clone(); - let title = group.name.clone(); - let description = get_summary_from_string_or_source(&group.summary, &group.source); - let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into()); + context: &LemmyContext, + ) -> Result<(), LemmyError> { + check_is_apub_id_valid(self.id.inner(), true, &context.settings())?; + verify_domains_match(expected_domain, self.id.inner())?; - let slur_regex = &settings.slur_regex(); - check_slurs(&name, slur_regex)?; - check_slurs(&title, slur_regex)?; + let slur_regex = &context.settings().slur_regex(); + check_slurs(&self.preferred_username, slur_regex)?; + check_slurs(&self.name, slur_regex)?; + let description = get_summary_from_string_or_source(&self.summary, &self.source); check_slurs_opt(&description, slur_regex)?; + Ok(()) + } - Ok(CommunityForm { - name, - title, - description, + pub(crate) fn into_form(self) -> CommunityForm { + CommunityForm { + name: self.preferred_username, + title: self.name, + description: get_summary_from_string_or_source(&self.summary, &self.source), removed: None, - published: group.published.map(|u| u.naive_local()), - updated: group.updated.map(|u| u.naive_local()), + published: self.published.map(|u| u.naive_local()), + updated: self.updated.map(|u| u.naive_local()), deleted: None, - nsfw: Some(group.sensitive.unwrap_or(false)), - actor_id: Some(group.id.clone().into()), + nsfw: Some(self.sensitive.unwrap_or(false)), + actor_id: Some(self.id.into()), local: Some(false), private_key: None, - public_key: Some(group.public_key.public_key_pem.clone()), + public_key: Some(self.public_key.public_key_pem), last_refreshed_at: Some(naive_now()), - icon: Some(group.icon.clone().map(|i| i.url.into())), - banner: Some(group.image.clone().map(|i| i.url.into())), - followers_url: Some(group.followers.clone().into()), - inbox_url: Some(group.inbox.clone().into()), - shared_inbox_url: Some(shared_inbox), - }) + icon: Some(self.icon.map(|i| i.url.into())), + banner: Some(self.image.map(|i| i.url.into())), + followers_url: Some(self.followers.into()), + inbox_url: Some(self.inbox.into()), + shared_inbox_url: Some(self.endpoints.shared_inbox.map(|s| s.into())), + } } } diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index 529ee6370..bf53d5c99 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; +use url::Url; + pub(crate) mod chat_message; pub(crate) mod group; pub(crate) mod note; @@ -5,6 +8,13 @@ pub(crate) mod page; pub(crate) mod person; pub(crate) mod tombstone; +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Endpoints { + #[serde(skip_serializing_if = "Option::is_none")] + pub shared_inbox: Option, +} + #[cfg(test)] mod tests { use crate::protocol::{ diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index bdc4da66b..3ffc1123d 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -1,19 +1,13 @@ use crate::{ - activities::{verify_is_public, verify_person_in_community}, - fetcher::{object_id::ObjectId, post_or_comment::PostOrComment}, - objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, + fetcher::post_or_comment::PostOrComment, + objects::{comment::ApubComment, person::ApubPerson, post::ApubPost}, protocol::Source, }; use activitystreams::{object::kind::NoteType, unparsed::Unparsed}; -use anyhow::anyhow; use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match}; -use lemmy_db_schema::{ - newtypes::CommentId, - source::{community::Community, post::Post}, - traits::Crud, -}; +use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; +use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -26,12 +20,11 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct Note { pub(crate) r#type: NoteType, - pub(crate) id: Url, + pub(crate) id: ObjectId, pub(crate) attributed_to: ObjectId, - /// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain - /// the community ID, as it would be incompatible with Pleroma (and we can get the community from - /// the post in [`in_reply_to`]). pub(crate) to: Vec, + #[serde(default)] + pub(crate) cc: Vec, pub(crate) content: String, pub(crate) media_type: Option, pub(crate) source: SourceCompat, @@ -52,14 +45,6 @@ pub(crate) enum SourceCompat { } impl Note { - pub(crate) fn id_unchecked(&self) -> &Url { - &self.id - } - pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { - verify_domains_match(&self.id, expected_domain)?; - Ok(&self.id) - } - pub(crate) async fn get_parents( &self, context: &LemmyContext, @@ -87,26 +72,4 @@ impl Note { } } } - - pub(crate) async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?; - let community_id = post.community_id; - let community: ApubCommunity = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await?? - .into(); - - if post.locked { - return Err(anyhow!("Post is locked").into()); - } - verify_domains_match(self.attributed_to.inner(), &self.id)?; - verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?; - verify_is_public(&self.to)?; - Ok(()) - } } diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 7887f19c1..89fb11415 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -1,14 +1,12 @@ use crate::{ - activities::{verify_is_public, verify_person_in_community}, - fetcher::object_id::ObjectId, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, protocol::{ImageObject, Source}, }; use activitystreams::{object::kind::PageType, unparsed::Unparsed}; use anyhow::anyhow; use chrono::{DateTime, FixedOffset}; -use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match}; -use lemmy_utils::{utils::check_slurs, LemmyError}; +use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; +use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -19,9 +17,11 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct Page { pub(crate) r#type: PageType, - pub(crate) id: Url, + pub(crate) id: ObjectId, pub(crate) attributed_to: ObjectId, pub(crate) to: Vec, + #[serde(default)] + pub(crate) cc: Vec, pub(crate) name: String, pub(crate) content: Option, pub(crate) media_type: Option, @@ -38,14 +38,6 @@ pub struct Page { } impl Page { - pub(crate) fn id_unchecked(&self) -> &Url { - &self.id - } - pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { - verify_domains_match(&self.id, expected_domain)?; - Ok(&self.id) - } - /// 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. /// @@ -63,20 +55,6 @@ impl Page { Ok(is_mod_action) } - pub(crate) async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - let community = self.extract_community(context, request_counter).await?; - - check_slurs(&self.name, &context.settings().slur_regex())?; - verify_domains_match(self.attributed_to.inner(), &self.id.clone())?; - verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?; - verify_is_public(&self.to.clone())?; - Ok(()) - } - pub(crate) async fn extract_community( &self, context: &LemmyContext, diff --git a/crates/apub/src/protocol/objects/person.rs b/crates/apub/src/protocol/objects/person.rs index 2aecf945e..e45ea78c7 100644 --- a/crates/apub/src/protocol/objects/person.rs +++ b/crates/apub/src/protocol/objects/person.rs @@ -1,7 +1,10 @@ -use crate::protocol::{ImageObject, Source}; -use activitystreams::{actor::Endpoints, unparsed::Unparsed, url::Url}; +use crate::{ + objects::person::ApubPerson, + protocol::{objects::Endpoints, ImageObject, Source}, +}; +use activitystreams::{unparsed::Unparsed, url::Url}; use chrono::{DateTime, FixedOffset}; -use lemmy_apub_lib::signatures::PublicKey; +use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -17,7 +20,7 @@ pub enum UserTypes { pub struct Person { #[serde(rename = "type")] pub(crate) kind: UserTypes, - pub(crate) id: Url, + pub(crate) id: ObjectId, /// username, set at account creation and can never be changed pub(crate) preferred_username: String, /// displayname (can be changed at any time) @@ -32,7 +35,7 @@ pub struct Person { pub(crate) inbox: Url, /// mandatory field in activitypub, currently empty in lemmy pub(crate) outbox: Url, - pub(crate) endpoints: Endpoints, + pub(crate) endpoints: Endpoints, pub(crate) public_key: PublicKey, pub(crate) published: Option>, pub(crate) updated: Option>, diff --git a/crates/apub_lib/Cargo.toml b/crates/apub_lib/Cargo.toml index c3835a12e..f3ef989cf 100644 --- a/crates/apub_lib/Cargo.toml +++ b/crates/apub_lib/Cargo.toml @@ -27,3 +27,4 @@ actix-web = { version = "4.0.0-beta.9", default-features = false } http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["server", "sha-2"] } http-signature-normalization-reqwest = { version = "0.2.0", default-features = false, features = ["sha-2"] } background-jobs = "0.9.1" +diesel = "1.4.8" diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index a35f54415..c65baee53 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -3,6 +3,7 @@ extern crate lazy_static; pub mod activity_queue; pub mod data; +pub mod object_id; pub mod signatures; pub mod traits; pub mod values; diff --git a/crates/apub/src/fetcher/object_id.rs b/crates/apub_lib/src/object_id.rs similarity index 69% rename from crates/apub/src/fetcher/object_id.rs rename to crates/apub_lib/src/object_id.rs index 5c5c518f4..546430685 100644 --- a/crates/apub/src/fetcher/object_id.rs +++ b/crates/apub_lib/src/object_id.rs @@ -1,8 +1,7 @@ -use crate::fetcher::should_refetch_object; +use crate::{traits::ApubObject, APUB_JSON_CONTENT_TYPE}; +use activitystreams::chrono::{Duration as ChronoDuration, NaiveDateTime, Utc}; use anyhow::anyhow; use diesel::NotFound; -use lemmy_apub_lib::{traits::ApubObject, APUB_JSON_CONTENT_TYPE}; -use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::{ request::{build_user_agent, retry}, settings::structs::Settings, @@ -29,9 +28,10 @@ lazy_static! { .unwrap(); } +/// We store Url on the heap because it is quite large (88 bytes). #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] #[serde(transparent)] -pub struct ObjectId(Url, #[serde(skip)] PhantomData) +pub struct ObjectId(Box, #[serde(skip)] PhantomData) where Kind: ApubObject + Send + 'static, for<'de2> ::ApubType: serde::Deserialize<'de2>; @@ -45,7 +45,7 @@ where where T: Into, { - ObjectId(url.into(), PhantomData::) + ObjectId(Box::new(url.into()), PhantomData::) } pub fn inner(&self) -> &Url { @@ -104,7 +104,7 @@ where data: &::DataType, ) -> Result, LemmyError> { let id = self.0.clone(); - ApubObject::read_from_apub_id(id, data).await + ApubObject::read_from_apub_id(*id, data).await } async fn dereference_from_http( @@ -140,16 +140,39 @@ where let res2: Kind::ApubType = res.json().await?; - Ok(Kind::from_apub(&res2, data, self.inner(), request_counter).await?) + Kind::verify(&res2, self.inner(), data, request_counter).await?; + Ok(Kind::from_apub(res2, data, request_counter).await?) } } +static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60; +static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10; + +/// Determines when a remote actor should be refetched from its instance. In release builds, this is +/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds +/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`. +/// +/// TODO it won't pick up new avatars, summaries etc until a day after. +/// Actors need an "update" activity pushed to other servers to fix this. +fn should_refetch_object(last_refreshed: NaiveDateTime) -> bool { + let update_interval = if cfg!(debug_assertions) { + // avoid infinite loop when fetching community outbox + ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG) + } else { + ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS) + }; + let refresh_limit = Utc::now().naive_utc() - update_interval; + last_refreshed.lt(&refresh_limit) +} + impl Display for ObjectId where Kind: ApubObject + Send + 'static, for<'de2> ::ApubType: serde::Deserialize<'de2>, { + #[allow(clippy::to_string_in_display)] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // Use to_string here because Url.display is not useful for us write!(f, "{}", self.0.to_string()) } } @@ -160,16 +183,21 @@ where for<'de2> ::ApubType: serde::Deserialize<'de2>, { fn from(id: ObjectId) -> Self { - id.0 + *id.0 } } -impl From> for DbUrl -where - Kind: ApubObject + Send + 'static, - for<'de2> ::ApubType: serde::Deserialize<'de2>, -{ - fn from(id: ObjectId) -> Self { - id.0.into() +#[cfg(test)] +mod tests { + use super::*; + use crate::object_id::should_refetch_object; + + #[test] + fn test_should_refetch_object() { + let one_second_ago = Utc::now().naive_utc() - ChronoDuration::seconds(1); + assert!(!should_refetch_object(one_second_ago)); + + let two_days_ago = Utc::now().naive_utc() - ChronoDuration::days(2); + assert!(should_refetch_object(two_days_ago)); } } diff --git a/crates/apub_lib/src/signatures.rs b/crates/apub_lib/src/signatures.rs index df9590685..ccc720815 100644 --- a/crates/apub_lib/src/signatures.rs +++ b/crates/apub_lib/src/signatures.rs @@ -91,7 +91,7 @@ pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), L #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PublicKey { - pub id: String, - pub owner: Url, + pub(crate) id: String, + pub(crate) owner: Box, pub public_key_pem: String, } diff --git a/crates/apub_lib/src/traits.rs b/crates/apub_lib/src/traits.rs index 8d819a27b..c0cdb1afb 100644 --- a/crates/apub_lib/src/traits.rs +++ b/crates/apub_lib/src/traits.rs @@ -5,11 +5,6 @@ pub use lemmy_apub_lib_derive::*; use lemmy_utils::{location_info, LemmyError}; use url::Url; -pub trait ActivityFields { - fn id_unchecked(&self) -> &Url; - fn actor(&self) -> &Url; -} - #[async_trait::async_trait(?Send)] pub trait ActivityHandler { type DataType; @@ -46,9 +41,16 @@ pub trait ApubObject { async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError>; /// Trait for converting an object or actor into the respective ActivityPub type. - async fn to_apub(&self, data: &Self::DataType) -> Result; + async fn into_apub(self, data: &Self::DataType) -> Result; fn to_tombstone(&self) -> Result; + async fn verify( + apub: &Self::ApubType, + expected_domain: &Url, + data: &Self::DataType, + request_counter: &mut i32, + ) -> Result<(), LemmyError>; + /// Converts an object from ActivityPub type to Lemmy internal type. /// /// * `apub` The object to read from @@ -56,9 +58,8 @@ pub trait ApubObject { /// * `expected_domain` Domain where the object was received from. None in case of mod action. /// * `mod_action_allowed` True if the object can be a mod activity, ignore `expected_domain` in this case async fn from_apub( - apub: &Self::ApubType, + apub: Self::ApubType, data: &Self::DataType, - expected_domain: &Url, request_counter: &mut i32, ) -> Result where @@ -68,9 +69,7 @@ pub trait ApubObject { /// Common methods provided by ActivityPub actors (community and person). Not all methods are /// implemented by all actors. pub trait ActorType { - fn is_local(&self) -> bool; fn actor_id(&self) -> Url; - fn name(&self) -> String; // TODO: this should not be an option (needs db migration in lemmy) fn public_key(&self) -> Option; @@ -87,7 +86,7 @@ pub trait ActorType { fn get_public_key(&self) -> Result { Ok(PublicKey { id: format!("{}#main-key", self.actor_id()), - owner: self.actor_id(), + owner: Box::new(self.actor_id()), public_key_pem: self.public_key().context(location_info!())?, }) } diff --git a/crates/apub_lib_derive/src/lib.rs b/crates/apub_lib_derive/src/lib.rs index 1f8d12a7d..72e00fe2b 100644 --- a/crates/apub_lib_derive/src/lib.rs +++ b/crates/apub_lib_derive/src/lib.rs @@ -127,40 +127,3 @@ fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream) _ => unimplemented!(), } } - -#[proc_macro_derive(ActivityFields)] -pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let name = input.ident; - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let expanded = match input.data { - Data::Enum(e) => { - let variants = e.variants; - let impl_id = variants - .iter() - .map(|v| generate_match_arm(&name, v, "e! {a.id_unchecked()})); - let impl_actor = variants - .iter() - .map(|v| generate_match_arm(&name, v, "e! {a.actor()})); - quote! { - impl #impl_generics lemmy_apub_lib::traits::ActivityFields for #name #ty_generics #where_clause { - fn id_unchecked(&self) -> &url::Url { match self { #(#impl_id)* } } - fn actor(&self) -> &url::Url { match self { #(#impl_actor)* } } - } - } - } - Data::Struct(_) => { - quote! { - impl #impl_generics lemmy_apub_lib::traits::ActivityFields for #name #ty_generics #where_clause { - fn id_unchecked(&self) -> &url::Url { &self.id } - fn actor(&self) -> &url::Url { &self.actor.inner() } - } - } - } - _ => unimplemented!(), - }; - expanded.into() -} diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 21212835f..025f590d4 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -12,6 +12,7 @@ doctest = false [dependencies] lemmy_utils = { version = "=0.13.5-rc.7", path = "../utils" } +lemmy_apub_lib = { version = "=0.13.5-rc.7", path = "../apub_lib" } diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] } diesel_migrations = "1.4.0" chrono = { version = "0.4.19", features = ["serde"] } diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index 3042d35e7..9219d77f9 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -4,6 +4,7 @@ use diesel::{ serialize::{Output, ToSql}, sql_types::Text, }; +use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject}; use serde::{Deserialize, Serialize}; use std::{ fmt, @@ -93,27 +94,32 @@ where } } -impl DbUrl { - // TODO: remove this method and just use into() - pub fn into_inner(self) -> Url { - self.0 - } -} - impl Display for DbUrl { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.to_owned().0.fmt(f) } } -impl From for Url { - fn from(url: DbUrl) -> Self { - url.0 +// the project doesnt compile with From +#[allow(clippy::from_over_into)] +impl Into for Url { + fn into(self) -> DbUrl { + DbUrl(self) + } +} +#[allow(clippy::from_over_into)] +impl Into for DbUrl { + fn into(self) -> Url { + self.0 } } -impl From for DbUrl { - fn from(url: Url) -> Self { - DbUrl(url) +impl From> for DbUrl +where + Kind: ApubObject + Send + 'static, + for<'de2> ::ApubType: serde::Deserialize<'de2>, +{ + fn from(id: ObjectId) -> Self { + DbUrl(id.into()) } }