Merge pull request #1877 from LemmyNet/refactor-apub-2

Refactor apub 2
This commit is contained in:
Dessalines 2021-11-08 10:25:25 -05:00 committed by GitHub
commit 5d321949e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 1043 additions and 1017 deletions

2
Cargo.lock generated
View file

@ -1869,6 +1869,7 @@ dependencies = [
"async-trait", "async-trait",
"background-jobs", "background-jobs",
"base64 0.13.0", "base64 0.13.0",
"diesel",
"http", "http",
"http-signature-normalization-actix", "http-signature-normalization-actix",
"http-signature-normalization-reqwest", "http-signature-normalization-reqwest",
@ -1904,6 +1905,7 @@ dependencies = [
"diesel-derive-newtype", "diesel-derive-newtype",
"diesel_migrations", "diesel_migrations",
"lazy_static", "lazy_static",
"lemmy_apub_lib",
"lemmy_utils", "lemmy_utils",
"log", "log",
"regex", "regex",

View file

@ -1,5 +1,5 @@
use crate::Perform;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
@ -7,7 +7,8 @@ use lemmy_api_common::{
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
is_mod_or_admin, 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_schema::{source::comment_report::*, traits::Reportable};
use lemmy_db_views::{ use lemmy_db_views::{
comment_report_view::{CommentReportQueryBuilder, CommentReportView}, comment_report_view::{CommentReportQueryBuilder, CommentReportView},
@ -16,8 +17,6 @@ use lemmy_db_views::{
use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use crate::Perform;
/// Creates a comment report and notifies the moderators of the community /// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for CreateCommentReport { impl Perform for CreateCommentReport {

View file

@ -73,7 +73,7 @@ impl Perform for CreatePostLike {
.await??; .await??;
let community_id = post.community_id; 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 // Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); 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 // apub updates
CreateOrUpdatePost::send( CreateOrUpdatePost::send(
&updated_post, updated_post,
&local_user_view.person.clone().into(), &local_user_view.person.clone().into(),
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
context, context,
@ -242,7 +242,7 @@ impl Perform for StickyPost {
// Apub updates // Apub updates
// TODO stickied should pry work like locked for ease of use // TODO stickied should pry work like locked for ease of use
CreateOrUpdatePost::send( CreateOrUpdatePost::send(
&updated_post, updated_post,
&local_user_view.person.clone().into(), &local_user_view.person.clone().into(),
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
context, context,

View file

@ -1,5 +1,5 @@
use crate::Perform;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
@ -13,7 +13,8 @@ use lemmy_api_common::{
ResolvePostReport, 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::{ use lemmy_db_schema::{
source::post_report::{PostReport, PostReportForm}, source::post_report::{PostReport, PostReportForm},
traits::Reportable, traits::Reportable,
@ -25,8 +26,6 @@ use lemmy_db_views::{
use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use crate::Perform;
/// Creates a post report and notifies the moderators of the community /// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for CreatePostReport { impl Perform for CreatePostReport {

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
@ -13,6 +13,7 @@ use lemmy_api_common::{
use lemmy_apub::{ use lemmy_apub::{
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
generate_local_apub_endpoint, generate_local_apub_endpoint,
objects::comment::ApubComment,
protocol::activities::{ protocol::activities::{
create_or_update::comment::CreateOrUpdateComment, create_or_update::comment::CreateOrUpdateComment,
voting::vote::{Vote, VoteType}, voting::vote::{Vote, VoteType},
@ -40,8 +41,6 @@ use lemmy_websocket::{
UserOperationCrud, UserOperationCrud,
}; };
use crate::PerformCrud;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl PerformCrud for CreateComment { impl PerformCrud for CreateComment {
type Response = CommentResponse; type Response = CommentResponse;
@ -121,14 +120,6 @@ impl PerformCrud for CreateComment {
.await? .await?
.map_err(|e| ApiError::err("couldnt_create_comment", e))?; .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 // Scan the comment for user mentions, add those rows
let post_id = post.id; let post_id = post.id;
let mentions = scrape_text_for_mentions(&comment_form.content); let mentions = scrape_text_for_mentions(&comment_form.content);
@ -155,7 +146,15 @@ impl PerformCrud for CreateComment {
.await? .await?
.map_err(|e| ApiError::err("couldnt_like_comment", e))?; .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( Vote::send(
&object, &object,
&local_user_view.person.clone().into(), &local_user_view.person.clone().into(),

View file

@ -72,15 +72,6 @@ impl PerformCrud for EditComment {
.await? .await?
.map_err(|e| ApiError::err("couldnt_update_comment", e))?; .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 // Do the mentions / recipients
let updated_comment_content = updated_comment.content.to_owned(); let updated_comment_content = updated_comment.content.to_owned();
let mentions = scrape_text_for_mentions(&updated_comment_content); let mentions = scrape_text_for_mentions(&updated_comment_content);
@ -94,6 +85,15 @@ impl PerformCrud for EditComment {
) )
.await?; .await?;
// Send the apub update
CreateOrUpdateComment::send(
updated_comment.into(),
&local_user_view.person.into(),
CreateOrUpdateType::Update,
context,
)
.await?;
send_comment_ws_message( send_comment_ws_message(
data.comment_id, data.comment_id,
UserOperationCrud::EditComment, UserOperationCrud::EditComment,

View file

@ -7,7 +7,6 @@ use lemmy_api_common::{
is_admin, is_admin,
}; };
use lemmy_apub::{ use lemmy_apub::{
fetcher::object_id::ObjectId,
generate_followers_url, generate_followers_url,
generate_inbox_url, generate_inbox_url,
generate_local_apub_endpoint, generate_local_apub_endpoint,
@ -15,6 +14,7 @@ use lemmy_apub::{
objects::community::ApubCommunity, objects::community::ApubCommunity,
EndpointType, EndpointType,
}; };
use lemmy_apub_lib::object_id::ObjectId;
use lemmy_db_schema::{ use lemmy_db_schema::{
diesel_option_overwrite_to_url, diesel_option_overwrite_to_url,
source::{ source::{

View file

@ -1,12 +1,8 @@
use crate::PerformCrud; use crate::PerformCrud;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt}; use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt};
use lemmy_apub::{ use lemmy_apub::{get_actor_id_from_name, objects::community::ApubCommunity};
fetcher::object_id::ObjectId, use lemmy_apub_lib::{object_id::ObjectId, webfinger::WebfingerType};
get_actor_id_from_name,
objects::community::ApubCommunity,
};
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{ use lemmy_db_schema::{
from_opt_str_to_opt_enum, from_opt_str_to_opt_enum,
traits::DeleteableOrRemoveable, traits::DeleteableOrRemoveable,

View file

@ -72,7 +72,7 @@ impl PerformCrud for EditCommunity {
.map_err(|e| ApiError::err("couldnt_update_community", e))?; .map_err(|e| ApiError::err("couldnt_update_community", e))?;
UpdateCommunity::send( UpdateCommunity::send(
&updated_community.into(), updated_community.into(),
&local_user_view.person.into(), &local_user_view.person.into(),
context, context,
) )

View file

@ -1,7 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data; use actix_web::web::Data;
use log::warn;
use webmention::{Webmention, WebmentionError};
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
@ -14,6 +12,7 @@ use lemmy_api_common::{
use lemmy_apub::{ use lemmy_apub::{
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
generate_local_apub_endpoint, generate_local_apub_endpoint,
objects::post::ApubPost,
protocol::activities::{ protocol::activities::{
create_or_update::post::CreateOrUpdatePost, create_or_update::post::CreateOrUpdatePost,
voting::vote::{Vote, VoteType}, voting::vote::{Vote, VoteType},
@ -33,8 +32,9 @@ use lemmy_utils::{
LemmyError, LemmyError,
}; };
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
use log::warn;
use crate::PerformCrud; use url::Url;
use webmention::{Webmention, WebmentionError};
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl PerformCrud for CreatePost { impl PerformCrud for CreatePost {
@ -110,14 +110,6 @@ impl PerformCrud for CreatePost {
.await? .await?
.map_err(|e| ApiError::err("couldnt_create_post", e))?; .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 // They like their own post by default
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let post_id = inserted_post.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?; mark_post_as_read(person_id, post_id, context.pool()).await?;
if let Some(url) = &updated_post.url { if let Some(url) = &updated_post.url {
let mut webmention = Webmention::new( let mut webmention =
updated_post.ap_id.clone().into_inner(), Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
url.clone().into_inner(),
)?;
webmention.set_checked(true); webmention.set_checked(true);
match webmention.send().await { match webmention.send().await {
Ok(_) => {} 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( Vote::send(
&object, &object,
&local_user_view.person.clone().into(), &local_user_view.person.clone().into(),

View file

@ -109,7 +109,7 @@ impl PerformCrud for EditPost {
// Send apub update // Send apub update
CreateOrUpdatePost::send( CreateOrUpdatePost::send(
&updated_post.into(), updated_post.into(),
&local_user_view.person.clone().into(), &local_user_view.person.clone().into(),
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
context, context,

View file

@ -83,7 +83,7 @@ impl PerformCrud for CreatePrivateMessage {
.map_err(|e| ApiError::err("couldnt_create_private_message", e))?; .map_err(|e| ApiError::err("couldnt_create_private_message", e))?;
CreateOrUpdatePrivateMessage::send( CreateOrUpdatePrivateMessage::send(
&updated_private_message.into(), updated_private_message.into(),
&local_user_view.person.into(), &local_user_view.person.into(),
CreateOrUpdateType::Create, CreateOrUpdateType::Create,
context, context,

View file

@ -47,7 +47,7 @@ impl PerformCrud for EditPrivateMessage {
// Send the apub update // Send the apub update
CreateOrUpdatePrivateMessage::send( CreateOrUpdatePrivateMessage::send(
&updated_private_message.into(), updated_private_message.into(),
&local_user_view.person.into(), &local_user_view.person.into(),
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
context, context,

View file

@ -1,12 +1,8 @@
use crate::PerformCrud; use crate::PerformCrud;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*}; use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*};
use lemmy_apub::{ use lemmy_apub::{get_actor_id_from_name, objects::person::ApubPerson};
fetcher::object_id::ObjectId, use lemmy_apub_lib::{object_id::ObjectId, webfinger::WebfingerType};
get_actor_id_from_name,
objects::person::ApubPerson,
};
use lemmy_apub_lib::webfinger::WebfingerType;
use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType}; 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::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{

View file

@ -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"
]

View file

@ -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::{ use crate::{
activities::{ activities::{
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -24,14 +9,27 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
protocol::activities::{create_or_update::comment::CreateOrUpdateComment, CreateOrUpdateType}, 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 { impl CreateOrUpdateComment {
pub async fn send( pub async fn send(
comment: &ApubComment, comment: ApubComment,
actor: &ApubPerson, actor: &ApubPerson,
kind: CreateOrUpdateType, kind: CreateOrUpdateType,
context: &LemmyContext, context: &LemmyContext,
@ -50,12 +48,12 @@ impl CreateOrUpdateComment {
kind.clone(), kind.clone(),
&context.settings().get_protocol_and_hostname(), &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 { let create_or_update = CreateOrUpdateComment {
actor: ObjectId::new(actor.actor_id()), actor: ObjectId::new(actor.actor_id()),
to: vec![public()], to: vec![public()],
object: comment.to_apub(context).await?, object: comment.into_apub(context).await?,
cc: maa.ccs, cc: maa.ccs,
tag: maa.tags, tag: maa.tags,
kind, kind,
@ -77,19 +75,17 @@ impl ActivityHandler for CreateOrUpdateComment {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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 post = self.object.get_parents(context, request_counter).await?.0;
let community = self.get_community(context, request_counter).await?; 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_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_community_deleted_or_removed(&community)?;
check_post_deleted_or_removed(&post)?; check_post_deleted_or_removed(&post)?;
// TODO: should add a check that the correct community is in cc (probably needs changes to ApubComment::verify(&self.object, self.actor.inner(), context, request_counter).await?;
// comment deserialization)
self.object.verify(context, request_counter).await?;
Ok(()) Ok(())
} }
@ -98,8 +94,7 @@ impl ActivityHandler for CreateOrUpdateComment {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let comment = let comment = ApubComment::from_apub(self.object, context, request_counter).await?;
ApubComment::from_apub(&self.object, context, self.actor.inner(), request_counter).await?;
let recipients = get_notif_recipients(&self.actor, &comment, context, request_counter).await?; let recipients = get_notif_recipients(&self.actor, &comment, context, request_counter).await?;
let notif_type = match self.kind { let notif_type = match self.kind {
CreateOrUpdateType::Create => UserOperationCrud::CreateComment, CreateOrUpdateType::Create => UserOperationCrud::CreateComment,

View file

@ -1,7 +1,4 @@
use crate::{ use crate::objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson};
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
};
use activitystreams::{ use activitystreams::{
base::BaseExt, base::BaseExt,
link::{LinkExt, Mention}, link::{LinkExt, Mention},
@ -9,7 +6,7 @@ use activitystreams::{
use anyhow::anyhow; use anyhow::anyhow;
use itertools::Itertools; use itertools::Itertools;
use lemmy_api_common::blocking; 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::{ use lemmy_db_schema::{
newtypes::LocalUserId, newtypes::LocalUserId,
source::{comment::Comment, person::Person, post::Post}, source::{comment::Comment, person::Person, post::Post},

View file

@ -9,7 +9,6 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
generate_moderators_url, generate_moderators_url,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::add_mod::AddMod, protocol::activities::community::add_mod::AddMod,
@ -18,6 +17,7 @@ use activitystreams::{activity::kind::AddType, public};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -64,8 +64,8 @@ impl ActivityHandler for AddMod {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; verify_mod_action(&self.actor, &community, context, request_counter).await?;

View file

@ -1,8 +1,7 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public}, activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public},
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId, http::{is_activity_already_known, ActivityCommonFields},
http::is_activity_already_known,
insert_activity, insert_activity,
objects::community::ApubCommunity, objects::community::ApubCommunity,
protocol::activities::community::announce::AnnounceActivity, protocol::activities::community::announce::AnnounceActivity,
@ -10,7 +9,8 @@ use crate::{
use activitystreams::{activity::kind::AnnounceType, public}; use activitystreams::{activity::kind::AnnounceType, public};
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
traits::{ActivityFields, ActivityHandler, ActorType}, object_id::ObjectId,
traits::{ActivityHandler, ActorType},
}; };
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
@ -36,7 +36,7 @@ impl AnnounceActivity {
actor: ObjectId::new(community.actor_id()), actor: ObjectId::new(community.actor_id()),
to: vec![public()], to: vec![public()],
object, object,
cc: vec![community.followers_url.clone().into_inner()], cc: vec![community.followers_url.clone().into()],
kind: AnnounceType::Announce, kind: AnnounceType::Announce,
id: generate_activity_id( id: generate_activity_id(
&AnnounceType::Announce, &AnnounceType::Announce,
@ -59,8 +59,8 @@ impl ActivityHandler for AnnounceActivity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
self.object.verify(context, request_counter).await?; self.object.verify(context, request_counter).await?;
Ok(()) Ok(())
} }
@ -70,11 +70,15 @@ impl ActivityHandler for AnnounceActivity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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(()); return Ok(());
} }
insert_activity( insert_activity(
self.object.id_unchecked(), &object_data.id,
self.object.clone(), self.object.clone(),
false, false,
true, true,

View file

@ -8,7 +8,6 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::block_user::BlockUserFromCommunity, protocol::activities::community::block_user::BlockUserFromCommunity,
}; };
@ -16,6 +15,7 @@ use activitystreams::{activity::kind::BlockType, public};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -75,8 +75,8 @@ impl ActivityHandler for BlockUserFromCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; verify_mod_action(&self.actor, &community, context, request_counter).await?;

View file

@ -1,12 +1,11 @@
use crate::{ use crate::{
activities::send_lemmy_activity, activities::send_lemmy_activity,
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
insert_activity, insert_activity,
objects::community::ApubCommunity, objects::community::ApubCommunity,
protocol::activities::community::announce::AnnounceActivity, 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_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;

View file

@ -9,7 +9,6 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
generate_moderators_url, generate_moderators_url,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::remove_mod::RemoveMod, protocol::activities::community::remove_mod::RemoveMod,
@ -18,6 +17,7 @@ use activitystreams::{activity::kind::RemoveType, public};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -63,8 +63,8 @@ impl ActivityHandler for RemoveMod {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; verify_mod_action(&self.actor, &community, context, request_counter).await?;

View file

@ -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 activitystreams::activity::kind::FlagType;
use lemmy_api_common::{blocking, comment::CommentReportResponse, post::PostReportResponse}; use lemmy_api_common::{blocking, comment::CommentReportResponse, post::PostReportResponse};
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ 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_utils::LemmyError;
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; 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 { impl Report {
pub async fn send( pub async fn send(
object_id: ObjectId<PostOrComment>, object_id: ObjectId<PostOrComment>,
@ -72,7 +70,7 @@ impl ActivityHandler for Report {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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?; let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(()) Ok(())

View file

@ -8,7 +8,6 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::{ protocol::activities::community::{
block_user::BlockUserFromCommunity, block_user::BlockUserFromCommunity,
@ -19,6 +18,7 @@ use activitystreams::{activity::kind::UndoType, public};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -65,8 +65,8 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; verify_mod_action(&self.actor, &community, context, request_counter).await?;

View file

@ -8,14 +8,14 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, 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 activitystreams::{activity::kind::UpdateType, public};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType, ApubObject}, traits::{ActivityHandler, ActorType, ApubObject},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -27,7 +27,7 @@ use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperati
impl UpdateCommunity { impl UpdateCommunity {
pub async fn send( pub async fn send(
community: &ApubCommunity, community: ApubCommunity,
actor: &ApubPerson, actor: &ApubPerson,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
@ -38,15 +38,15 @@ impl UpdateCommunity {
let update = UpdateCommunity { let update = UpdateCommunity {
actor: ObjectId::new(actor.actor_id()), actor: ObjectId::new(actor.actor_id()),
to: vec![public()], to: vec![public()],
object: community.to_apub(context).await?, object: Box::new(community.clone().into_apub(context).await?),
cc: vec![community.actor_id()], cc: vec![community.actor_id()],
kind: UpdateType::Update, kind: UpdateType::Update,
id: id.clone(), id: id.clone(),
unparsed: Default::default(), unparsed: Default::default(),
}; };
let activity = AnnouncableActivities::UpdateCommunity(Box::new(update)); let activity = AnnouncableActivities::UpdateCommunity(update);
send_to_community(activity, &id, actor, community, vec![], context).await send_to_community(activity, &id, actor, &community, vec![], context).await
} }
} }
@ -58,11 +58,18 @@ impl ActivityHandler for UpdateCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; verify_mod_action(&self.actor, &community, context, request_counter).await?;
ApubCommunity::verify(
&self.object,
&community.actor_id.clone().into(),
context,
request_counter,
)
.await?;
Ok(()) Ok(())
} }
@ -73,12 +80,7 @@ impl ActivityHandler for UpdateCommunity {
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
let updated_community = Group::from_apub_to_form( let updated_community = self.object.into_form();
&self.object,
&community.actor_id.clone().into(),
&context.settings(),
)
.await?;
let cf = CommunityForm { let cf = CommunityForm {
name: updated_community.name, name: updated_community.name,
title: updated_community.title, title: updated_community.title,

View file

@ -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 activitystreams::{activity::kind::DeleteType, public};
use anyhow::anyhow; use anyhow::anyhow;
use url::Url;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -29,20 +40,7 @@ use lemmy_websocket::{
LemmyContext, LemmyContext,
UserOperationCrud, UserOperationCrud,
}; };
use url::Url;
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,
};
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ActivityHandler for Delete { impl ActivityHandler for Delete {
@ -52,12 +50,12 @@ impl ActivityHandler for Delete {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_delete_activity( verify_delete_activity(
&self.object, &self.object,
self, &self.actor,
&community, &community,
self.summary.is_some(), self.summary.is_some(),
context, context,

View file

@ -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_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
traits::{ActivityFields, ActorType, ApubObject}, object_id::ObjectId,
traits::{ActorType, ApubObject},
verify::verify_domains_match, verify::verify_domains_match,
}; };
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
@ -12,13 +16,7 @@ use lemmy_websocket::{
LemmyContext, LemmyContext,
UserOperationCrud, UserOperationCrud,
}; };
use url::Url;
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},
};
pub mod delete; pub mod delete;
pub mod undo_delete; pub mod undo_delete;
@ -55,9 +53,9 @@ pub async fn send_apub_remove(
} }
pub enum DeletableObjects { pub enum DeletableObjects {
Community(Box<ApubCommunity>), Community(ApubCommunity),
Comment(Box<ApubComment>), Comment(ApubComment),
Post(Box<ApubPost>), Post(ApubPost),
} }
impl DeletableObjects { impl DeletableObjects {
@ -66,13 +64,13 @@ impl DeletableObjects {
context: &LemmyContext, context: &LemmyContext,
) -> Result<DeletableObjects, LemmyError> { ) -> Result<DeletableObjects, LemmyError> {
if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? { 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? { 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? { 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()) Err(diesel::NotFound.into())
} }
@ -80,27 +78,26 @@ impl DeletableObjects {
pub(in crate::activities) async fn verify_delete_activity( pub(in crate::activities) async fn verify_delete_activity(
object: &Url, object: &Url,
activity: &dyn ActivityFields, actor: &ObjectId<ApubPerson>,
community: &ApubCommunity, community: &ApubCommunity,
is_mod_action: bool, is_mod_action: bool,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let object = DeletableObjects::read_from_db(object, context).await?; let object = DeletableObjects::read_from_db(object, context).await?;
let actor = ObjectId::new(activity.actor().clone());
match object { match object {
DeletableObjects::Community(community) => { DeletableObjects::Community(community) => {
if community.local { if community.local {
// can only do this check for local community, in remote case it would try to fetch the // can only do this check for local community, in remote case it would try to fetch the
// deleted community (which fails) // 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 // 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) => { DeletableObjects::Post(p) => {
verify_delete_activity_post_or_comment( verify_delete_activity_post_or_comment(
activity, actor,
&p.ap_id.clone().into(), &p.ap_id.clone().into(),
community, community,
is_mod_action, is_mod_action,
@ -111,7 +108,7 @@ pub(in crate::activities) async fn verify_delete_activity(
} }
DeletableObjects::Comment(c) => { DeletableObjects::Comment(c) => {
verify_delete_activity_post_or_comment( verify_delete_activity_post_or_comment(
activity, actor,
&c.ap_id.clone().into(), &c.ap_id.clone().into(),
community, community,
is_mod_action, is_mod_action,
@ -125,20 +122,19 @@ pub(in crate::activities) async fn verify_delete_activity(
} }
async fn verify_delete_activity_post_or_comment( async fn verify_delete_activity_post_or_comment(
activity: &dyn ActivityFields, actor: &ObjectId<ApubPerson>,
object_id: &Url, object_id: &Url,
community: &ApubCommunity, community: &ApubCommunity,
is_mod_action: bool, is_mod_action: bool,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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 { if is_mod_action {
verify_mod_action(&actor, community, context, request_counter).await?; verify_mod_action(actor, community, context, request_counter).await?;
} else { } else {
// domain of post ap_id and post.creator ap_id are identical, so we just check the former // 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(()) Ok(())
} }

View file

@ -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::{ use crate::{
activities::{ activities::{
community::{announce::GetCommunity, send_to_community}, community::{announce::GetCommunity, send_to_community},
@ -24,10 +7,25 @@ use crate::{
verify_is_public, verify_is_public,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete}, 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)] #[async_trait::async_trait(?Send)]
impl ActivityHandler for UndoDelete { impl ActivityHandler for UndoDelete {
@ -37,13 +35,13 @@ impl ActivityHandler for UndoDelete {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
self.object.verify(context, request_counter).await?; self.object.verify(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_delete_activity( verify_delete_activity(
&self.object.object, &self.object.object,
self, &self.actor,
&community, &community,
self.object.summary.is_some(), self.object.summary.is_some(),
context, context,

View file

@ -1,13 +1,13 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity}, activities::{generate_activity_id, send_lemmy_activity, verify_activity},
fetcher::object_id::ObjectId,
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity}, protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
}; };
use activitystreams::activity::kind::AcceptType; use activitystreams::activity::kind::AcceptType;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
traits::{ActivityFields, ActivityHandler, ActorType}, object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::verify_urls_match, verify::verify_urls_match,
}; };
use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable}; use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable};
@ -51,9 +51,9 @@ impl ActivityHandler for AcceptFollowCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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.actor())?; verify_urls_match(self.to[0].inner(), self.object.actor.inner())?;
verify_urls_match(self.actor(), self.object.to[0].inner())?; verify_urls_match(self.actor.inner(), self.object.to[0].inner())?;
self.object.verify(context, request_counter).await?; self.object.verify(context, request_counter).await?;
Ok(()) Ok(())
} }

View file

@ -6,7 +6,6 @@ use crate::{
verify_person, verify_person,
verify_person_in_community, verify_person_in_community,
}, },
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity}, protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
}; };
@ -14,6 +13,7 @@ use activitystreams::activity::kind::FollowType;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
verify::verify_urls_match, verify::verify_urls_match,
}; };
@ -71,7 +71,7 @@ impl ActivityHandler for FollowCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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_urls_match(self.to[0].inner(), self.object.inner())?;
verify_person(&self.actor, context, request_counter).await?; verify_person(&self.actor, context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?; let community = self.to[0].dereference(context, request_counter).await?;

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity}, protocol::activities::following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity},
}; };
@ -8,7 +7,8 @@ use activitystreams::activity::kind::UndoType;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
traits::{ActivityFields, ActivityHandler, ActorType}, object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::verify_urls_match, verify::verify_urls_match,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -49,9 +49,9 @@ impl ActivityHandler for UndoFollowCommunity {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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.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?; verify_person(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?; self.object.verify(context, request_counter).await?;
Ok(()) Ok(())

View file

@ -1,7 +1,6 @@
use crate::{ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
context::WithContext, context::WithContext,
fetcher::object_id::ObjectId,
generate_moderators_url, generate_moderators_url,
insert_activity, insert_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
@ -11,7 +10,8 @@ use anyhow::anyhow;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
activity_queue::send_activity, activity_queue::send_activity,
traits::{ActivityFields, ActorType}, object_id::ObjectId,
traits::ActorType,
verify::verify_domains_match, verify::verify_domains_match,
}; };
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
@ -71,9 +71,9 @@ pub(crate) async fn verify_person_in_community(
Ok(()) Ok(())
} }
fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result<(), LemmyError> { fn verify_activity(id: &Url, actor: &Url, settings: &Settings) -> Result<(), LemmyError> {
check_is_apub_id_valid(activity.actor(), false, settings)?; check_is_apub_id_valid(actor, false, settings)?;
verify_domains_match(activity.id_unchecked(), activity.actor())?; verify_domains_match(id, actor)?;
Ok(()) Ok(())
} }
@ -110,14 +110,14 @@ fn verify_add_remove_moderator_target(
target: &Url, target: &Url,
community: &ApubCommunity, community: &ApubCommunity,
) -> Result<(), LemmyError> { ) -> 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()); return Err(anyhow!("Unkown target url").into());
} }
Ok(()) Ok(())
} }
pub(crate) fn verify_is_public(to: &[Url]) -> Result<(), LemmyError> { pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
if !to.contains(&public()) { if !to.contains(&public()) && !cc.contains(&public()) {
return Err(anyhow!("Object is not public").into()); return Err(anyhow!("Object is not public").into());
} }
Ok(()) Ok(())

View file

@ -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::{ use crate::{
activities::{ activities::{
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -22,14 +9,25 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType}, 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 { impl CreateOrUpdatePost {
pub(crate) async fn new( pub(crate) async fn new(
post: &ApubPost, post: ApubPost,
actor: &ApubPerson, actor: &ApubPerson,
community: &ApubCommunity, community: &ApubCommunity,
kind: CreateOrUpdateType, kind: CreateOrUpdateType,
@ -42,7 +40,7 @@ impl CreateOrUpdatePost {
Ok(CreateOrUpdatePost { Ok(CreateOrUpdatePost {
actor: ObjectId::new(actor.actor_id()), actor: ObjectId::new(actor.actor_id()),
to: vec![public()], to: vec![public()],
object: post.to_apub(context).await?, object: post.into_apub(context).await?,
cc: vec![community.actor_id()], cc: vec![community.actor_id()],
kind, kind,
id: id.clone(), id: id.clone(),
@ -50,7 +48,7 @@ impl CreateOrUpdatePost {
}) })
} }
pub async fn send( pub async fn send(
post: &ApubPost, post: ApubPost,
actor: &ApubPerson, actor: &ApubPerson,
kind: CreateOrUpdateType, kind: CreateOrUpdateType,
context: &LemmyContext, context: &LemmyContext,
@ -63,7 +61,7 @@ impl CreateOrUpdatePost {
.into(); .into();
let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?; let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
let id = create_or_update.id.clone(); 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 send_to_community(activity, &id, actor, &community, vec![], context).await
} }
} }
@ -76,16 +74,16 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?;
check_community_deleted_or_removed(&community)?; check_community_deleted_or_removed(&community)?;
match self.kind { match self.kind {
CreateOrUpdateType::Create => { CreateOrUpdateType::Create => {
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_urls_match(self.actor(), self.object.attributed_to.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. // 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 // 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, // 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 { if is_mod_action {
verify_mod_action(&self.actor, &community, context, request_counter).await?; verify_mod_action(&self.actor, &community, context, request_counter).await?;
} else { } else {
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_urls_match(self.actor(), self.object.attributed_to.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(()) Ok(())
} }
@ -115,9 +113,7 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context, request_counter).await?; let post = ApubPost::from_apub(self.object, context, request_counter).await?;
let post =
ApubPost::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
let notif_type = match self.kind { let notif_type = match self.kind {
CreateOrUpdateType::Create => UserOperationCrud::CreatePost, CreateOrUpdateType::Create => UserOperationCrud::CreatePost,

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage}, objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{ protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage, private_message::create_or_update::CreateOrUpdatePrivateMessage,
@ -10,6 +9,7 @@ use crate::{
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType, ApubObject}, traits::{ActivityHandler, ActorType, ApubObject},
verify::verify_domains_match, verify::verify_domains_match,
}; };
@ -19,7 +19,7 @@ use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}
impl CreateOrUpdatePrivateMessage { impl CreateOrUpdatePrivateMessage {
pub async fn send( pub async fn send(
private_message: &ApubPrivateMessage, private_message: ApubPrivateMessage,
actor: &ApubPerson, actor: &ApubPerson,
kind: CreateOrUpdateType, kind: CreateOrUpdateType,
context: &LemmyContext, context: &LemmyContext,
@ -38,7 +38,7 @@ impl CreateOrUpdatePrivateMessage {
id: id.clone(), id: id.clone(),
actor: ObjectId::new(actor.actor_id()), actor: ObjectId::new(actor.actor_id()),
to: [ObjectId::new(recipient.actor_id())], to: [ObjectId::new(recipient.actor_id())],
object: private_message.to_apub(context).await?, object: private_message.into_apub(context).await?,
kind, kind,
unparsed: Default::default(), unparsed: Default::default(),
}; };
@ -54,10 +54,10 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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_person(&self.actor, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;
self.object.verify(context, request_counter).await?; ApubPrivateMessage::verify(&self.object, self.actor.inner(), context, request_counter).await?;
Ok(()) Ok(())
} }
@ -67,8 +67,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let private_message = let private_message =
ApubPrivateMessage::from_apub(&self.object, context, self.actor.inner(), request_counter) ApubPrivateMessage::from_apub(self.object, context, request_counter).await?;
.await?;
let notif_type = match self.kind { let notif_type = match self.kind {
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage, CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage}, objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::private_message::delete::DeletePrivateMessage, protocol::activities::private_message::delete::DeletePrivateMessage,
}; };
@ -8,6 +7,7 @@ use activitystreams::activity::kind::DeleteType;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType}, traits::{ActivityHandler, ActorType},
verify::verify_domains_match, verify::verify_domains_match,
}; };
@ -62,7 +62,7 @@ impl ActivityHandler for DeletePrivateMessage {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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_person(&self.actor, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.inner())?; verify_domains_match(self.actor.inner(), self.object.inner())?;
Ok(()) Ok(())

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage}, objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::private_message::{ protocol::activities::private_message::{
delete::DeletePrivateMessage, delete::DeletePrivateMessage,
@ -11,7 +10,8 @@ use activitystreams::activity::kind::UndoType;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
traits::{ActivityFields, ActivityHandler, ActorType}, object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::{verify_domains_match, verify_urls_match}, verify::{verify_domains_match, verify_urls_match},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -59,10 +59,10 @@ impl ActivityHandler for UndoDeletePrivateMessage {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> 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_person(&self.actor, context, request_counter).await?;
verify_urls_match(self.actor(), self.object.actor())?; verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
verify_domains_match(self.actor(), self.object.object.inner())?; verify_domains_match(self.actor.inner(), self.object.object.inner())?;
self.object.verify(context, request_counter).await?; self.object.verify(context, request_counter).await?;
Ok(()) Ok(())
} }

View file

@ -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::{ use crate::{
activities::{ activities::{
community::{announce::GetCommunity, send_to_community}, community::{announce::GetCommunity, send_to_community},
@ -22,7 +8,6 @@ use crate::{
voting::{undo_vote_comment, undo_vote_post}, voting::{undo_vote_comment, undo_vote_post},
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::{ protocol::activities::voting::{
undo_vote::UndoVote, undo_vote::UndoVote,
@ -30,6 +15,17 @@ use crate::{
}, },
PostOrComment, 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 { impl UndoVote {
pub async fn send( pub async fn send(
@ -72,11 +68,11 @@ impl ActivityHandler for UndoVote {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &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?; self.object.verify(context, request_counter).await?;
Ok(()) Ok(())
} }
@ -93,7 +89,7 @@ impl ActivityHandler for UndoVote {
.dereference(context, request_counter) .dereference(context, request_counter)
.await?; .await?;
match object { 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, PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
} }
} }

View file

@ -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::{ use crate::{
activities::{ activities::{
community::{announce::GetCommunity, send_to_community}, community::{announce::GetCommunity, send_to_community},
@ -25,11 +8,24 @@ use crate::{
voting::{vote_comment, vote_post}, voting::{vote_comment, vote_post},
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::vote::{Vote, VoteType}, protocol::activities::voting::vote::{Vote, VoteType},
PostOrComment, 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 { impl Vote {
pub(in crate::activities::voting) fn new( pub(in crate::activities::voting) fn new(
@ -78,8 +74,8 @@ impl ActivityHandler for Vote {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
verify_is_public(&self.to)?; verify_is_public(&self.to, &self.cc)?;
verify_activity(self, &context.settings())?; verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?; let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?; verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(()) Ok(())
@ -93,7 +89,7 @@ impl ActivityHandler for Vote {
let actor = self.actor.dereference(context, request_counter).await?; let actor = self.actor.dereference(context, request_counter).await?;
let object = self.object.dereference(context, request_counter).await?; let object = self.object.dereference(context, request_counter).await?;
match object { 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, PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
} }
} }

View file

@ -26,32 +26,32 @@ use crate::{
voting::{undo_vote::UndoVote, vote::Vote}, 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_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)] #[serde(untagged)]
#[activity_handler(LemmyContext)] #[activity_handler(LemmyContext)]
pub enum SharedInboxActivities { pub enum SharedInboxActivities {
GroupInboxActivities(GroupInboxActivities), GroupInboxActivities(GroupInboxActivities),
// Note, pm activities need to be at the end, otherwise comments will end up here. We can probably // 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. // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
PersonInboxActivities(PersonInboxActivities), PersonInboxActivities(Box<PersonInboxActivities>),
} }
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)] #[serde(untagged)]
#[activity_handler(LemmyContext)] #[activity_handler(LemmyContext)]
pub enum GroupInboxActivities { pub enum GroupInboxActivities {
FollowCommunity(FollowCommunity), FollowCommunity(FollowCommunity),
UndoFollowCommunity(UndoFollowCommunity), UndoFollowCommunity(UndoFollowCommunity),
AnnouncableActivities(AnnouncableActivities), AnnouncableActivities(Box<AnnouncableActivities>),
Report(Report), Report(Report),
} }
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)] #[serde(untagged)]
#[activity_handler(LemmyContext)] #[activity_handler(LemmyContext)]
pub enum PersonInboxActivities { pub enum PersonInboxActivities {
@ -61,20 +61,20 @@ pub enum PersonInboxActivities {
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage), CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage), DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage), UndoDeletePrivateMessage(UndoDeletePrivateMessage),
AnnounceActivity(Box<AnnounceActivity>), AnnounceActivity(AnnounceActivity),
} }
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)] #[serde(untagged)]
#[activity_handler(LemmyContext)] #[activity_handler(LemmyContext)]
pub enum AnnouncableActivities { pub enum AnnouncableActivities {
CreateOrUpdateComment(CreateOrUpdateComment), CreateOrUpdateComment(CreateOrUpdateComment),
CreateOrUpdatePost(Box<CreateOrUpdatePost>), CreateOrUpdatePost(CreateOrUpdatePost),
Vote(Vote), Vote(Vote),
UndoVote(UndoVote), UndoVote(UndoVote),
Delete(Delete), Delete(Delete),
UndoDelete(UndoDelete), UndoDelete(UndoDelete),
UpdateCommunity(Box<UpdateCommunity>), UpdateCommunity(UpdateCommunity),
BlockUserFromCommunity(BlockUserFromCommunity), BlockUserFromCommunity(BlockUserFromCommunity),
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
AddMod(AddMod), AddMod(AddMod),

View file

@ -1,13 +1,12 @@
use crate::{ use crate::{
collections::CommunityContext, collections::CommunityContext,
fetcher::object_id::ObjectId,
generate_moderators_url, generate_moderators_url,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::collections::group_moderators::GroupModerators, protocol::collections::group_moderators::GroupModerators,
}; };
use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType}; use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType};
use lemmy_api_common::blocking; 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::{ use lemmy_db_schema::{
source::community::{CommunityModerator, CommunityModeratorForm}, source::community::{CommunityModerator, CommunityModeratorForm},
traits::Joinable, traits::Joinable,
@ -50,11 +49,11 @@ impl ApubObject for ApubCommunityModerators {
unimplemented!() unimplemented!()
} }
async fn to_apub(&self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> { async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
let ordered_items = self let ordered_items = self
.0 .0
.iter() .into_iter()
.map(|m| ObjectId::<ApubPerson>::new(m.moderator.actor_id.clone().into_inner())) .map(|m| ObjectId::<ApubPerson>::new(m.moderator.actor_id))
.collect(); .collect();
Ok(GroupModerators { Ok(GroupModerators {
r#type: OrderedCollectionType::OrderedCollection, r#type: OrderedCollectionType::OrderedCollection,
@ -67,13 +66,21 @@ impl ApubObject for ApubCommunityModerators {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
apub: &Self::ApubType, group_moderators: &GroupModerators,
data: &Self::DataType,
expected_domain: &Url, 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, request_counter: &mut i32,
) -> Result<Self, LemmyError> { ) -> Result<Self, LemmyError> {
verify_domains_match(expected_domain, &apub.id)?;
let community_id = data.0.id; let community_id = data.0.id;
let current_moderators = blocking(data.1.pool(), move |conn| { let current_moderators = blocking(data.1.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id) CommunityModeratorView::for_community(conn, community_id)
@ -81,7 +88,7 @@ impl ApubObject for ApubCommunityModerators {
.await??; .await??;
// Remove old mods from database which arent in the moderators collection anymore // Remove old mods from database which arent in the moderators collection anymore
for mod_user in &current_moderators { for mod_user in &current_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) { if !apub.ordered_items.contains(&mod_id) {
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: mod_user.community.id, 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 // Add new mods to database which have been added to moderators collection
for mod_id in &apub.ordered_items { for mod_id in apub.ordered_items {
let mod_id = ObjectId::new(mod_id.clone()); let mod_id = ObjectId::new(mod_id);
let mod_user: ApubPerson = mod_id.dereference(&data.1, request_counter).await?; let mod_user: ApubPerson = mod_id.dereference(&data.1, request_counter).await?;
if !current_moderators if !current_moderators
.clone()
.iter() .iter()
.map(|c| c.moderator.actor_id.clone()) .map(|c| c.moderator.actor_id.clone())
.any(|x| x == mod_user.actor_id) .any(|x| x == mod_user.actor_id)
@ -167,7 +173,10 @@ mod tests {
0: community, 0: community,
1: context, 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 .await
.unwrap(); .unwrap();
assert_eq!(request_counter, 0); assert_eq!(request_counter, 0);

View file

@ -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 activitystreams::collection::kind::OrderedCollectionType;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use url::Url;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
@ -13,16 +20,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use url::Url;
use crate::{
collections::CommunityContext,
generate_outbox_url,
objects::{person::ApubPerson, post::ApubPost},
protocol::{
activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
collections::group_outbox::GroupOutbox,
},
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>); pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>);
@ -62,9 +60,9 @@ impl ApubObject for ApubCommunityOutbox {
Ok(()) Ok(())
} }
async fn to_apub(&self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> { async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
let mut ordered_items = vec![]; let mut ordered_items = vec![];
for post in &self.0 { for post in self.0 {
let actor = post.creator_id; let actor = post.creator_id;
let actor: ApubPerson = blocking(data.1.pool(), move |conn| Person::read(conn, actor)) let actor: ApubPerson = blocking(data.1.pool(), move |conn| Person::read(conn, actor))
.await?? .await??
@ -87,14 +85,22 @@ impl ApubObject for ApubCommunityOutbox {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
apub: &Self::ApubType, group_outbox: &GroupOutbox,
data: &Self::DataType,
expected_domain: &Url, 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, request_counter: &mut i32,
) -> Result<Self, LemmyError> { ) -> Result<Self, LemmyError> {
verify_domains_match(expected_domain, &apub.id)?; let mut outbox_activities = apub.ordered_items;
let mut outbox_activities = apub.ordered_items.clone();
if outbox_activities.len() > 20 { if outbox_activities.len() > 20 {
outbox_activities = outbox_activities[0..20].to_vec(); outbox_activities = outbox_activities[0..20].to_vec();
} }

View file

@ -1,33 +1,9 @@
use activitystreams::{base::AnyBase, context, primitives::OneOrMany}; use activitystreams::{base::AnyBase, primitives::OneOrMany};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json;
use url::Url;
lazy_static! { lazy_static! {
static ref CONTEXT: OneOrMany<AnyBase> = { static ref CONTEXT: OneOrMany<AnyBase> =
let context_ext = AnyBase::from_arbitrary_json(json!( serde_json::from_str(include_str!("../assets/lemmy/context.json")).expect("parse context");
{
"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")),
])
};
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]

View file

@ -1,56 +1,3 @@
pub mod object_id;
pub mod post_or_comment; pub mod post_or_comment;
pub mod search; pub mod search;
pub mod user_or_community;
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<Box<dyn ActorType>, LemmyError> {
let community_id = ObjectId::<ApubCommunity>::new(apub_id.clone());
let community = community_id.dereference(context, recursion_counter).await;
let actor: Box<dyn ActorType> = 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))
}

View file

@ -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::{ use crate::{
objects::{comment::ApubComment, post::ApubPost}, objects::{comment::ApubComment, post::ApubPost},
protocol::objects::{note::Note, page::Page}, 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)] #[derive(Clone, Debug)]
pub enum PostOrComment { pub enum PostOrComment {
Post(Box<ApubPost>), Post(ApubPost),
Comment(ApubComment), Comment(ApubComment),
} }
pub enum PostOrCommentForm {
PostForm(Box<PostForm>),
CommentForm(CommentForm),
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum PageOrNote { pub enum PageOrNote {
Page(Box<Page>), Page(Page),
Note(Box<Note>), Note(Note),
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -44,13 +36,10 @@ impl ApubObject for PostOrComment {
async fn read_from_apub_id( async fn read_from_apub_id(
object_id: Url, object_id: Url,
data: &Self::DataType, data: &Self::DataType,
) -> Result<Option<Self>, LemmyError> ) -> Result<Option<Self>, LemmyError> {
where
Self: Sized,
{
let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?; let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?;
Ok(match post { 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) None => ApubComment::read_from_apub_id(object_id, data)
.await? .await?
.map(PostOrComment::Comment), .map(PostOrComment::Comment),
@ -64,7 +53,7 @@ impl ApubObject for PostOrComment {
} }
} }
async fn to_apub(&self, _data: &Self::DataType) -> Result<Self::ApubType, LemmyError> { async fn into_apub(self, _data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
unimplemented!() unimplemented!()
} }
@ -72,22 +61,30 @@ impl ApubObject for PostOrComment {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
apub: &PageOrNote, apub: &Self::ApubType,
context: &LemmyContext,
expected_domain: &Url, expected_domain: &Url,
data: &Self::DataType,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<Self, LemmyError> ) -> Result<(), LemmyError> {
where match apub {
Self: Sized, 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<Self, LemmyError> {
Ok(match apub { Ok(match apub {
PageOrNote::Page(p) => PostOrComment::Post(Box::new( PageOrNote::Page(p) => {
ApubPost::from_apub(p, context, expected_domain, request_counter).await?, PostOrComment::Post(ApubPost::from_apub(p, context, request_counter).await?)
)), }
PageOrNote::Note(n) => PostOrComment::Comment( PageOrNote::Note(n) => {
ApubComment::from_apub(n, context, expected_domain, request_counter).await?, PostOrComment::Comment(ApubComment::from_apub(n, context, request_counter).await?)
), }
}) })
} }
} }

View file

@ -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 anyhow::anyhow;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use itertools::Itertools; use itertools::Itertools;
use serde::Deserialize;
use url::Url;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::ApubObject, traits::ApubObject,
webfinger::{webfinger_resolve_actor, WebfingerType}, webfinger::{webfinger_resolve_actor, WebfingerType},
}; };
@ -15,12 +17,8 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use crate::{ use url::Url;
fetcher::object_id::ObjectId,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::objects::{group::Group, note::Note, page::Page, person::Person},
};
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// 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<Self::ApubType, LemmyError> { async fn into_apub(self, _data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
unimplemented!() unimplemented!()
} }
@ -165,19 +163,40 @@ impl ApubObject for SearchableObjects {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
apub: &Self::ApubType, 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, context: &LemmyContext,
ed: &Url,
rc: &mut i32, rc: &mut i32,
) -> Result<Self, LemmyError> { ) -> Result<Self, LemmyError> {
use SearchableApubTypes as SAT; use SearchableApubTypes as SAT;
use SearchableObjects as SO; use SearchableObjects as SO;
Ok(match apub { Ok(match apub {
SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, 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, ed, rc).await?), SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, rc).await?),
SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, ed, rc).await?), SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, rc).await?),
SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, ed, rc).await?), SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, rc).await?),
}) })
} }
} }

View file

@ -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<NaiveDateTime> {
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<Option<Self>, 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<Self::ApubType, LemmyError> {
unimplemented!()
}
fn to_tombstone(&self) -> Result<Self::TombstoneType, LemmyError> {
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<Self, LemmyError> {
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<String> {
match self {
UserOrCommunity::User(p) => p.public_key(),
UserOrCommunity::Community(p) => p.public_key(),
}
}
fn private_key(&self) -> Option<String> {
todo!()
}
fn inbox_url(&self) -> Url {
todo!()
}
fn shared_inbox_url(&self) -> Option<Url> {
todo!()
}
}

View file

@ -30,7 +30,7 @@ pub(crate) async fn get_apub_comment(
} }
if !comment.deleted { if !comment.deleted {
Ok(create_apub_response(&comment.to_apub(&**context).await?)) Ok(create_apub_response(&comment.into_apub(&**context).await?))
} else { } else {
Ok(create_apub_tombstone_response(&comment.to_tombstone()?)) Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
} }

View file

@ -7,13 +7,13 @@ use crate::{
CommunityContext, CommunityContext,
}, },
context::WithContext, context::WithContext,
fetcher::object_id::ObjectId,
generate_outbox_url, generate_outbox_url,
http::{ http::{
create_apub_response, create_apub_response,
create_apub_tombstone_response, create_apub_tombstone_response,
payload_to_string, payload_to_string,
receive_activity, receive_activity,
ActivityCommonFields,
}, },
objects::community::ApubCommunity, objects::community::ApubCommunity,
protocol::{ protocol::{
@ -23,7 +23,7 @@ use crate::{
}; };
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking; 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_db_schema::source::community::Community;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
@ -47,7 +47,7 @@ pub(crate) async fn get_apub_community_http(
.into(); .into();
if !community.deleted { if !community.deleted {
let apub = community.to_apub(&**context).await?; let apub = community.into_apub(&**context).await?;
Ok(create_apub_response(&apub)) Ok(create_apub_response(&apub))
} else { } else {
@ -64,26 +64,28 @@ pub async fn community_inbox(
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?; let unparsed = payload_to_string(payload).await?;
info!("Received community inbox activity {}", unparsed); info!("Received community inbox activity {}", unparsed);
let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?;
let activity = serde_json::from_str::<WithContext<GroupInboxActivities>>(&unparsed)?; let activity = serde_json::from_str::<WithContext<GroupInboxActivities>>(&unparsed)?;
receive_group_inbox(activity.inner(), request, &context).await?; receive_group_inbox(activity.inner(), activity_data, request, &context).await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
pub(in crate::http) async fn receive_group_inbox( pub(in crate::http) async fn receive_group_inbox(
activity: GroupInboxActivities, activity: GroupInboxActivities,
activity_data: ActivityCommonFields,
request: HttpRequest, request: HttpRequest,
context: &LemmyContext, context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
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 { if let GroupInboxActivities::AnnouncableActivities(announcable) = activity {
let community = announcable.get_community(context, &mut 0).await?; 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?; verify_person_in_community(&actor_id, &community, context, &mut 0).await?;
if community.local { 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) Community::read_from_name(conn, &info.community_name)
}) })
.await??; .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_data = CommunityContext(community.into(), context.get_ref().clone());
let outbox: ApubCommunityOutbox = id.dereference(&outbox_data, &mut 0).await?; 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( pub(crate) async fn get_apub_community_moderators(
@ -128,10 +130,10 @@ pub(crate) async fn get_apub_community_moderators(
}) })
.await?? .await??
.into(); .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 outbox_data = CommunityContext(community, context.get_ref().clone());
let moderators: ApubCommunityModerators = id.dereference(&outbox_data, &mut 0).await?; let moderators: ApubCommunityModerators = id.dereference(&outbox_data, &mut 0).await?;
Ok(create_apub_response( Ok(create_apub_response(
&moderators.to_apub(&outbox_data).await?, &moderators.into_apub(&outbox_data).await?,
)) ))
} }

View file

@ -2,7 +2,7 @@ use crate::{
activity_lists::SharedInboxActivities, activity_lists::SharedInboxActivities,
check_is_apub_id_valid, check_is_apub_id_valid,
context::WithContext, context::WithContext,
fetcher::get_or_fetch_and_upsert_actor, fetcher::user_or_community::UserOrCommunity,
http::{community::receive_group_inbox, person::receive_person_inbox}, http::{community::receive_group_inbox, person::receive_person_inbox},
insert_activity, insert_activity,
}; };
@ -19,8 +19,9 @@ use http::StatusCode;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
data::Data, data::Data,
object_id::ObjectId,
signatures::verify_signature, signatures::verify_signature,
traits::{ActivityFields, ActivityHandler}, traits::{ActivityHandler, ActorType},
APUB_JSON_CONTENT_TYPE, APUB_JSON_CONTENT_TYPE,
}; };
use lemmy_db_schema::{source::activity::Activity, DbPool}; use lemmy_db_schema::{source::activity::Activity, DbPool};
@ -44,13 +45,14 @@ pub async fn shared_inbox(
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?; let unparsed = payload_to_string(payload).await?;
info!("Received shared inbox activity {}", unparsed); info!("Received shared inbox activity {}", unparsed);
let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?;
let activity = serde_json::from_str::<WithContext<SharedInboxActivities>>(&unparsed)?; let activity = serde_json::from_str::<WithContext<SharedInboxActivities>>(&unparsed)?;
match activity.inner() { match activity.inner() {
SharedInboxActivities::GroupInboxActivities(g) => { SharedInboxActivities::GroupInboxActivities(g) => {
receive_group_inbox(g, request, &context).await receive_group_inbox(g, activity_data, request, &context).await
} }
SharedInboxActivities::PersonInboxActivities(p) => { 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<String, LemmyError> {
Ok(unparsed) 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 // TODO: move most of this code to library
async fn receive_activity<'a, T>( async fn receive_activity<'a, T>(
request: HttpRequest, request: HttpRequest,
activity: T, activity: T,
activity_data: ActivityCommonFields,
context: &LemmyContext, context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> ) -> Result<HttpResponse, LemmyError>
where where
T: ActivityHandler<DataType = LemmyContext> T: ActivityHandler<DataType = LemmyContext>
+ ActivityFields
+ Clone + Clone
+ Deserialize<'a> + Deserialize<'a>
+ Serialize + Serialize
@ -81,26 +90,27 @@ where
+ Send + Send
+ 'static, + 'static,
{ {
check_is_apub_id_valid(&activity_data.actor, false, &context.settings())?;
let request_counter = &mut 0; let request_counter = &mut 0;
let actor = let actor = ObjectId::<UserOrCommunity>::new(activity_data.actor)
get_or_fetch_and_upsert_actor(activity.actor().clone(), context, request_counter).await?; .dereference(context, request_counter)
.await?;
verify_signature(&request, &actor.public_key().context(location_info!())?)?; verify_signature(&request, &actor.public_key().context(location_info!())?)?;
// Do nothing if we received the same activity before // 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()); return Ok(HttpResponse::Ok().finish());
} }
check_is_apub_id_valid(activity.actor(), false, &context.settings())?; info!("Verifying activity {}", activity_data.id.to_string());
info!("Verifying activity {}", activity.id_unchecked().to_string());
activity activity
.verify(&Data::new(context.clone()), request_counter) .verify(&Data::new(context.clone()), request_counter)
.await?; .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 // 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. // if we receive the same activity twice in very quick succession.
insert_activity( insert_activity(
activity.id_unchecked(), &activity_data.id,
activity.clone(), activity.clone(),
false, false,
true, true,
@ -108,7 +118,7 @@ where
) )
.await?; .await?;
info!("Receiving activity {}", activity.id_unchecked().to_string()); info!("Receiving activity {}", activity_data.id.to_string());
activity activity
.receive(&Data::new(context.clone()), request_counter) .receive(&Data::new(context.clone()), request_counter)
.await?; .await?;
@ -183,17 +193,14 @@ pub(crate) async fn is_activity_already_known(
} }
} }
fn assert_activity_not_local<T: Debug + ActivityFields>( fn assert_activity_not_local(id: &Url, hostname: &str) -> Result<(), LemmyError> {
activity: &T, let activity_domain = id.domain().context(location_info!())?;
hostname: &str,
) -> Result<(), LemmyError> {
let activity_domain = activity.id_unchecked().domain().context(location_info!())?;
if activity_domain == hostname { if activity_domain == hostname {
return Err( return Err(
anyhow!( anyhow!(
"Error: received activity which was sent by local instance: {:?}", "Error: received activity which was sent by local instance: {:?}",
activity id
) )
.into(), .into(),
); );

View file

@ -6,6 +6,7 @@ use crate::{
create_apub_tombstone_response, create_apub_tombstone_response,
payload_to_string, payload_to_string,
receive_activity, receive_activity,
ActivityCommonFields,
}, },
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::collections::person_outbox::PersonOutbox, protocol::collections::person_outbox::PersonOutbox,
@ -38,7 +39,7 @@ pub(crate) async fn get_apub_person_http(
.into(); .into();
if !person.deleted { if !person.deleted {
let apub = person.to_apub(&context).await?; let apub = person.into_apub(&context).await?;
Ok(create_apub_response(&apub)) Ok(create_apub_response(&apub))
} else { } else {
@ -54,16 +55,18 @@ pub async fn person_inbox(
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let unparsed = payload_to_string(payload).await?; let unparsed = payload_to_string(payload).await?;
info!("Received person inbox activity {}", unparsed); info!("Received person inbox activity {}", unparsed);
let activity_data: ActivityCommonFields = serde_json::from_str(&unparsed)?;
let activity = serde_json::from_str::<WithContext<PersonInboxActivities>>(&unparsed)?; let activity = serde_json::from_str::<WithContext<PersonInboxActivities>>(&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( pub(in crate::http) async fn receive_person_inbox(
activity: PersonInboxActivities, activity: PersonInboxActivities,
activity_data: ActivityCommonFields,
request: HttpRequest, request: HttpRequest,
context: &LemmyContext, context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
receive_activity(request, activity, context).await receive_activity(request, activity, activity_data, context).await
} }
pub(crate) async fn get_apub_person_outbox( pub(crate) async fn get_apub_person_outbox(

View file

@ -30,7 +30,7 @@ pub(crate) async fn get_apub_post(
} }
if !post.deleted { if !post.deleted {
Ok(create_apub_response(&post.to_apub(&context).await?)) Ok(create_apub_response(&post.into_apub(&context).await?))
} else { } else {
Ok(create_apub_tombstone_response(&post.to_tombstone()?)) Ok(create_apub_tombstone_response(&post.to_tombstone()?))
} }

View file

@ -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 activitystreams::{object::kind::NoteType, public};
use anyhow::anyhow; use anyhow::anyhow;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use html2md::parse_html; use html2md::parse_html;
use url::Url;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::ApubObject, traits::ApubObject,
values::{MediaTypeHtml, MediaTypeMarkdown}, values::{MediaTypeHtml, MediaTypeMarkdown},
verify::verify_domains_match,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -21,25 +31,12 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_utils::{ use lemmy_utils::{
utils::{convert_datetime, remove_slurs}, utils::{convert_datetime, markdown_to_html, remove_slurs},
LemmyError, LemmyError,
}; };
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use std::ops::Deref;
use crate::{ use url::Url;
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;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ApubComment(Comment); pub struct ApubComment(Comment);
@ -90,7 +87,7 @@ impl ApubObject for ApubComment {
Ok(()) Ok(())
} }
async fn to_apub(&self, context: &LemmyContext) -> Result<Note, LemmyError> { async fn into_apub(self, context: &LemmyContext) -> Result<Note, LemmyError> {
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; 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 in_reply_to = if let Some(comment_id) = self.parent_id {
let parent_comment = let parent_comment =
blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??; blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
ObjectId::<PostOrComment>::new(parent_comment.ap_id.into_inner()) ObjectId::<PostOrComment>::new(parent_comment.ap_id)
} else { } else {
ObjectId::<PostOrComment>::new(post.ap_id.into_inner()) ObjectId::<PostOrComment>::new(post.ap_id)
}; };
let note = Note { let note = Note {
r#type: NoteType::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), attributed_to: ObjectId::new(creator.actor_id),
to: vec![public()], to: vec![public()],
cc: vec![],
content: markdown_to_html(&self.content), content: markdown_to_html(&self.content),
media_type: Some(MediaTypeHtml::Html), media_type: Some(MediaTypeHtml::Html),
source: SourceCompat::Lemmy(Source { source: SourceCompat::Lemmy(Source {
@ -132,27 +130,22 @@ impl ApubObject for ApubComment {
)) ))
} }
/// Converts a `Note` to `Comment`. async fn verify(
///
/// If the parent community, post and comment(s) are not known locally, these are also fetched.
async fn from_apub(
note: &Note, note: &Note,
context: &LemmyContext,
expected_domain: &Url, expected_domain: &Url,
context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<ApubComment, LemmyError> { ) -> Result<(), LemmyError> {
let ap_id = Some(note.id(expected_domain)?.clone().into()); verify_domains_match(note.id.inner(), expected_domain)?;
let creator = note verify_domains_match(note.attributed_to.inner(), note.id.inner())?;
.attributed_to verify_is_public(&note.to, &note.cc)?;
.dereference(context, request_counter) let (post, _) = note.get_parents(context, request_counter).await?;
.await?;
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
let community_id = post.community_id; let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
check_is_apub_id_valid(&note.id, community.local, &context.settings())?; check_is_apub_id_valid(note.id.inner(), community.local, &context.settings())?;
verify_person_in_community( verify_person_in_community(
&note.attributed_to, &note.attributed_to,
&community.into(), &community.into(),
@ -163,6 +156,22 @@ impl ApubObject for ApubComment {
if post.locked { if post.locked {
return Err(anyhow!("Post is locked").into()); 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<ApubComment, LemmyError> {
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) = &note.source { let content = if let SourceCompat::Lemmy(source) = &note.source {
source.content.clone() source.content.clone()
@ -178,10 +187,10 @@ impl ApubObject for ApubComment {
content: content_slurs_removed, content: content_slurs_removed,
removed: None, removed: None,
read: None, read: None,
published: note.published.map(|u| u.to_owned().naive_local()), published: note.published.map(|u| u.naive_local()),
updated: note.updated.map(|u| u.to_owned().naive_local()), updated: note.updated.map(|u| u.naive_local()),
deleted: None, deleted: None,
ap_id, ap_id: Some(note.id.into()),
local: Some(false), local: Some(false),
}; };
let comment = blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??; 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 person = parse_lemmy_person(context).await;
let community = parse_lemmy_community(context).await; let community = parse_lemmy_community(context).await;
let post_json = file_to_json_object("assets/lemmy/objects/page.json"); 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 .await
.unwrap(); .unwrap();
(person, community, post) (person, community, post)
@ -227,21 +239,25 @@ pub(crate) mod tests {
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap();
let data = prepare_comment_test(&url, &context).await; 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 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 .await
.unwrap(); .unwrap();
assert_eq!(comment.ap_id.clone().into_inner(), url); assert_eq!(comment.ap_id, url.into());
assert_eq!(comment.content.len(), 14); assert_eq!(comment.content.len(), 14);
assert!(!comment.local); assert!(!comment.local);
assert_eq!(request_counter, 0); 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); 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); cleanup(data, &context);
} }
@ -256,16 +272,22 @@ pub(crate) mod tests {
Url::parse("https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2") Url::parse("https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2")
.unwrap(); .unwrap();
let person_json = file_to_json_object("assets/pleroma/objects/person.json"); 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 .await
.unwrap(); .unwrap();
let json = file_to_json_object("assets/pleroma/objects/note.json"); let json = file_to_json_object("assets/pleroma/objects/note.json");
let mut request_counter = 0; 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 .await
.unwrap(); .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_eq!(comment.content.len(), 64);
assert!(!comment.local); assert!(!comment.local);
assert_eq!(request_counter, 0); assert_eq!(request_counter, 0);

View file

@ -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::{ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
collections::{community_moderators::ApubCommunityModerators, CommunityContext}, collections::{community_moderators::ApubCommunityModerators, CommunityContext},
fetcher::object_id::ObjectId,
generate_moderators_url, generate_moderators_url,
generate_outbox_url, generate_outbox_url,
protocol::{ protocol::{
objects::{group::Group, tombstone::Tombstone}, objects::{group::Group, tombstone::Tombstone, Endpoints},
ImageObject, ImageObject,
Source, Source,
}, },
}; };
use activitystreams::{actor::kind::GroupType, object::kind::ImageType};
use chrono::NaiveDateTime;
use itertools::Itertools;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::{ActorType, ApubObject}, traits::{ActorType, ApubObject},
values::MediaTypeMarkdown, values::MediaTypeMarkdown,
}; };
@ -32,6 +25,9 @@ use lemmy_utils::{
LemmyError, LemmyError,
}; };
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug;
use std::ops::Deref;
use url::Url;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ApubCommunity(Community); pub struct ApubCommunity(Community);
@ -80,7 +76,7 @@ impl ApubObject for ApubCommunity {
Ok(()) Ok(())
} }
async fn to_apub(&self, _context: &LemmyContext) -> Result<Group, LemmyError> { async fn into_apub(self, _context: &LemmyContext) -> Result<Group, LemmyError> {
let source = self.description.clone().map(|bio| Source { let source = self.description.clone().map(|bio| Source {
content: bio, content: bio,
media_type: MediaTypeMarkdown::Markdown, media_type: MediaTypeMarkdown::Markdown,
@ -96,7 +92,7 @@ impl ApubObject for ApubCommunity {
let group = Group { let group = Group {
kind: GroupType::Group, kind: GroupType::Group,
id: self.actor_id(), id: ObjectId::new(self.actor_id()),
preferred_username: self.name.clone(), preferred_username: self.name.clone(),
name: self.title.clone(), name: self.title.clone(),
summary: self.description.as_ref().map(|b| markdown_to_html(b)), summary: self.description.as_ref().map(|b| markdown_to_html(b)),
@ -105,14 +101,13 @@ impl ApubObject for ApubCommunity {
image, image,
sensitive: Some(self.nsfw), sensitive: Some(self.nsfw),
moderators: Some(ObjectId::<ApubCommunityModerators>::new( moderators: Some(ObjectId::<ApubCommunityModerators>::new(
generate_moderators_url(&self.actor_id)?.into_inner(), generate_moderators_url(&self.actor_id)?,
)), )),
inbox: self.inbox_url.clone().into(), inbox: self.inbox_url.clone().into(),
outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?), outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?),
followers: self.followers_url.clone().into(), followers: self.followers_url.clone().into(),
endpoints: Endpoints { endpoints: Endpoints {
shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
..Default::default()
}, },
public_key: self.get_public_key()?, public_key: self.get_public_key()?,
published: Some(convert_datetime(self.published)), 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. /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
async fn from_apub( async fn from_apub(
group: &Group, group: Group,
context: &LemmyContext, context: &LemmyContext,
expected_domain: &Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> { ) -> Result<ApubCommunity, LemmyError> {
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, // 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. // 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 { impl ActorType for ApubCommunity {
fn is_local(&self) -> bool {
self.local
}
fn actor_id(&self) -> Url { fn actor_id(&self) -> Url {
self.actor_id.to_owned().into() self.actor_id.to_owned().into()
} }
fn name(&self) -> String {
self.name.clone()
}
fn public_key(&self) -> Option<String> { fn public_key(&self) -> Option<String> {
self.public_key.to_owned() self.public_key.to_owned()
} }
@ -187,7 +184,7 @@ impl ActorType for ApubCommunity {
} }
fn shared_inbox_url(&self) -> Option<Url> { fn shared_inbox_url(&self) -> Option<Url> {
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<Url> = follows let follower_inboxes: Vec<Url> = follows
.into_iter() .into_iter()
.filter(|f| !f.follower.local) .filter(|f| !f.follower.local)
.map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url)) .map(|f| {
.map(|i| i.into_inner()) f.follower
.shared_inbox_url
.unwrap_or(f.follower.inbox_url)
.into()
})
.collect(); .collect();
let inboxes = vec![follower_inboxes, additional_inboxes] let inboxes = vec![follower_inboxes, additional_inboxes]
.into_iter() .into_iter()
@ -239,7 +240,10 @@ pub(crate) mod tests {
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
let mut request_counter = 0; 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 .await
.unwrap(); .unwrap();
// this makes two requests to the (intentionally) broken outbox/moderators collections // this makes two requests to the (intentionally) broken outbox/moderators collections

View file

@ -3,15 +3,19 @@ use crate::{
generate_outbox_url, generate_outbox_url,
objects::get_summary_from_string_or_source, objects::get_summary_from_string_or_source,
protocol::{ protocol::{
objects::person::{Person, UserTypes}, objects::{
person::{Person, UserTypes},
Endpoints,
},
ImageObject, ImageObject,
Source, Source,
}, },
}; };
use activitystreams::{actor::Endpoints, object::kind::ImageType}; use activitystreams::object::kind::ImageType;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::{ActorType, ApubObject}, traits::{ActorType, ApubObject},
values::MediaTypeMarkdown, values::MediaTypeMarkdown,
verify::verify_domains_match, verify::verify_domains_match,
@ -75,7 +79,7 @@ impl ApubObject for ApubPerson {
Ok(()) Ok(())
} }
async fn to_apub(&self, _pool: &LemmyContext) -> Result<Person, LemmyError> { async fn into_apub(self, _pool: &LemmyContext) -> Result<Person, LemmyError> {
let kind = if self.bot_account { let kind = if self.bot_account {
UserTypes::Service UserTypes::Service
} else { } else {
@ -96,7 +100,7 @@ impl ApubObject for ApubPerson {
let person = Person { let person = Person {
kind, kind,
id: self.actor_id.to_owned().into_inner(), id: ObjectId::new(self.actor_id.clone()),
preferred_username: self.name.clone(), preferred_username: self.name.clone(),
name: self.display_name.clone(), name: self.display_name.clone(),
summary: self.bio.as_ref().map(|b| markdown_to_html(b)), 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(), outbox: generate_outbox_url(&self.actor_id)?.into(),
endpoints: Endpoints { endpoints: Endpoints {
shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
..Default::default()
}, },
public_key: self.get_public_key()?, public_key: self.get_public_key()?,
updated: self.updated.map(convert_datetime), updated: self.updated.map(convert_datetime),
@ -122,50 +125,51 @@ impl ApubObject for ApubPerson {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
person: &Person, person: &Person,
context: &LemmyContext,
expected_domain: &Url, expected_domain: &Url,
context: &LemmyContext,
_request_counter: &mut i32, _request_counter: &mut i32,
) -> Result<ApubPerson, LemmyError> { ) -> Result<(), LemmyError> {
verify_domains_match(&person.id, expected_domain)?; verify_domains_match(person.id.inner(), expected_domain)?;
let actor_id = Some(person.id.clone().into()); check_is_apub_id_valid(person.id.inner(), false, &context.settings())?;
let name = person.preferred_username.clone();
let display_name: Option<String> = 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,
};
let slur_regex = &context.settings().slur_regex(); let slur_regex = &context.settings().slur_regex();
check_slurs(&name, slur_regex)?; check_slurs(&person.preferred_username, slur_regex)?;
check_slurs_opt(&display_name, 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)?; 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<ApubPerson, LemmyError> {
let person_form = PersonForm { let person_form = PersonForm {
name, name: person.preferred_username,
display_name: Some(display_name), display_name: Some(person.name),
banned: None, banned: None,
deleted: None, deleted: None,
avatar: Some(person.icon.clone().map(|i| i.url.into())), avatar: Some(person.icon.map(|i| i.url.into())),
banner: Some(person.image.clone().map(|i| i.url.into())), banner: Some(person.image.map(|i| i.url.into())),
published: person.published.map(|u| u.clone().naive_local()), published: person.published.map(|u| u.naive_local()),
updated: person.updated.map(|u| u.clone().naive_local()), updated: person.updated.map(|u| u.naive_local()),
actor_id, actor_id: Some(person.id.into()),
bio: Some(bio), bio: Some(get_summary_from_string_or_source(
&person.summary,
&person.source,
)),
local: Some(false), local: Some(false),
admin: Some(false), admin: Some(false),
bot_account: Some(bot_account), bot_account: Some(person.kind == UserTypes::Service),
private_key: None, 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()), last_refreshed_at: Some(naive_now()),
inbox_url: Some(person.inbox.to_owned().into()), inbox_url: Some(person.inbox.into()),
shared_inbox_url: Some(shared_inbox), shared_inbox_url: Some(person.endpoints.shared_inbox.map(|s| s.into())),
matrix_user_id: Some(person.matrix_user_id.clone()), matrix_user_id: Some(person.matrix_user_id),
}; };
let person = blocking(context.pool(), move |conn| { let person = blocking(context.pool(), move |conn| {
DbPerson::upsert(conn, &person_form) DbPerson::upsert(conn, &person_form)
@ -176,14 +180,8 @@ impl ApubObject for ApubPerson {
} }
impl ActorType for ApubPerson { impl ActorType for ApubPerson {
fn is_local(&self) -> bool {
self.local
}
fn actor_id(&self) -> Url { fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner() self.actor_id.to_owned().into()
}
fn name(&self) -> String {
self.name.clone()
} }
fn public_key(&self) -> Option<String> { fn public_key(&self) -> Option<String> {
@ -199,7 +197,7 @@ impl ActorType for ApubPerson {
} }
fn shared_inbox_url(&self) -> Option<Url> { fn shared_inbox_url(&self) -> Option<Url> {
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 json = file_to_json_object("assets/lemmy/objects/person.json");
let url = Url::parse("https://enterprise.lemmy.ml/u/picard").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/u/picard").unwrap();
let mut request_counter = 0; 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 .await
.unwrap(); .unwrap();
assert_eq!(request_counter, 0); 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 json = file_to_json_object("assets/pleroma/objects/person.json");
let url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap(); let url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap();
let mut request_counter = 0; 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 .await
.unwrap(); .unwrap();
assert_eq!(person.actor_id.clone().into_inner(), url); assert_eq!(person.actor_id, url.into());
assert_eq!(person.name, "lanodan"); assert_eq!(person.name, "lanodan");
assert!(person.public_key.is_some()); assert!(person.public_key.is_some());
assert!(!person.local); assert!(!person.local);

View file

@ -1,7 +1,6 @@
use crate::{ use crate::{
activities::verify_person_in_community, activities::{verify_is_public, verify_person_in_community},
check_is_apub_id_valid, check_is_apub_id_valid,
fetcher::object_id::ObjectId,
protocol::{ protocol::{
objects::{page::Page, tombstone::Tombstone}, objects::{page::Page, tombstone::Tombstone},
ImageObject, ImageObject,
@ -15,8 +14,10 @@ use activitystreams::{
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::ApubObject, traits::ApubObject,
values::{MediaTypeHtml, MediaTypeMarkdown}, values::{MediaTypeHtml, MediaTypeMarkdown},
verify::verify_domains_match,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
self, self,
@ -29,7 +30,7 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::{ use lemmy_utils::{
request::fetch_site_data, request::fetch_site_data,
utils::{convert_datetime, markdown_to_html, remove_slurs}, utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs},
LemmyError, LemmyError,
}; };
use lemmy_websocket::LemmyContext; 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. // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
async fn to_apub(&self, context: &LemmyContext) -> Result<Page, LemmyError> { async fn into_apub(self, context: &LemmyContext) -> Result<Page, LemmyError> {
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
let community_id = self.community_id; let community_id = self.community_id;
@ -106,9 +107,10 @@ impl ApubObject for ApubPost {
let page = Page { let page = Page {
r#type: PageType::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), attributed_to: ObjectId::new(creator.actor_id),
to: vec![community.actor_id.into(), public()], to: vec![community.actor_id.into(), public()],
cc: vec![],
name: self.name.clone(), name: self.name.clone(),
content: self.body.as_ref().map(|b| markdown_to_html(b)), content: self.body.as_ref().map(|b| markdown_to_html(b)),
media_type: Some(MediaTypeHtml::Html), media_type: Some(MediaTypeHtml::Html),
@ -132,29 +134,39 @@ impl ApubObject for ApubPost {
)) ))
} }
async fn from_apub( async fn verify(
page: &Page, page: &Page,
context: &LemmyContext,
expected_domain: &Url, expected_domain: &Url,
context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<ApubPost, LemmyError> { ) -> Result<(), LemmyError> {
// We can't verify the domain in case of mod action, because the mod may be on a different // We can't verify the domain in case of mod action, because the mod may be on a different
// instance from the post author. // instance from the post author.
let ap_id = if page.is_mod_action(context).await? { if !page.is_mod_action(context).await? {
page.id_unchecked() verify_domains_match(page.id.inner(), expected_domain)?;
} else {
page.id(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<ApubPost, LemmyError> {
let creator = page let creator = page
.attributed_to .attributed_to
.dereference(context, request_counter) .dereference(context, request_counter)
.await?; .await?;
let community = page.extract_community(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<Url> = page.image.clone().map(|i| i.url); let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url { let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url {
fetch_site_data(context.client(), &context.settings(), Some(url)).await fetch_site_data(context.client(), &context.settings(), Some(url)).await
} else { } else {
@ -169,8 +181,8 @@ impl ApubObject for ApubPost {
.as_ref() .as_ref()
.map(|s| remove_slurs(&s.content, &context.settings().slur_regex())); .map(|s| remove_slurs(&s.content, &context.settings().slur_regex()));
let form = PostForm { let form = PostForm {
name: page.name.clone(), name: page.name,
url: page.url.clone().map(|u| u.into()), url: page.url.map(|u| u.into()),
body: body_slurs_removed, body: body_slurs_removed,
creator_id: creator.id, creator_id: creator.id,
community_id: community.id, community_id: community.id,
@ -185,7 +197,7 @@ impl ApubObject for ApubPost {
embed_description, embed_description,
embed_html, embed_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()), thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id, ap_id: Some(page.id.into()),
local: Some(false), local: Some(false),
}; };
let post = blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??; 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 json = file_to_json_object("assets/lemmy/objects/page.json");
let url = Url::parse("https://enterprise.lemmy.ml/post/55143").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/post/55143").unwrap();
let mut request_counter = 0; 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 .await
.unwrap(); .unwrap();
assert_eq!(post.ap_id.clone().into_inner(), url); assert_eq!(post.ap_id, url.into());
assert_eq!(post.name, "Post title"); assert_eq!(post.name, "Post title");
assert!(post.body.is_some()); assert!(post.body.is_some());
assert_eq!(post.body.as_ref().unwrap().len(), 45); assert_eq!(post.body.as_ref().unwrap().len(), 45);

View file

@ -1,16 +1,16 @@
use crate::{ use crate::protocol::{
fetcher::object_id::ObjectId, objects::chat_message::{ChatMessage, ChatMessageType},
protocol::{ Source,
objects::chat_message::{ChatMessage, ChatMessageType},
Source,
},
}; };
use anyhow::anyhow;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use html2md::parse_html; use html2md::parse_html;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{ use lemmy_apub_lib::{
object_id::ObjectId,
traits::ApubObject, traits::ApubObject,
values::{MediaTypeHtml, MediaTypeMarkdown}, values::{MediaTypeHtml, MediaTypeMarkdown},
verify::verify_domains_match,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -71,7 +71,7 @@ impl ApubObject for ApubPrivateMessage {
unimplemented!() unimplemented!()
} }
async fn to_apub(&self, context: &LemmyContext) -> Result<ChatMessage, LemmyError> { async fn into_apub(self, context: &LemmyContext) -> Result<ChatMessage, LemmyError> {
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??; let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
@ -81,7 +81,7 @@ impl ApubObject for ApubPrivateMessage {
let note = ChatMessage { let note = ChatMessage {
r#type: ChatMessageType::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), attributed_to: ObjectId::new(creator.actor_id),
to: [ObjectId::new(recipient.actor_id)], to: [ObjectId::new(recipient.actor_id)],
content: markdown_to_html(&self.content), content: markdown_to_html(&self.content),
@ -101,13 +101,29 @@ impl ApubObject for ApubPrivateMessage {
unimplemented!() unimplemented!()
} }
async fn from_apub( async fn verify(
note: &ChatMessage, note: &ChatMessage,
context: &LemmyContext,
expected_domain: &Url, 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, request_counter: &mut i32,
) -> Result<ApubPrivateMessage, LemmyError> { ) -> Result<ApubPrivateMessage, LemmyError> {
let ap_id = Some(note.id(expected_domain)?.clone().into());
let creator = note let creator = note
.attributed_to .attributed_to
.dereference(context, request_counter) .dereference(context, request_counter)
@ -123,11 +139,11 @@ impl ApubObject for ApubPrivateMessage {
creator_id: creator.id, creator_id: creator.id,
recipient_id: recipient.id, recipient_id: recipient.id,
content, content,
published: note.published.map(|u| u.to_owned().naive_local()), published: note.published.map(|u| u.naive_local()),
updated: note.updated.map(|u| u.to_owned().naive_local()), updated: note.updated.map(|u| u.naive_local()),
deleted: None, deleted: None,
read: None, read: None,
ap_id, ap_id: Some(note.id.into()),
local: Some(false), local: Some(false),
}; };
let pm = blocking(context.pool(), move |conn| { let pm = blocking(context.pool(), move |conn| {
@ -150,12 +166,18 @@ mod tests {
async fn prepare_comment_test(url: &Url, context: &LemmyContext) -> (ApubPerson, ApubPerson) { async fn prepare_comment_test(url: &Url, context: &LemmyContext) -> (ApubPerson, ApubPerson) {
let lemmy_person = file_to_json_object("assets/lemmy/objects/person.json"); 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 .await
.unwrap(); .unwrap();
let pleroma_person = file_to_json_object("assets/pleroma/objects/person.json"); 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 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 .await
.unwrap(); .unwrap();
(person1, person2) (person1, person2)
@ -172,20 +194,24 @@ mod tests {
let context = init_context(); let context = init_context();
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap();
let data = prepare_comment_test(&url, &context).await; 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 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 .await
.unwrap(); .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!(pm.content.len(), 20);
assert_eq!(request_counter, 0); 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); 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); cleanup(data, &context);
} }
@ -198,11 +224,14 @@ mod tests {
let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2").unwrap(); 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 json = file_to_json_object("assets/pleroma/objects/chat_message.json");
let mut request_counter = 0; 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 .await
.unwrap(); .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!(pm.content.len(), 3);
assert_eq!(request_counter, 0); assert_eq!(request_counter, 0);

View file

@ -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 activitystreams::{activity::kind::AddType, unparsed::Unparsed};
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AddMod { pub struct AddMod {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,10 @@
use crate::{ use crate::{activity_lists::AnnouncableActivities, objects::community::ApubCommunity};
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::community::ApubCommunity,
};
use activitystreams::{activity::kind::AnnounceType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AnnounceActivity { pub struct AnnounceActivity {
pub(crate) actor: ObjectId<ApubCommunity>, pub(crate) actor: ObjectId<ApubCommunity>,

View file

@ -1,13 +1,10 @@
use crate::{ use crate::objects::{community::ApubCommunity, person::ApubPerson};
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
};
use activitystreams::{activity::kind::BlockType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BlockUserFromCommunity { pub struct BlockUserFromCommunity {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -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 activitystreams::{activity::kind::RemoveType, unparsed::Unparsed};
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RemoveMod { pub struct RemoveMod {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,13 +1,13 @@
use crate::{ use crate::{
fetcher::{object_id::ObjectId, post_or_comment::PostOrComment}, fetcher::post_or_comment::PostOrComment,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
}; };
use activitystreams::{activity::kind::FlagType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Report { pub struct Report {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::activities::community::block_user::BlockUserFromCommunity, protocol::activities::community::block_user::BlockUserFromCommunity,
}; };
use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UndoBlockUserFromCommunity { pub struct UndoBlockUserFromCommunity {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,22 +1,18 @@
use crate::{ use crate::{objects::person::ApubPerson, protocol::objects::group::Group};
fetcher::object_id::ObjectId,
objects::person::ApubPerson,
protocol::objects::group::Group,
};
use activitystreams::{activity::kind::UpdateType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
/// This activity is received from a remote community mod, and updates the description or other /// This activity is received from a remote community mod, and updates the description or other
/// fields of a local community. /// fields of a local community.
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UpdateCommunity { pub struct UpdateCommunity {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,
pub(crate) to: Vec<Url>, pub(crate) to: Vec<Url>,
// TODO: would be nice to use a separate struct here, which only contains the fields updated here // 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<Group>,
pub(crate) cc: Vec<Url>, pub(crate) cc: Vec<Url>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UpdateType, pub(crate) kind: UpdateType,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::{activities::CreateOrUpdateType, objects::note::Note}, protocol::{activities::CreateOrUpdateType, objects::note::Note},
}; };
use activitystreams::{link::Mention, unparsed::Unparsed}; use activitystreams::{link::Mention, unparsed::Unparsed};
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateOrUpdateComment { pub struct CreateOrUpdateComment {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::{activities::CreateOrUpdateType, objects::page::Page}, protocol::{activities::CreateOrUpdateType, objects::page::Page},
}; };
use activitystreams::unparsed::Unparsed; use activitystreams::unparsed::Unparsed;
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePost { pub struct CreateOrUpdatePost {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -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 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::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use url::Url; use url::Url;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Delete { pub struct Delete {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,16 +1,10 @@
use crate::{objects::person::ApubPerson, protocol::activities::deletion::delete::Delete};
use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed};
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use lemmy_apub_lib::traits::ActivityFields; #[derive(Clone, Debug, Deserialize, Serialize)]
use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson,
protocol::activities::deletion::delete::Delete,
};
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UndoDelete { pub struct UndoDelete {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::follow::FollowCommunity, protocol::activities::following::follow::FollowCommunity,
}; };
use activitystreams::{activity::kind::AcceptType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AcceptFollowCommunity { pub struct AcceptFollowCommunity {
pub(crate) actor: ObjectId<ApubCommunity>, pub(crate) actor: ObjectId<ApubCommunity>,

View file

@ -1,13 +1,10 @@
use crate::{ use crate::objects::{community::ApubCommunity, person::ApubPerson};
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
};
use activitystreams::{activity::kind::FollowType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FollowCommunity { pub struct FollowCommunity {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::follow::FollowCommunity, protocol::activities::following::follow::FollowCommunity,
}; };
use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UndoFollowCommunity { pub struct UndoFollowCommunity {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::{activities::CreateOrUpdateType, objects::chat_message::ChatMessage}, protocol::{activities::CreateOrUpdateType, objects::chat_message::ChatMessage},
}; };
use activitystreams::unparsed::Unparsed; use activitystreams::unparsed::Unparsed;
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePrivateMessage { pub struct CreateOrUpdatePrivateMessage {
pub(crate) id: Url, pub(crate) id: Url,

View file

@ -1,13 +1,10 @@
use crate::{ use crate::objects::{person::ApubPerson, private_message::ApubPrivateMessage};
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
};
use activitystreams::{activity::kind::DeleteType, unparsed::Unparsed}; 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::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DeletePrivateMessage { pub struct DeletePrivateMessage {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::activities::private_message::delete::DeletePrivateMessage, protocol::activities::private_message::delete::DeletePrivateMessage,
}; };
use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; 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 serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UndoDeletePrivateMessage { pub struct UndoDeletePrivateMessage {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,16 +1,10 @@
use crate::{objects::person::ApubPerson, protocol::activities::voting::vote::Vote};
use activitystreams::{activity::kind::UndoType, unparsed::Unparsed}; use activitystreams::{activity::kind::UndoType, unparsed::Unparsed};
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use lemmy_apub_lib::traits::ActivityFields; #[derive(Clone, Debug, Deserialize, Serialize)]
use crate::{
fetcher::object_id::ObjectId,
objects::person::ApubPerson,
protocol::activities::voting::vote::Vote,
};
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UndoVote { pub struct UndoVote {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -1,17 +1,14 @@
use crate::{ use crate::{fetcher::post_or_comment::PostOrComment, objects::person::ApubPerson};
fetcher::{object_id::ObjectId, post_or_comment::PostOrComment},
objects::person::ApubPerson,
};
use activitystreams::unparsed::Unparsed; use activitystreams::unparsed::Unparsed;
use anyhow::anyhow; use anyhow::anyhow;
use lemmy_apub_lib::traits::ActivityFields; use lemmy_apub_lib::object_id::ObjectId;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use strum_macros::ToString; use strum_macros::ToString;
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Vote { pub struct Vote {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,

View file

@ -29,7 +29,7 @@ impl GroupFollowers {
.await??; .await??;
Ok(GroupFollowers { Ok(GroupFollowers {
id: generate_followers_url(&community.actor_id)?.into_inner(), id: generate_followers_url(&community.actor_id)?.into(),
r#type: CollectionType::Collection, r#type: CollectionType::Collection,
total_items: community_followers.len() as i32, total_items: community_followers.len() as i32,
items: vec![], items: vec![],

View file

@ -1,5 +1,6 @@
use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson}; use crate::objects::person::ApubPerson;
use activitystreams::collection::kind::OrderedCollectionType; use activitystreams::collection::kind::OrderedCollectionType;
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;

View file

@ -18,7 +18,7 @@ impl PersonOutbox {
pub(crate) async fn new(user: Person) -> Result<PersonOutbox, LemmyError> { pub(crate) async fn new(user: Person) -> Result<PersonOutbox, LemmyError> {
Ok(PersonOutbox { Ok(PersonOutbox {
r#type: OrderedCollectionType::OrderedCollection, r#type: OrderedCollectionType::OrderedCollection,
id: generate_outbox_url(&user.actor_id)?.into_inner(), id: generate_outbox_url(&user.actor_id)?.into(),
ordered_items: vec![], ordered_items: vec![],
total_items: 0, total_items: 0,
}) })

View file

@ -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::{ use activitystreams::{
chrono::{DateTime, FixedOffset}, chrono::{DateTime, FixedOffset},
unparsed::Unparsed, unparsed::Unparsed,
}; };
use anyhow::anyhow; use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use url::Url;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChatMessage { pub struct ChatMessage {
pub(crate) r#type: ChatMessageType, pub(crate) r#type: ChatMessageType,
pub(crate) id: Url, pub(crate) id: ObjectId<ApubPrivateMessage>,
pub(crate) attributed_to: ObjectId<ApubPerson>, pub(crate) attributed_to: ObjectId<ApubPerson>,
pub(crate) to: [ObjectId<ApubPerson>; 1], pub(crate) to: [ObjectId<ApubPerson>; 1],
pub(crate) content: String, pub(crate) content: String,
@ -33,29 +32,3 @@ pub struct ChatMessage {
pub enum ChatMessageType { pub enum ChatMessageType {
ChatMessage, 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(())
}
}

View file

@ -4,22 +4,18 @@ use crate::{
community_moderators::ApubCommunityModerators, community_moderators::ApubCommunityModerators,
community_outbox::ApubCommunityOutbox, community_outbox::ApubCommunityOutbox,
}, },
fetcher::object_id::ObjectId, objects::{community::ApubCommunity, get_summary_from_string_or_source},
objects::get_summary_from_string_or_source, protocol::{objects::Endpoints, ImageObject, Source},
protocol::{ImageObject, Source},
};
use activitystreams::{
actor::{kind::GroupType, Endpoints},
unparsed::Unparsed,
}; };
use activitystreams::{actor::kind::GroupType, unparsed::Unparsed};
use chrono::{DateTime, FixedOffset}; 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_db_schema::{naive_now, source::community::CommunityForm};
use lemmy_utils::{ use lemmy_utils::{
settings::structs::Settings,
utils::{check_slurs, check_slurs_opt}, utils::{check_slurs, check_slurs_opt},
LemmyError, LemmyError,
}; };
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use url::Url; use url::Url;
@ -30,7 +26,7 @@ use url::Url;
pub struct Group { pub struct Group {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: GroupType, pub(crate) kind: GroupType,
pub(crate) id: Url, pub(crate) id: ObjectId<ApubCommunity>,
/// username, set at account creation and can never be changed /// username, set at account creation and can never be changed
pub(crate) preferred_username: String, pub(crate) preferred_username: String,
/// title (can be changed at any time) /// title (can be changed at any time)
@ -47,7 +43,7 @@ pub struct Group {
pub(crate) inbox: Url, pub(crate) inbox: Url,
pub(crate) outbox: ObjectId<ApubCommunityOutbox>, pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
pub(crate) followers: Url, pub(crate) followers: Url,
pub(crate) endpoints: Endpoints<Url>, pub(crate) endpoints: Endpoints,
pub(crate) public_key: PublicKey, pub(crate) public_key: PublicKey,
pub(crate) published: Option<DateTime<FixedOffset>>, pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>, pub(crate) updated: Option<DateTime<FixedOffset>>,
@ -56,42 +52,42 @@ pub struct Group {
} }
impl Group { impl Group {
pub(crate) async fn from_apub_to_form( pub(crate) async fn verify(
group: &Group, &self,
expected_domain: &Url, expected_domain: &Url,
settings: &Settings, context: &LemmyContext,
) -> Result<CommunityForm, LemmyError> { ) -> Result<(), LemmyError> {
check_is_apub_id_valid(&group.id, true, settings)?; check_is_apub_id_valid(self.id.inner(), true, &context.settings())?;
verify_domains_match(expected_domain, &group.id)?; verify_domains_match(expected_domain, self.id.inner())?;
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());
let slur_regex = &settings.slur_regex(); let slur_regex = &context.settings().slur_regex();
check_slurs(&name, slur_regex)?; check_slurs(&self.preferred_username, slur_regex)?;
check_slurs(&title, 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)?; check_slurs_opt(&description, slur_regex)?;
Ok(())
}
Ok(CommunityForm { pub(crate) fn into_form(self) -> CommunityForm {
name, CommunityForm {
title, name: self.preferred_username,
description, title: self.name,
description: get_summary_from_string_or_source(&self.summary, &self.source),
removed: None, removed: None,
published: group.published.map(|u| u.naive_local()), published: self.published.map(|u| u.naive_local()),
updated: group.updated.map(|u| u.naive_local()), updated: self.updated.map(|u| u.naive_local()),
deleted: None, deleted: None,
nsfw: Some(group.sensitive.unwrap_or(false)), nsfw: Some(self.sensitive.unwrap_or(false)),
actor_id: Some(group.id.clone().into()), actor_id: Some(self.id.into()),
local: Some(false), local: Some(false),
private_key: None, 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()), last_refreshed_at: Some(naive_now()),
icon: Some(group.icon.clone().map(|i| i.url.into())), icon: Some(self.icon.map(|i| i.url.into())),
banner: Some(group.image.clone().map(|i| i.url.into())), banner: Some(self.image.map(|i| i.url.into())),
followers_url: Some(group.followers.clone().into()), followers_url: Some(self.followers.into()),
inbox_url: Some(group.inbox.clone().into()), inbox_url: Some(self.inbox.into()),
shared_inbox_url: Some(shared_inbox), shared_inbox_url: Some(self.endpoints.shared_inbox.map(|s| s.into())),
}) }
} }
} }

View file

@ -1,3 +1,6 @@
use serde::{Deserialize, Serialize};
use url::Url;
pub(crate) mod chat_message; pub(crate) mod chat_message;
pub(crate) mod group; pub(crate) mod group;
pub(crate) mod note; pub(crate) mod note;
@ -5,6 +8,13 @@ pub(crate) mod page;
pub(crate) mod person; pub(crate) mod person;
pub(crate) mod tombstone; 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<Url>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::protocol::{ use crate::protocol::{

View file

@ -1,19 +1,13 @@
use crate::{ use crate::{
activities::{verify_is_public, verify_person_in_community}, fetcher::post_or_comment::PostOrComment,
fetcher::{object_id::ObjectId, post_or_comment::PostOrComment}, objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::Source, protocol::Source,
}; };
use activitystreams::{object::kind::NoteType, unparsed::Unparsed}; use activitystreams::{object::kind::NoteType, unparsed::Unparsed};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match}; use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use lemmy_db_schema::{ use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud};
newtypes::CommentId,
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -26,12 +20,11 @@ use url::Url;
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Note { pub struct Note {
pub(crate) r#type: NoteType, pub(crate) r#type: NoteType,
pub(crate) id: Url, pub(crate) id: ObjectId<ApubComment>,
pub(crate) attributed_to: ObjectId<ApubPerson>, pub(crate) attributed_to: ObjectId<ApubPerson>,
/// 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<Url>, pub(crate) to: Vec<Url>,
#[serde(default)]
pub(crate) cc: Vec<Url>,
pub(crate) content: String, pub(crate) content: String,
pub(crate) media_type: Option<MediaTypeHtml>, pub(crate) media_type: Option<MediaTypeHtml>,
pub(crate) source: SourceCompat, pub(crate) source: SourceCompat,
@ -52,14 +45,6 @@ pub(crate) enum SourceCompat {
} }
impl Note { 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( pub(crate) async fn get_parents(
&self, &self,
context: &LemmyContext, 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(())
}
} }

View file

@ -1,14 +1,12 @@
use crate::{ use crate::{
activities::{verify_is_public, verify_person_in_community},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{ImageObject, Source}, protocol::{ImageObject, Source},
}; };
use activitystreams::{object::kind::PageType, unparsed::Unparsed}; use activitystreams::{object::kind::PageType, unparsed::Unparsed};
use anyhow::anyhow; use anyhow::anyhow;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match}; use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use lemmy_utils::{utils::check_slurs, LemmyError}; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
@ -19,9 +17,11 @@ use url::Url;
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Page { pub struct Page {
pub(crate) r#type: PageType, pub(crate) r#type: PageType,
pub(crate) id: Url, pub(crate) id: ObjectId<ApubPost>,
pub(crate) attributed_to: ObjectId<ApubPerson>, pub(crate) attributed_to: ObjectId<ApubPerson>,
pub(crate) to: Vec<Url>, pub(crate) to: Vec<Url>,
#[serde(default)]
pub(crate) cc: Vec<Url>,
pub(crate) name: String, pub(crate) name: String,
pub(crate) content: Option<String>, pub(crate) content: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>, pub(crate) media_type: Option<MediaTypeHtml>,
@ -38,14 +38,6 @@ pub struct Page {
} }
impl 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 /// 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. /// 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) 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( pub(crate) async fn extract_community(
&self, &self,
context: &LemmyContext, context: &LemmyContext,

View file

@ -1,7 +1,10 @@
use crate::protocol::{ImageObject, Source}; use crate::{
use activitystreams::{actor::Endpoints, unparsed::Unparsed, url::Url}; objects::person::ApubPerson,
protocol::{objects::Endpoints, ImageObject, Source},
};
use activitystreams::{unparsed::Unparsed, url::Url};
use chrono::{DateTime, FixedOffset}; 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::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
@ -17,7 +20,7 @@ pub enum UserTypes {
pub struct Person { pub struct Person {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UserTypes, pub(crate) kind: UserTypes,
pub(crate) id: Url, pub(crate) id: ObjectId<ApubPerson>,
/// username, set at account creation and can never be changed /// username, set at account creation and can never be changed
pub(crate) preferred_username: String, pub(crate) preferred_username: String,
/// displayname (can be changed at any time) /// displayname (can be changed at any time)
@ -32,7 +35,7 @@ pub struct Person {
pub(crate) inbox: Url, pub(crate) inbox: Url,
/// mandatory field in activitypub, currently empty in lemmy /// mandatory field in activitypub, currently empty in lemmy
pub(crate) outbox: Url, pub(crate) outbox: Url,
pub(crate) endpoints: Endpoints<Url>, pub(crate) endpoints: Endpoints,
pub(crate) public_key: PublicKey, pub(crate) public_key: PublicKey,
pub(crate) published: Option<DateTime<FixedOffset>>, pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>, pub(crate) updated: Option<DateTime<FixedOffset>>,

View file

@ -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-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"] } http-signature-normalization-reqwest = { version = "0.2.0", default-features = false, features = ["sha-2"] }
background-jobs = "0.9.1" background-jobs = "0.9.1"
diesel = "1.4.8"

View file

@ -3,6 +3,7 @@ extern crate lazy_static;
pub mod activity_queue; pub mod activity_queue;
pub mod data; pub mod data;
pub mod object_id;
pub mod signatures; pub mod signatures;
pub mod traits; pub mod traits;
pub mod values; pub mod values;

View file

@ -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 anyhow::anyhow;
use diesel::NotFound; use diesel::NotFound;
use lemmy_apub_lib::{traits::ApubObject, APUB_JSON_CONTENT_TYPE};
use lemmy_db_schema::newtypes::DbUrl;
use lemmy_utils::{ use lemmy_utils::{
request::{build_user_agent, retry}, request::{build_user_agent, retry},
settings::structs::Settings, settings::structs::Settings,
@ -29,9 +28,10 @@ lazy_static! {
.unwrap(); .unwrap();
} }
/// We store Url on the heap because it is quite large (88 bytes).
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
#[serde(transparent)] #[serde(transparent)]
pub struct ObjectId<Kind>(Url, #[serde(skip)] PhantomData<Kind>) pub struct ObjectId<Kind>(Box<Url>, #[serde(skip)] PhantomData<Kind>)
where where
Kind: ApubObject + Send + 'static, Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>; for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>;
@ -45,7 +45,7 @@ where
where where
T: Into<Url>, T: Into<Url>,
{ {
ObjectId(url.into(), PhantomData::<Kind>) ObjectId(Box::new(url.into()), PhantomData::<Kind>)
} }
pub fn inner(&self) -> &Url { pub fn inner(&self) -> &Url {
@ -104,7 +104,7 @@ where
data: &<Kind as ApubObject>::DataType, data: &<Kind as ApubObject>::DataType,
) -> Result<Option<Kind>, LemmyError> { ) -> Result<Option<Kind>, LemmyError> {
let id = self.0.clone(); 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( async fn dereference_from_http(
@ -140,16 +140,39 @@ where
let res2: Kind::ApubType = res.json().await?; 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<Kind> Display for ObjectId<Kind> impl<Kind> Display for ObjectId<Kind>
where where
Kind: ApubObject + Send + 'static, Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>, for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{ {
#[allow(clippy::to_string_in_display)]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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()) write!(f, "{}", self.0.to_string())
} }
} }
@ -160,16 +183,21 @@ where
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>, for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{ {
fn from(id: ObjectId<Kind>) -> Self { fn from(id: ObjectId<Kind>) -> Self {
id.0 *id.0
} }
} }
impl<Kind> From<ObjectId<Kind>> for DbUrl #[cfg(test)]
where mod tests {
Kind: ApubObject + Send + 'static, use super::*;
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>, use crate::object_id::should_refetch_object;
{
fn from(id: ObjectId<Kind>) -> Self { #[test]
id.0.into() 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));
} }
} }

View file

@ -91,7 +91,7 @@ pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), L
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKey { pub struct PublicKey {
pub id: String, pub(crate) id: String,
pub owner: Url, pub(crate) owner: Box<Url>,
pub public_key_pem: String, pub public_key_pem: String,
} }

View file

@ -5,11 +5,6 @@ pub use lemmy_apub_lib_derive::*;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use url::Url; use url::Url;
pub trait ActivityFields {
fn id_unchecked(&self) -> &Url;
fn actor(&self) -> &Url;
}
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait ActivityHandler { pub trait ActivityHandler {
type DataType; type DataType;
@ -46,9 +41,16 @@ pub trait ApubObject {
async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError>; async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError>;
/// Trait for converting an object or actor into the respective ActivityPub type. /// Trait for converting an object or actor into the respective ActivityPub type.
async fn to_apub(&self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError>; async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError>;
fn to_tombstone(&self) -> Result<Self::TombstoneType, LemmyError>; fn to_tombstone(&self) -> Result<Self::TombstoneType, LemmyError>;
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. /// Converts an object from ActivityPub type to Lemmy internal type.
/// ///
/// * `apub` The object to read from /// * `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. /// * `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 /// * `mod_action_allowed` True if the object can be a mod activity, ignore `expected_domain` in this case
async fn from_apub( async fn from_apub(
apub: &Self::ApubType, apub: Self::ApubType,
data: &Self::DataType, data: &Self::DataType,
expected_domain: &Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<Self, LemmyError> ) -> Result<Self, LemmyError>
where where
@ -68,9 +69,7 @@ pub trait ApubObject {
/// Common methods provided by ActivityPub actors (community and person). Not all methods are /// Common methods provided by ActivityPub actors (community and person). Not all methods are
/// implemented by all actors. /// implemented by all actors.
pub trait ActorType { pub trait ActorType {
fn is_local(&self) -> bool;
fn actor_id(&self) -> Url; fn actor_id(&self) -> Url;
fn name(&self) -> String;
// TODO: this should not be an option (needs db migration in lemmy) // TODO: this should not be an option (needs db migration in lemmy)
fn public_key(&self) -> Option<String>; fn public_key(&self) -> Option<String>;
@ -87,7 +86,7 @@ pub trait ActorType {
fn get_public_key(&self) -> Result<PublicKey, LemmyError> { fn get_public_key(&self) -> Result<PublicKey, LemmyError> {
Ok(PublicKey { Ok(PublicKey {
id: format!("{}#main-key", self.actor_id()), 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!())?, public_key_pem: self.public_key().context(location_info!())?,
}) })
} }

View file

@ -127,40 +127,3 @@ fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream)
_ => unimplemented!(), _ => 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, &quote! {a.id_unchecked()}));
let impl_actor = variants
.iter()
.map(|v| generate_match_arm(&name, v, &quote! {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()
}

View file

@ -12,6 +12,7 @@ doctest = false
[dependencies] [dependencies]
lemmy_utils = { version = "=0.13.5-rc.7", path = "../utils" } 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 = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }

View file

@ -4,6 +4,7 @@ use diesel::{
serialize::{Output, ToSql}, serialize::{Output, ToSql},
sql_types::Text, sql_types::Text,
}; };
use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt, 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 { impl Display for DbUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_owned().0.fmt(f) self.to_owned().0.fmt(f)
} }
} }
impl From<DbUrl> for Url { // the project doesnt compile with From
fn from(url: DbUrl) -> Self { #[allow(clippy::from_over_into)]
url.0 impl Into<DbUrl> for Url {
fn into(self) -> DbUrl {
DbUrl(self)
}
}
#[allow(clippy::from_over_into)]
impl Into<Url> for DbUrl {
fn into(self) -> Url {
self.0
} }
} }
impl From<Url> for DbUrl { impl<Kind> From<ObjectId<Kind>> for DbUrl
fn from(url: Url) -> Self { where
DbUrl(url) Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{
fn from(id: ObjectId<Kind>) -> Self {
DbUrl(id.into())
} }
} }