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",
"background-jobs",
"base64 0.13.0",
"diesel",
"http",
"http-signature-normalization-actix",
"http-signature-normalization-reqwest",
@ -1904,6 +1905,7 @@ dependencies = [
"diesel-derive-newtype",
"diesel_migrations",
"lazy_static",
"lemmy_apub_lib",
"lemmy_utils",
"log",
"regex",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
http::is_activity_already_known,
http::{is_activity_already_known, ActivityCommonFields},
insert_activity,
objects::community::ApubCommunity,
protocol::activities::community::announce::AnnounceActivity,
@ -10,7 +9,8 @@ use crate::{
use activitystreams::{activity::kind::AnnounceType, public};
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
@ -36,7 +36,7 @@ impl AnnounceActivity {
actor: ObjectId::new(community.actor_id()),
to: vec![public()],
object,
cc: vec![community.followers_url.clone().into_inner()],
cc: vec![community.followers_url.clone().into()],
kind: AnnounceType::Announce,
id: generate_activity_id(
&AnnounceType::Announce,
@ -59,8 +59,8 @@ impl ActivityHandler for AnnounceActivity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -70,11 +70,15 @@ impl ActivityHandler for AnnounceActivity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
if is_activity_already_known(context.pool(), self.object.id_unchecked()).await? {
// TODO: this is pretty ugly, but i cant think of a much better way
let object = serde_json::to_string(&self.object)?;
let object_data: ActivityCommonFields = serde_json::from_str(&object)?;
if is_activity_already_known(context.pool(), &object_data.id).await? {
return Ok(());
}
insert_activity(
self.object.id_unchecked(),
&object_data.id,
self.object.clone(),
false,
true,

View File

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

View File

@ -1,12 +1,11 @@
use crate::{
activities::send_lemmy_activity,
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
insert_activity,
objects::community::ApubCommunity,
protocol::activities::community::announce::AnnounceActivity,
};
use lemmy_apub_lib::traits::ActorType;
use lemmy_apub_lib::{object_id::ObjectId, traits::ActorType};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;

View File

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

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 lemmy_api_common::{blocking, comment::CommentReportResponse, post::PostReportResponse};
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
@ -16,19 +27,6 @@ use lemmy_db_views::{comment_report_view::CommentReportView, post_report_view::P
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
use crate::{
activities::{
generate_activity_id,
send_lemmy_activity,
verify_activity,
verify_person_in_community,
},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::report::Report,
PostOrComment,
};
impl Report {
pub async fn send(
object_id: ObjectId<PostOrComment>,
@ -72,7 +70,7 @@ impl ActivityHandler for Report {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())

View File

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

View File

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

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 anyhow::anyhow;
use url::Url;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::{
@ -29,20 +40,7 @@ use lemmy_websocket::{
LemmyContext,
UserOperationCrud,
};
use crate::{
activities::{
community::{announce::GetCommunity, send_to_community},
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id,
verify_activity,
verify_is_public,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::delete::Delete,
};
use url::Url;
#[async_trait::async_trait(?Send)]
impl ActivityHandler for Delete {
@ -52,12 +50,12 @@ impl ActivityHandler for Delete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object,
self,
&self.actor,
&community,
self.summary.is_some(),
context,

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

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::{
activities::{
community::{announce::GetCommunity, send_to_community},
@ -24,10 +7,25 @@ use crate::{
verify_is_public,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
use activitystreams::{activity::kind::UndoType, public};
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
use lemmy_utils::LemmyError;
use lemmy_websocket::{
send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
LemmyContext,
UserOperationCrud,
};
use url::Url;
#[async_trait::async_trait(?Send)]
impl ActivityHandler for UndoDelete {
@ -37,13 +35,13 @@ impl ActivityHandler for UndoDelete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
self.object.verify(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object.object,
self,
&self.actor,
&community,
self.object.summary.is_some(),
context,

View File

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

View File

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

View File

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

View File

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

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::{
activities::{
check_community_deleted_or_removed,
@ -22,14 +9,25 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
};
use activitystreams::public;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType, ApubObject},
verify::{verify_domains_match, verify_urls_match},
};
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
impl CreateOrUpdatePost {
pub(crate) async fn new(
post: &ApubPost,
post: ApubPost,
actor: &ApubPerson,
community: &ApubCommunity,
kind: CreateOrUpdateType,
@ -42,7 +40,7 @@ impl CreateOrUpdatePost {
Ok(CreateOrUpdatePost {
actor: ObjectId::new(actor.actor_id()),
to: vec![public()],
object: post.to_apub(context).await?,
object: post.into_apub(context).await?,
cc: vec![community.actor_id()],
kind,
id: id.clone(),
@ -50,7 +48,7 @@ impl CreateOrUpdatePost {
})
}
pub async fn send(
post: &ApubPost,
post: ApubPost,
actor: &ApubPerson,
kind: CreateOrUpdateType,
context: &LemmyContext,
@ -63,7 +61,7 @@ impl CreateOrUpdatePost {
.into();
let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
let id = create_or_update.id.clone();
let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
send_to_community(activity, &id, actor, &community, vec![], context).await
}
}
@ -76,16 +74,16 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
check_community_deleted_or_removed(&community)?;
match self.kind {
CreateOrUpdateType::Create => {
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
// Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
// However, when fetching a remote post we generate a new create activity with the current
// locked/stickied value, so this check may fail. So only check if its a local community,
@ -101,12 +99,12 @@ impl ActivityHandler for CreateOrUpdatePost {
if is_mod_action {
verify_mod_action(&self.actor, &community, context, request_counter).await?;
} else {
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
}
}
}
self.object.verify(context, request_counter).await?;
ApubPost::verify(&self.object, self.actor.inner(), context, request_counter).await?;
Ok(())
}
@ -115,9 +113,7 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context, request_counter).await?;
let post =
ApubPost::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
let post = ApubPost::from_apub(self.object, context, request_counter).await?;
let notif_type = match self.kind {
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,

View File

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

View File

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

View File

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

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::{
activities::{
community::{announce::GetCommunity, send_to_community},
@ -22,7 +8,6 @@ use crate::{
voting::{undo_vote_comment, undo_vote_post},
},
activity_lists::AnnouncableActivities,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::{
undo_vote::UndoVote,
@ -30,6 +15,17 @@ use crate::{
},
PostOrComment,
};
use activitystreams::{activity::kind::UndoType, public};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::verify_urls_match,
};
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
impl UndoVote {
pub async fn send(
@ -72,11 +68,11 @@ impl ActivityHandler for UndoVote {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_urls_match(self.actor(), self.object.actor())?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -93,7 +89,7 @@ impl ActivityHandler for UndoVote {
.dereference(context, request_counter)
.await?;
match object {
PostOrComment::Post(p) => undo_vote_post(actor, p.deref(), context).await,
PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
}
}

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

View File

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

View File

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

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

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_json::json;
use url::Url;
lazy_static! {
static ref CONTEXT: OneOrMany<AnyBase> = {
let context_ext = AnyBase::from_arbitrary_json(json!(
{
"sc": "http://schema.org#",
"sensitive": "as:sensitive",
"stickied": "as:stickied",
"pt": "https://join-lemmy.org#",
"comments_enabled": {
"type": "sc:Boolean",
"id": "pt:commentsEnabled"
},
"moderators": "as:moderators",
"matrixUserId": {
"type": "sc:Text",
"id": "as:alsoKnownAs"
},
}))
.expect("parse context");
OneOrMany::from(vec![
AnyBase::from(context()),
context_ext,
AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")),
])
};
static ref CONTEXT: OneOrMany<AnyBase> =
serde_json::from_str(include_str!("../assets/lemmy/context.json")).expect("parse context");
}
#[derive(Serialize, Deserialize)]

View File

@ -1,56 +1,3 @@
pub mod object_id;
pub mod post_or_comment;
pub mod search;
use crate::{
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
};
use chrono::NaiveDateTime;
use lemmy_apub_lib::traits::ActorType;
use lemmy_db_schema::naive_now;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
/// Get a remote actor from its apub ID (either a person or a community). Thin wrapper around
/// `get_or_fetch_and_upsert_person()` and `get_or_fetch_and_upsert_community()`.
///
/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database.
/// Otherwise it is fetched from the remote instance, stored and returned.
pub(crate) async fn get_or_fetch_and_upsert_actor(
apub_id: Url,
context: &LemmyContext,
recursion_counter: &mut i32,
) -> Result<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))
}
pub mod user_or_community;

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

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

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 {
Ok(create_apub_response(&comment.to_apub(&**context).await?))
Ok(create_apub_response(&comment.into_apub(&**context).await?))
} else {
Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
}

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize};
use url::Url;

View File

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

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::{
chrono::{DateTime, FixedOffset},
unparsed::Unparsed,
};
use anyhow::anyhow;
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use url::Url;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatMessage {
pub(crate) r#type: ChatMessageType,
pub(crate) id: Url,
pub(crate) id: ObjectId<ApubPrivateMessage>,
pub(crate) attributed_to: ObjectId<ApubPerson>,
pub(crate) to: [ObjectId<ApubPerson>; 1],
pub(crate) content: String,
@ -33,29 +32,3 @@ pub struct ChatMessage {
pub enum ChatMessageType {
ChatMessage,
}
impl ChatMessage {
pub(crate) fn id_unchecked(&self) -> &Url {
&self.id
}
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
pub(crate) async fn verify(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_domains_match(self.attributed_to.inner(), &self.id)?;
let person = self
.attributed_to
.dereference(context, request_counter)
.await?;
if person.banned {
return Err(anyhow!("Person is banned from site").into());
}
Ok(())
}
}

View File

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

View File

@ -1,3 +1,6 @@
use serde::{Deserialize, Serialize};
use url::Url;
pub(crate) mod chat_message;
pub(crate) mod group;
pub(crate) mod note;
@ -5,6 +8,13 @@ pub(crate) mod page;
pub(crate) mod person;
pub(crate) mod tombstone;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Endpoints {
#[serde(skip_serializing_if = "Option::is_none")]
pub shared_inbox: Option<Url>,
}
#[cfg(test)]
mod tests {
use crate::protocol::{

View File

@ -1,19 +1,13 @@
use crate::{
activities::{verify_is_public, verify_person_in_community},
fetcher::{object_id::ObjectId, post_or_comment::PostOrComment},
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
fetcher::post_or_comment::PostOrComment,
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
protocol::Source,
};
use activitystreams::{object::kind::NoteType, unparsed::Unparsed};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
use lemmy_db_schema::{
newtypes::CommentId,
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
@ -26,12 +20,11 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct Note {
pub(crate) r#type: NoteType,
pub(crate) id: Url,
pub(crate) id: ObjectId<ApubComment>,
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>,
#[serde(default)]
pub(crate) cc: Vec<Url>,
pub(crate) content: String,
pub(crate) media_type: Option<MediaTypeHtml>,
pub(crate) source: SourceCompat,
@ -52,14 +45,6 @@ pub(crate) enum SourceCompat {
}
impl Note {
pub(crate) fn id_unchecked(&self) -> &Url {
&self.id
}
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
pub(crate) async fn get_parents(
&self,
context: &LemmyContext,
@ -87,26 +72,4 @@ impl Note {
}
}
}
pub(crate) async fn verify(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
let community_id = post.community_id;
let community: ApubCommunity = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??
.into();
if post.locked {
return Err(anyhow!("Post is locked").into());
}
verify_domains_match(self.attributed_to.inner(), &self.id)?;
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
verify_is_public(&self.to)?;
Ok(())
}
}

View File

@ -1,14 +1,12 @@
use crate::{
activities::{verify_is_public, verify_person_in_community},
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{ImageObject, Source},
};
use activitystreams::{object::kind::PageType, unparsed::Unparsed};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset};
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
use lemmy_utils::{utils::check_slurs, LemmyError};
use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
@ -19,9 +17,11 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct Page {
pub(crate) r#type: PageType,
pub(crate) id: Url,
pub(crate) id: ObjectId<ApubPost>,
pub(crate) attributed_to: ObjectId<ApubPerson>,
pub(crate) to: Vec<Url>,
#[serde(default)]
pub(crate) cc: Vec<Url>,
pub(crate) name: String,
pub(crate) content: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,
@ -38,14 +38,6 @@ pub struct Page {
}
impl Page {
pub(crate) fn id_unchecked(&self) -> &Url {
&self.id
}
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
/// the current value, it is a mod action and needs to be verified as such.
///
@ -63,20 +55,6 @@ impl Page {
Ok(is_mod_action)
}
pub(crate) async fn verify(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.extract_community(context, request_counter).await?;
check_slurs(&self.name, &context.settings().slur_regex())?;
verify_domains_match(self.attributed_to.inner(), &self.id.clone())?;
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
verify_is_public(&self.to.clone())?;
Ok(())
}
pub(crate) async fn extract_community(
&self,
context: &LemmyContext,

View File

@ -1,7 +1,10 @@
use crate::protocol::{ImageObject, Source};
use activitystreams::{actor::Endpoints, unparsed::Unparsed, url::Url};
use crate::{
objects::person::ApubPerson,
protocol::{objects::Endpoints, ImageObject, Source},
};
use activitystreams::{unparsed::Unparsed, url::Url};
use chrono::{DateTime, FixedOffset};
use lemmy_apub_lib::signatures::PublicKey;
use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
@ -17,7 +20,7 @@ pub enum UserTypes {
pub struct Person {
#[serde(rename = "type")]
pub(crate) kind: UserTypes,
pub(crate) id: Url,
pub(crate) id: ObjectId<ApubPerson>,
/// username, set at account creation and can never be changed
pub(crate) preferred_username: String,
/// displayname (can be changed at any time)
@ -32,7 +35,7 @@ pub struct Person {
pub(crate) inbox: Url,
/// mandatory field in activitypub, currently empty in lemmy
pub(crate) outbox: Url,
pub(crate) endpoints: Endpoints<Url>,
pub(crate) endpoints: Endpoints,
pub(crate) public_key: PublicKey,
pub(crate) published: 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-reqwest = { version = "0.2.0", default-features = false, features = ["sha-2"] }
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 data;
pub mod object_id;
pub mod signatures;
pub mod traits;
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 diesel::NotFound;
use lemmy_apub_lib::{traits::ApubObject, APUB_JSON_CONTENT_TYPE};
use lemmy_db_schema::newtypes::DbUrl;
use lemmy_utils::{
request::{build_user_agent, retry},
settings::structs::Settings,
@ -29,9 +28,10 @@ lazy_static! {
.unwrap();
}
/// We store Url on the heap because it is quite large (88 bytes).
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
#[serde(transparent)]
pub struct ObjectId<Kind>(Url, #[serde(skip)] PhantomData<Kind>)
pub struct ObjectId<Kind>(Box<Url>, #[serde(skip)] PhantomData<Kind>)
where
Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>;
@ -45,7 +45,7 @@ where
where
T: Into<Url>,
{
ObjectId(url.into(), PhantomData::<Kind>)
ObjectId(Box::new(url.into()), PhantomData::<Kind>)
}
pub fn inner(&self) -> &Url {
@ -104,7 +104,7 @@ where
data: &<Kind as ApubObject>::DataType,
) -> Result<Option<Kind>, LemmyError> {
let id = self.0.clone();
ApubObject::read_from_apub_id(id, data).await
ApubObject::read_from_apub_id(*id, data).await
}
async fn dereference_from_http(
@ -140,16 +140,39 @@ where
let res2: Kind::ApubType = res.json().await?;
Ok(Kind::from_apub(&res2, data, self.inner(), request_counter).await?)
Kind::verify(&res2, self.inner(), data, request_counter).await?;
Ok(Kind::from_apub(res2, data, request_counter).await?)
}
}
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
/// Determines when a remote actor should be refetched from its instance. In release builds, this is
/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds
/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`.
///
/// TODO it won't pick up new avatars, summaries etc until a day after.
/// Actors need an "update" activity pushed to other servers to fix this.
fn should_refetch_object(last_refreshed: NaiveDateTime) -> bool {
let update_interval = if cfg!(debug_assertions) {
// avoid infinite loop when fetching community outbox
ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG)
} else {
ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS)
};
let refresh_limit = Utc::now().naive_utc() - update_interval;
last_refreshed.lt(&refresh_limit)
}
impl<Kind> Display for ObjectId<Kind>
where
Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{
#[allow(clippy::to_string_in_display)]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Use to_string here because Url.display is not useful for us
write!(f, "{}", self.0.to_string())
}
}
@ -160,16 +183,21 @@ where
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{
fn from(id: ObjectId<Kind>) -> Self {
id.0
*id.0
}
}
impl<Kind> From<ObjectId<Kind>> for DbUrl
where
Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
{
fn from(id: ObjectId<Kind>) -> Self {
id.0.into()
#[cfg(test)]
mod tests {
use super::*;
use crate::object_id::should_refetch_object;
#[test]
fn test_should_refetch_object() {
let one_second_ago = Utc::now().naive_utc() - ChronoDuration::seconds(1);
assert!(!should_refetch_object(one_second_ago));
let two_days_ago = Utc::now().naive_utc() - ChronoDuration::days(2);
assert!(should_refetch_object(two_days_ago));
}
}

View File

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

View File

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

View File

@ -127,40 +127,3 @@ fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream)
_ => unimplemented!(),
}
}
#[proc_macro_derive(ActivityFields)]
pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = match input.data {
Data::Enum(e) => {
let variants = e.variants;
let impl_id = variants
.iter()
.map(|v| generate_match_arm(&name, v, &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]
lemmy_utils = { version = "=0.13.5-rc.7", path = "../utils" }
lemmy_apub_lib = { version = "=0.13.5-rc.7", path = "../apub_lib" }
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] }

View File

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