make ObjectId a newtype

rewrite-fetcher
Felix Ableitner 2021-09-23 14:12:33 +02:00
parent 171d17e00d
commit 13eca1b106
36 changed files with 443 additions and 343 deletions

View File

@ -10,6 +10,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{comment::Note, FromApub, ToApub},
ActorType,
};
@ -26,7 +27,7 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdateComment {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: Note,
cc: Vec<Url>,
@ -60,7 +61,7 @@ impl CreateOrUpdateComment {
let maa = collect_non_local_mentions(comment, &community, context).await?;
let create_or_update = CreateOrUpdateComment {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: comment.to_apub(context.pool()).await?,
cc: maa.ccs,
@ -84,11 +85,11 @@ impl ActivityHandler for CreateOrUpdateComment {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = extract_community(&self.cc, context, request_counter).await?;
let community_id = ObjectId::<Community>::new(community.actor_id());
verify_activity(self)?;
verify_person_in_community(&self.actor, &community.actor_id(), context, request_counter)
.await?;
verify_domains_match(&self.actor, self.object.id_unchecked())?;
verify_person_in_community(&self.actor, &community_id, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
// 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?;
@ -100,7 +101,8 @@ impl ActivityHandler for CreateOrUpdateComment {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let comment = Comment::from_apub(&self.object, context, &self.actor, request_counter).await?;
let comment =
Comment::from_apub(&self.object, context, self.actor.inner(), 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,4 +1,4 @@
use crate::{fetcher::dereference_object_id::dereference, ActorType};
use crate::{fetcher::object_id::ObjectId, ActorType};
use activitystreams::{
base::BaseExt,
link::{LinkExt, Mention},
@ -26,14 +26,14 @@ use url::Url;
pub mod create_or_update;
async fn get_notif_recipients(
actor: &Url,
actor: &ObjectId<Person>,
comment: &Comment,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Vec<LocalUserId>, LemmyError> {
let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let actor = dereference::<Person>(actor, context, request_counter).await?;
let actor = actor.dereference(context, request_counter).await?;
// Note:
// Although mentions could be gotten from the post tags (they are included there), or the ccs,
@ -76,14 +76,17 @@ pub async fn collect_non_local_mentions(
for mention in &mentions {
// TODO should it be fetching it every time?
if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
let actor_id: ObjectId<Person> = ObjectId::<Person>::new(actor_id);
debug!("mention actor_id: {}", actor_id);
addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
let mention_person = dereference::<Person>(&actor_id, context, &mut 0).await?;
let mention_person = actor_id.dereference(context, &mut 0).await?;
inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
let mut mention_tag = Mention::new();
mention_tag.set_href(actor_id).set_name(mention.full_name());
mention_tag
.set_href(actor_id.into())
.set_name(mention.full_name());
tags.push(mention_tag);
}
}

View File

@ -9,7 +9,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
generate_moderators_url,
ActorType,
};
@ -34,11 +34,11 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AddMod {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: Url,
object: ObjectId<Person>,
target: Url,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: AddType,
id: Url,
@ -57,11 +57,11 @@ impl AddMod {
) -> Result<(), LemmyError> {
let id = generate_activity_id(AddType::Add)?;
let add = AddMod {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: added_mod.actor_id(),
object: ObjectId::<Person>::new(added_mod.actor_id()),
target: generate_moderators_url(&community.actor_id)?.into(),
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: AddType::Add,
id: id.clone(),
context: lemmy_context(),
@ -84,7 +84,7 @@ impl ActivityHandler for AddMod {
verify_activity(self)?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, self.cc[0].clone(), context).await?;
verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?;
verify_add_remove_moderator_target(&self.target, &self.cc[0])?;
Ok(())
}
@ -93,8 +93,8 @@ impl ActivityHandler for AddMod {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = dereference::<Community>(&self.cc[0], context, request_counter).await?;
let new_mod = dereference::<Person>(&self.object, context, request_counter).await?;
let community = self.cc[0].dereference(context, request_counter).await?;
let new_mod = self.object.dereference(context, request_counter).await?;
// If we had to refetch the community while parsing the activity, then the new mod has already
// been added. Skip it here as it would result in a duplicate key error.

View File

@ -19,6 +19,7 @@ use crate::{
},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
http::is_activity_already_known,
insert_activity,
ActorType,
@ -57,7 +58,7 @@ pub enum AnnouncableActivities {
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AnnounceActivity {
actor: Url,
actor: ObjectId<Community>,
to: [PublicUrl; 1],
object: AnnouncableActivities,
cc: Vec<Url>,
@ -78,7 +79,7 @@ impl AnnounceActivity {
context: &LemmyContext,
) -> Result<(), LemmyError> {
let announce = AnnounceActivity {
actor: community.actor_id(),
actor: ObjectId::<Community>::new(community.actor_id()),
to: [PublicUrl::Public],
object,
cc: vec![community.followers_url()],

View File

@ -8,7 +8,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -38,10 +38,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct BlockUserFromCommunity {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
pub(in crate::activities::community) object: Url,
cc: [Url; 1],
pub(in crate::activities::community) object: ObjectId<Person>,
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: BlockType,
id: Url,
@ -58,10 +58,10 @@ impl BlockUserFromCommunity {
actor: &Person,
) -> Result<BlockUserFromCommunity, LemmyError> {
Ok(BlockUserFromCommunity {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: target.actor_id(),
cc: [community.actor_id()],
object: ObjectId::<Person>::new(target.actor_id()),
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: BlockType::Block,
id: generate_activity_id(BlockType::Block)?,
context: lemmy_context(),
@ -102,8 +102,8 @@ impl ActivityHandler for BlockUserFromCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = dereference::<Community>(&self.cc[0], context, request_counter).await?;
let blocked_user = dereference::<Person>(&self.object, context, request_counter).await?;
let community = self.cc[0].dereference(context, request_counter).await?;
let blocked_user = self.object.dereference(context, request_counter).await?;
let community_user_ban_form = CommunityPersonBanForm {
community_id: community.id,

View File

@ -10,7 +10,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
generate_moderators_url,
ActorType,
};
@ -35,10 +35,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct RemoveMod {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
pub(in crate::activities) object: Url,
cc: [Url; 1],
pub(in crate::activities) object: ObjectId<Person>,
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: RemoveType,
// if target is set, this is means remove mod from community
@ -59,13 +59,13 @@ impl RemoveMod {
) -> Result<(), LemmyError> {
let id = generate_activity_id(RemoveType::Remove)?;
let remove = RemoveMod {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: removed_mod.actor_id(),
object: ObjectId::<Person>::new(removed_mod.actor_id()),
target: Some(generate_moderators_url(&community.actor_id)?.into()),
id: id.clone(),
context: lemmy_context(),
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: RemoveType::Remove,
unparsed: Default::default(),
};
@ -87,10 +87,10 @@ impl ActivityHandler for RemoveMod {
if let Some(target) = &self.target {
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_mod_action(&self.actor, self.cc[0].clone(), context).await?;
verify_add_remove_moderator_target(target, self.cc[0].clone())?;
verify_add_remove_moderator_target(target, &self.cc[0])?;
} else {
verify_delete_activity(
&self.object,
self.object.inner(),
self,
&self.cc[0],
true,
@ -108,8 +108,8 @@ impl ActivityHandler for RemoveMod {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
if self.target.is_some() {
let community = dereference::<Community>(&self.cc[0], context, request_counter).await?;
let remove_mod = dereference::<Person>(&self.object, context, request_counter).await?;
let community = self.cc[0].dereference(context, request_counter).await?;
let remove_mod = self.object.dereference(context, request_counter).await?;
let form = CommunityModeratorForm {
community_id: community.id,
@ -122,7 +122,14 @@ impl ActivityHandler for RemoveMod {
// TODO: send websocket notification about removed mod
Ok(())
} else {
receive_remove_action(&self.actor, &self.object, None, context, request_counter).await
receive_remove_action(
&self.actor,
self.object.inner(),
None,
context,
request_counter,
)
.await
}
}
}

View File

@ -8,7 +8,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -32,10 +32,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoBlockUserFromCommunity {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: BlockUserFromCommunity,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
@ -56,10 +56,10 @@ impl UndoBlockUserFromCommunity {
let id = generate_activity_id(UndoType::Undo)?;
let undo = UndoBlockUserFromCommunity {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: block,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
@ -91,8 +91,12 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = dereference::<Community>(&self.cc[0], context, request_counter).await?;
let blocked_user = dereference::<Person>(&self.object.object, context, request_counter).await?;
let community = self.cc[0].dereference(context, request_counter).await?;
let blocked_user = self
.object
.object
.dereference(context, request_counter)
.await?;
let community_user_ban_form = CommunityPersonBanForm {
community_id: community.id,

View File

@ -8,6 +8,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::Group, ToApub},
ActorType,
};
@ -34,11 +35,11 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UpdateCommunity {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
// TODO: would be nice to use a separate struct here, which only contains the fields updated here
object: Group,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: UpdateType,
id: Url,
@ -56,10 +57,10 @@ impl UpdateCommunity {
) -> Result<(), LemmyError> {
let id = generate_activity_id(UpdateType::Update)?;
let update = UpdateCommunity {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: community.to_apub(context.pool()).await?,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: UpdateType::Update,
id: id.clone(),
context: lemmy_context(),

View File

@ -12,7 +12,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -62,10 +62,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct Delete {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
pub(in crate::activities::deletion) object: Url,
pub(in crate::activities::deletion) cc: [Url; 1],
pub(in crate::activities::deletion) cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: DeleteType,
/// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user
@ -138,10 +138,10 @@ impl Delete {
summary: Option<String>,
) -> Result<Delete, LemmyError> {
Ok(Delete {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: object_id,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: DeleteType::Delete,
summary,
id: generate_activity_id(DeleteType::Delete)?,
@ -165,13 +165,13 @@ impl Delete {
}
pub(in crate::activities) async fn receive_remove_action(
actor: &Url,
actor: &ObjectId<Person>,
object: &Url,
reason: Option<String>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(actor, context, request_counter).await?;
let actor = actor.dereference(context, request_counter).await?;
use UserOperationCrud::*;
match DeletableObjects::read_from_db(object, context).await? {
DeletableObjects::Community(community) => {

View File

@ -4,7 +4,7 @@ use crate::{
verify_mod_action,
verify_person_in_community,
},
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use lemmy_api_common::blocking;
@ -99,22 +99,22 @@ impl DeletableObjects {
pub(in crate::activities) async fn verify_delete_activity(
object: &Url,
activity: &dyn ActivityFields,
community_id: &Url,
community_id: &ObjectId<Community>,
is_mod_action: bool,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let object = DeletableObjects::read_from_db(object, context).await?;
let actor = ObjectId::<Person>::new(activity.actor().clone());
match object {
DeletableObjects::Community(c) => {
if c.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(activity.actor(), community_id, context, request_counter)
.await?;
verify_person_in_community(&actor, community_id, context, request_counter).await?;
}
// community deletion is always a mod (or admin) action
verify_mod_action(activity.actor(), c.actor_id(), context).await?;
verify_mod_action(&actor, ObjectId::<Community>::new(c.actor_id()), context).await?;
}
DeletableObjects::Post(p) => {
verify_delete_activity_post_or_comment(
@ -145,14 +145,15 @@ pub(in crate::activities) async fn verify_delete_activity(
async fn verify_delete_activity_post_or_comment(
activity: &dyn ActivityFields,
object_id: &Url,
community_id: &Url,
community_id: &ObjectId<Community>,
is_mod_action: bool,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_person_in_community(activity.actor(), community_id, context, request_counter).await?;
let actor = ObjectId::<Person>::new(activity.actor().clone());
verify_person_in_community(&actor, community_id, context, request_counter).await?;
if is_mod_action {
verify_mod_action(activity.actor(), community_id.clone(), context).await?;
verify_mod_action(&actor, community_id.clone(), context).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)?;
@ -171,7 +172,7 @@ struct WebsocketMessages {
/// because of the mod log
async fn receive_delete_action(
object: &Url,
actor: &Url,
actor: &ObjectId<Person>,
ws_messages: WebsocketMessages,
deleted: bool,
context: &LemmyContext,
@ -180,7 +181,7 @@ async fn receive_delete_action(
match DeletableObjects::read_from_db(object, context).await? {
DeletableObjects::Community(community) => {
if community.local {
let mod_ = dereference::<Person>(actor, context, request_counter).await?;
let mod_ = actor.dereference(context, request_counter).await?;
let object = community.actor_id();
send_apub_delete(&mod_, &community.clone(), object, true, context).await?;
}

View File

@ -13,6 +13,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -38,10 +39,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoDelete {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: Delete,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
@ -109,10 +110,10 @@ impl UndoDelete {
let id = generate_activity_id(UndoType::Undo)?;
let undo = UndoDelete {
actor: actor.actor_id(),
actor: ObjectId::<Community>::new(actor.actor_id()),
to: [PublicUrl::Public],
object,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),

View File

@ -7,7 +7,7 @@ use crate::{
},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -31,8 +31,8 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AcceptFollowCommunity {
actor: Url,
to: Url,
actor: ObjectId<Community>,
to: ObjectId<Person>,
object: FollowCommunity,
#[serde(rename = "type")]
kind: AcceptType,
@ -57,8 +57,8 @@ impl AcceptFollowCommunity {
.await??;
let accept = AcceptFollowCommunity {
actor: community.actor_id(),
to: person.actor_id(),
actor: ObjectId::<Community>::new(community.actor_id()),
to: ObjectId::<Person>::new(person.actor_id()),
object: follow,
kind: AcceptType::Accept,
id: generate_activity_id(AcceptType::Accept)?,
@ -78,8 +78,8 @@ impl ActivityHandler for AcceptFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_urls_match(&self.to, self.object.actor())?;
verify_urls_match(&self.actor, &self.object.to)?;
verify_urls_match(self.to.inner(), self.object.actor())?;
verify_urls_match(self.actor(), self.object.to.inner())?;
verify_community(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
@ -90,8 +90,8 @@ impl ActivityHandler for AcceptFollowCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Community>(&self.actor, context, request_counter).await?;
let to = dereference::<Person>(&self.to, context, request_counter).await?;
let actor = self.actor.dereference(context, request_counter).await?;
let to = self.to.dereference(context, request_counter).await?;
// This will throw an error if no follow was requested
blocking(context.pool(), move |conn| {
CommunityFollower::follow_accepted(conn, actor.id, to.id)

View File

@ -7,7 +7,7 @@ use crate::{
},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -31,9 +31,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct FollowCommunity {
actor: Url,
pub(in crate::activities::following) to: Url,
pub(in crate::activities::following) object: Url,
actor: ObjectId<Person>,
// TODO: is there any reason to put the same community id twice, in to and object?
pub(in crate::activities::following) to: ObjectId<Community>,
pub(in crate::activities::following) object: ObjectId<Community>,
#[serde(rename = "type")]
kind: FollowType,
id: Url,
@ -49,9 +50,9 @@ impl FollowCommunity {
community: &Community,
) -> Result<FollowCommunity, LemmyError> {
Ok(FollowCommunity {
actor: actor.actor_id(),
to: community.actor_id(),
object: community.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: ObjectId::<Community>::new(community.actor_id()),
object: ObjectId::<Community>::new(community.actor_id()),
kind: FollowType::Follow,
id: generate_activity_id(FollowType::Follow)?,
context: lemmy_context(),
@ -87,7 +88,7 @@ impl ActivityHandler for FollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_urls_match(&self.to, &self.object)?;
verify_urls_match(self.to.inner(), self.object.inner())?;
verify_person(&self.actor, context, request_counter).await?;
Ok(())
}
@ -97,8 +98,8 @@ impl ActivityHandler for FollowCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(&self.actor, context, request_counter).await?;
let community = dereference::<Community>(&self.object, context, request_counter).await?;
let actor = self.actor.dereference(context, request_counter).await?;
let community = self.object.dereference(context, request_counter).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
person_id: actor.id,

View File

@ -7,7 +7,7 @@ use crate::{
},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -31,8 +31,8 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoFollowCommunity {
actor: Url,
to: Url,
actor: ObjectId<Person>,
to: ObjectId<Community>,
object: FollowCommunity,
#[serde(rename = "type")]
kind: UndoType,
@ -51,8 +51,8 @@ impl UndoFollowCommunity {
) -> Result<(), LemmyError> {
let object = FollowCommunity::new(actor, community)?;
let undo = UndoFollowCommunity {
actor: actor.actor_id(),
to: community.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: ObjectId::<Community>::new(community.actor_id()),
object,
kind: UndoType::Undo,
id: generate_activity_id(UndoType::Undo)?,
@ -72,8 +72,8 @@ impl ActivityHandler for UndoFollowCommunity {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_urls_match(&self.to, &self.object.object)?;
verify_urls_match(&self.actor, self.object.actor())?;
verify_urls_match(self.to.inner(), self.object.object.inner())?;
verify_urls_match(self.actor(), self.object.actor())?;
verify_person(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
@ -84,8 +84,8 @@ impl ActivityHandler for UndoFollowCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(&self.actor, context, request_counter).await?;
let community = dereference::<Community>(&self.to, context, request_counter).await?;
let actor = self.actor.dereference(context, request_counter).await?;
let community = self.to.dereference(context, request_counter).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,

View File

@ -1,7 +1,7 @@
use crate::{
check_community_or_site_ban,
check_is_apub_id_valid,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
generate_moderators_url,
};
use anyhow::anyhow;
@ -39,11 +39,11 @@ pub enum CreateOrUpdateType {
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
/// doesn't have a site ban.
async fn verify_person(
person_id: &Url,
person_id: &ObjectId<Person>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let person = dereference::<Person>(person_id, context, request_counter).await?;
let person = person_id.dereference(context, request_counter).await?;
if person.banned {
return Err(anyhow!("Person {} is banned", person_id).into());
}
@ -58,7 +58,8 @@ pub(crate) async fn extract_community(
let mut cc_iter = cc.iter();
loop {
if let Some(cid) = cc_iter.next() {
if let Ok(c) = dereference(cid, context, request_counter).await {
let cid = ObjectId::<Community>::new(cid.clone());
if let Ok(c) = cid.dereference(context, request_counter).await {
break Ok(c);
}
} else {
@ -70,23 +71,23 @@ pub(crate) async fn extract_community(
/// Fetches the person and community to verify their type, then checks if person is banned from site
/// or community.
pub(crate) async fn verify_person_in_community(
person_id: &Url,
community_id: &Url,
person_id: &ObjectId<Person>,
community_id: &ObjectId<Community>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = dereference::<Community>(community_id, context, request_counter).await?;
let person = dereference::<Person>(person_id, context, request_counter).await?;
let community = community_id.dereference(context, request_counter).await?;
let person = person_id.dereference(context, request_counter).await?;
check_community_or_site_ban(&person, community.id, context.pool()).await
}
/// Simply check that the url actually refers to a valid group.
async fn verify_community(
community_id: &Url,
community_id: &ObjectId<Community>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
dereference::<Community>(community_id, context, request_counter).await?;
community_id.dereference(context, request_counter).await?;
Ok(())
}
@ -100,12 +101,12 @@ fn verify_activity(activity: &dyn ActivityFields) -> Result<(), LemmyError> {
/// because in case of remote communities, admins can also perform mod actions. As admin status
/// is not federated, we cant verify their actions remotely.
pub(crate) async fn verify_mod_action(
actor_id: &Url,
community: Url,
actor_id: &ObjectId<Person>,
community_id: ObjectId<Community>,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &community.into())
Community::read_from_apub_id(conn, &community_id.into())
})
.await??;
@ -133,8 +134,11 @@ pub(crate) async fn verify_mod_action(
/// For Add/Remove community moderator activities, check that the target field actually contains
/// /c/community/moderators. Any different values are unsupported.
fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<(), LemmyError> {
if target != &generate_moderators_url(&community.into())?.into_inner() {
fn verify_add_remove_moderator_target(
target: &Url,
community: &ObjectId<Community>,
) -> Result<(), LemmyError> {
if target != &generate_moderators_url(&community.clone().into())?.into_inner() {
return Err(anyhow!("Unkown target url").into());
}
Ok(())

View File

@ -1,7 +1,6 @@
use crate::{
activities::{
community::announce::AnnouncableActivities,
extract_community,
generate_activity_id,
verify_activity,
verify_mod_action,
@ -10,7 +9,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
objects::{post::Page, FromApub, ToApub},
ActorType,
};
@ -34,10 +33,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePost {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: Page,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: CreateOrUpdateType,
id: Url,
@ -62,10 +61,10 @@ impl CreateOrUpdatePost {
let id = generate_activity_id(kind.clone())?;
let create_or_update = CreateOrUpdatePost {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: post.to_apub(context.pool()).await?,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind,
id: id.clone(),
context: lemmy_context(),
@ -85,13 +84,12 @@ impl ActivityHandler for CreateOrUpdatePost {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self)?;
let community = extract_community(&self.cc, context, request_counter).await?;
let community_id = community.actor_id();
verify_person_in_community(&self.actor, &community_id, context, request_counter).await?;
let community = self.cc[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
match self.kind {
CreateOrUpdateType::Create => {
verify_domains_match(&self.actor, self.object.id_unchecked())?;
verify_urls_match(&self.actor, &self.object.attributed_to)?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
verify_urls_match(self.actor(), 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,
@ -105,10 +103,10 @@ impl ActivityHandler for CreateOrUpdatePost {
CreateOrUpdateType::Update => {
let is_mod_action = self.object.is_mod_action(context.pool()).await?;
if is_mod_action {
verify_mod_action(&self.actor, community_id, context).await?;
verify_mod_action(&self.actor, self.cc[0].clone(), context).await?;
} else {
verify_domains_match(&self.actor, self.object.id_unchecked())?;
verify_urls_match(&self.actor, &self.object.attributed_to)?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
}
}
}
@ -121,7 +119,7 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(&self.actor, context, request_counter).await?;
let actor = self.actor.dereference(context, request_counter).await?;
let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
let notif_type = match self.kind {

View File

@ -2,6 +2,7 @@ use crate::{
activities::{generate_activity_id, verify_activity, verify_person, CreateOrUpdateType},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{private_message::Note, FromApub, ToApub},
ActorType,
};
@ -21,9 +22,8 @@ pub struct CreateOrUpdatePrivateMessage {
#[serde(rename = "@context")]
pub context: OneOrMany<AnyBase>,
id: Url,
actor: Url,
to: Url,
cc: [Url; 0],
actor: ObjectId<Person>,
to: ObjectId<Person>,
object: Note,
#[serde(rename = "type")]
kind: CreateOrUpdateType,
@ -46,9 +46,8 @@ impl CreateOrUpdatePrivateMessage {
let create_or_update = CreateOrUpdatePrivateMessage {
context: lemmy_context(),
id: id.clone(),
actor: actor.actor_id(),
to: recipient.actor_id(),
cc: [],
actor: ObjectId::<Person>::new(actor.actor_id()),
to: ObjectId::<Person>::new(recipient.actor_id()),
object: private_message.to_apub(context.pool()).await?,
kind,
unparsed: Default::default(),
@ -66,7 +65,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_person(&self.actor, context, request_counter).await?;
verify_domains_match(&self.actor, self.object.id_unchecked())?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -77,7 +76,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let private_message =
PrivateMessage::from_apub(&self.object, context, &self.actor, request_counter).await?;
PrivateMessage::from_apub(&self.object, context, self.actor.inner(), request_counter).await?;
let notif_type = match self.kind {
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,

View File

@ -2,6 +2,7 @@ use crate::{
activities::{generate_activity_id, verify_activity, verify_person},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -22,8 +23,8 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct DeletePrivateMessage {
actor: Url,
to: Url,
actor: ObjectId<Person>,
to: ObjectId<Person>,
pub(in crate::activities::private_message) object: Url,
#[serde(rename = "type")]
kind: DeleteType,
@ -40,8 +41,8 @@ impl DeletePrivateMessage {
pm: &PrivateMessage,
) -> Result<DeletePrivateMessage, LemmyError> {
Ok(DeletePrivateMessage {
actor: actor.actor_id(),
to: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: ObjectId::<Person>::new(actor.actor_id()),
object: pm.ap_id.clone().into(),
kind: DeleteType::Delete,
id: generate_activity_id(DeleteType::Delete)?,
@ -74,7 +75,7 @@ impl ActivityHandler for DeletePrivateMessage {
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_person(&self.actor, context, request_counter).await?;
verify_domains_match(&self.actor, &self.object)?;
verify_domains_match(self.actor.inner(), &self.object)?;
Ok(())
}

View File

@ -7,6 +7,7 @@ use crate::{
},
activity_queue::send_activity_new,
extensions::context::lemmy_context,
fetcher::object_id::ObjectId,
ActorType,
};
use activitystreams::{
@ -27,8 +28,8 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeletePrivateMessage {
actor: Url,
to: Url,
actor: ObjectId<Person>,
to: ObjectId<Person>,
object: DeletePrivateMessage,
#[serde(rename = "type")]
kind: UndoType,
@ -52,8 +53,8 @@ impl UndoDeletePrivateMessage {
let object = DeletePrivateMessage::new(actor, pm)?;
let id = generate_activity_id(UndoType::Undo)?;
let undo = UndoDeletePrivateMessage {
actor: actor.actor_id(),
to: recipient.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: ObjectId::<Person>::new(recipient.actor_id()),
object,
kind: UndoType::Undo,
id: id.clone(),
@ -74,8 +75,8 @@ impl ActivityHandler for UndoDeletePrivateMessage {
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_person(&self.actor, context, request_counter).await?;
verify_urls_match(&self.actor, self.object.actor())?;
verify_domains_match(&self.actor, &self.object.object)?;
verify_urls_match(self.actor(), self.object.actor())?;
verify_domains_match(self.actor(), &self.object.object)?;
self.object.verify(context, request_counter).await?;
Ok(())
}

View File

@ -1,7 +1,10 @@
use crate::activities::{
use crate::{
activities::{
community::remove_mod::RemoveMod,
deletion::{undo_delete::UndoDelete, verify_delete_activity},
verify_activity,
},
fetcher::object_id::ObjectId,
};
use activitystreams::{
activity::kind::UndoType,
@ -10,6 +13,7 @@ use activitystreams::{
unparsed::Unparsed,
};
use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler};
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
@ -18,11 +22,11 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoRemovePostCommentOrCommunity {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
// Note, there is no such thing as Undo/Remove/Mod, so we ignore that
object: RemoveMod,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
@ -43,7 +47,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity {
self.object.verify(context, request_counter).await?;
verify_delete_activity(
&self.object.object,
self.object.object.inner(),
self,
&self.cc[0],
true,
@ -59,6 +63,6 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity {
context: &LemmyContext,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
UndoDelete::receive_undo_remove_action(&self.object.object, context).await
UndoDelete::receive_undo_remove_action(self.object.object.inner(), context).await
}
}

View File

@ -12,7 +12,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
PostOrComment,
};
@ -38,10 +38,10 @@ use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct UndoVote {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
object: Vote,
cc: [Url; 1],
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
kind: UndoType,
id: Url,
@ -67,10 +67,10 @@ impl UndoVote {
let object = Vote::new(object, actor, &community, kind.clone())?;
let id = generate_activity_id(UndoType::Undo)?;
let undo_vote = UndoVote {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object,
cc: [community.actor_id()],
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: UndoType::Undo,
id: id.clone(),
context: lemmy_context(),
@ -90,7 +90,7 @@ impl ActivityHandler for UndoVote {
) -> Result<(), LemmyError> {
verify_activity(self)?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
verify_urls_match(&self.actor, self.object.actor())?;
verify_urls_match(self.actor(), self.object.actor())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
@ -100,9 +100,12 @@ impl ActivityHandler for UndoVote {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(&self.actor, context, request_counter).await?;
let object =
dereference::<PostOrComment>(&self.object.object, context, request_counter).await?;
let actor = self.actor.dereference(context, request_counter).await?;
let object = self
.object
.object
.dereference(context, request_counter)
.await?;
match object {
PostOrComment::Post(p) => undo_vote_post(actor, p.deref(), context).await,
PostOrComment::Comment(c) => undo_vote_comment(actor, c.deref(), context).await,

View File

@ -8,7 +8,7 @@ use crate::{
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
ActorType,
PostOrComment,
};
@ -58,10 +58,10 @@ impl From<&VoteType> for i16 {
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct Vote {
actor: Url,
actor: ObjectId<Person>,
to: [PublicUrl; 1],
pub(in crate::activities::voting) object: Url,
cc: [Url; 1],
pub(in crate::activities::voting) object: ObjectId<PostOrComment>,
cc: [ObjectId<Community>; 1],
#[serde(rename = "type")]
pub(in crate::activities::voting) kind: VoteType,
id: Url,
@ -79,10 +79,10 @@ impl Vote {
kind: VoteType,
) -> Result<Vote, LemmyError> {
Ok(Vote {
actor: actor.actor_id(),
actor: ObjectId::<Person>::new(actor.actor_id()),
to: [PublicUrl::Public],
object: object.ap_id(),
cc: [community.actor_id()],
object: ObjectId::<PostOrComment>::new(object.ap_id()),
cc: [ObjectId::<Community>::new(community.actor_id())],
kind: kind.clone(),
id: generate_activity_id(kind)?,
context: lemmy_context(),
@ -126,8 +126,8 @@ impl ActivityHandler for Vote {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = dereference::<Person>(&self.actor, context, request_counter).await?;
let object = dereference::<PostOrComment>(&self.object, context, request_counter).await?;
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::Comment(c) => vote_comment(&self.kind, actor, c.deref(), context).await,

View File

@ -1,6 +1,6 @@
use crate::{
activities::community::announce::AnnounceActivity,
fetcher::{dereference_object_id::dereference, fetch::fetch_remote_object},
fetcher::{fetch::fetch_remote_object, object_id::ObjectId},
objects::community::Group,
};
use activitystreams::collection::{CollectionExt, OrderedCollection};
@ -44,8 +44,9 @@ pub(crate) async fn update_community_mods(
}
// Add new mods to database which have been added to moderators collection
for mod_uri in new_moderators {
let mod_user = dereference::<Person>(&mod_uri, context, request_counter).await?;
for mod_id in new_moderators {
let mod_id = ObjectId::<Person>::new::<Person, Url>(mod_id);
let mod_user = mod_id.dereference(context, request_counter).await?;
if !current_moderators
.clone()
@ -91,7 +92,7 @@ pub(crate) async fn fetch_community_outbox(
Ok(())
}
pub(crate) async fn fetch_community_mods(
async fn fetch_community_mods(
context: &LemmyContext,
group: &Group,
recursion_counter: &mut i32,

View File

@ -1,112 +0,0 @@
use crate::{fetcher::should_refetch_actor, objects::FromApub, APUB_JSON_CONTENT_TYPE};
use anyhow::anyhow;
use diesel::NotFound;
use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use log::debug;
use reqwest::StatusCode;
use std::time::Duration;
use url::Url;
/// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
/// fetch through the search). This should be configurable.
static REQUEST_LIMIT: i32 = 25;
/// Fetches an activitypub object, either from local database (if possible), or over http.
pub(crate) async fn dereference<Kind>(
id: &Url,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Kind, LemmyError>
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
let db_object = dereference_locally::<Kind>(id.clone(), context.pool()).await?;
// if its a local object, only fetch it from the database and not over http
if id.domain() == Some(&Settings::get().get_hostname_without_port()?) {
return match db_object {
None => Err(NotFound {}.into()),
Some(o) => Ok(o),
};
}
if let Some(object) = db_object {
if let Some(last_refreshed_at) = object.last_refreshed_at() {
// TODO: rename to should_refetch_object()
if should_refetch_actor(last_refreshed_at) {
debug!("Refetching remote object {}", id);
return dereference_remotely(id, context, request_counter).await;
}
}
Ok(object)
} else {
debug!("Fetching remote object {}", id);
dereference_remotely(id, context, request_counter).await
}
}
/// returning none means the object was not found in local db
async fn dereference_locally<Kind>(id: Url, pool: &DbPool) -> Result<Option<Kind>, LemmyError>
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
let object = blocking(pool, move |conn| {
ApubObject::read_from_apub_id(conn, &id.into())
})
.await?;
match object {
Ok(o) => Ok(Some(o)),
Err(NotFound {}) => Ok(None),
Err(e) => Err(e.into()),
}
}
async fn dereference_remotely<Kind>(
id: &Url,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Kind, LemmyError>
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
// dont fetch local objects this way
debug_assert!(id.domain() != Some(&Settings::get().hostname));
*request_counter += 1;
if *request_counter > REQUEST_LIMIT {
return Err(LemmyError::from(anyhow!("Request limit reached")));
}
let res = retry(|| {
context
.client()
.get(id.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE)
.timeout(Duration::from_secs(60))
.send()
})
.await?;
if res.status() == StatusCode::GONE {
mark_object_deleted::<Kind>(id, context).await?;
return Err(anyhow!("Fetched remote object {} which was deleted", id.to_string()).into());
}
let res2: Kind::ApubType = res.json().await?;
Ok(Kind::from_apub(&res2, context, id, request_counter).await?)
}
async fn mark_object_deleted<Kind>(_id: &Url, _context: &LemmyContext) -> Result<(), LemmyError>
where
Kind: FromApub + ApubObject + Send + 'static,
{
// TODO: need to move methods update_deleted, update_removed etc into a trait to use them here.
// also, how do we know if the object was deleted vs removed?
todo!()
}

View File

@ -1,11 +1,11 @@
pub mod community;
pub mod dereference_object_id;
mod fetch;
pub mod object_id;
pub mod post_or_comment;
pub mod search;
use crate::{
fetcher::{dereference_object_id::dereference, fetch::FetchError},
fetcher::{fetch::FetchError, object_id::ObjectId},
ActorType,
};
use chrono::NaiveDateTime;
@ -42,14 +42,19 @@ where
/// 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,
apub_id: Url,
context: &LemmyContext,
recursion_counter: &mut i32,
) -> Result<Box<dyn ActorType>, LemmyError> {
let community = dereference::<Community>(apub_id, context, recursion_counter).await;
let community_id: ObjectId<Community> = ObjectId::<Community>::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(_) => Box::new(dereference::<Person>(apub_id, context, recursion_counter).await?),
Err(_) => {
let person_id: ObjectId<Person> = ObjectId::<Person>::new(apub_id);
let person: Person = person_id.dereference(context, recursion_counter).await?;
Box::new(person)
}
};
Ok(actor)
}

View File

@ -0,0 +1,162 @@
use crate::{fetcher::should_refetch_actor, objects::FromApub, APUB_JSON_CONTENT_TYPE};
use anyhow::anyhow;
use diesel::NotFound;
use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::DbUrl;
use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use log::debug;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Display, Formatter},
marker::PhantomData,
time::Duration,
};
use url::Url;
/// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
/// fetch through the search). This should be configurable.
static REQUEST_LIMIT: i32 = 25;
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct ObjectId<Kind>(Url, #[serde(skip)] PhantomData<Kind>)
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de2> <Kind as FromApub>::ApubType: serde::Deserialize<'de2>;
impl<Kind> ObjectId<Kind>
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
pub fn new<K, T>(url: T) -> ObjectId<K>
where
T: Into<Url>,
K: FromApub + ApubObject + Send + 'static,
for<'de> <K as FromApub>::ApubType: serde::Deserialize<'de>,
{
ObjectId(url.into(), PhantomData::<K>)
}
pub fn inner(&self) -> &Url {
&self.0
}
/// Fetches an activitypub object, either from local database (if possible), or over http.
pub(crate) async fn dereference(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Kind, LemmyError> {
let db_object = self.dereference_locally(context.pool()).await?;
// if its a local object, only fetch it from the database and not over http
if self.0.domain() == Some(&Settings::get().get_hostname_without_port()?) {
return match db_object {
None => Err(NotFound {}.into()),
Some(o) => Ok(o),
};
}
if let Some(object) = db_object {
if let Some(last_refreshed_at) = object.last_refreshed_at() {
// TODO: rename to should_refetch_object()
if should_refetch_actor(last_refreshed_at) {
debug!("Refetching remote object {}", self.0.as_str());
return self.dereference_remotely(context, request_counter).await;
}
}
Ok(object)
} else {
debug!("Fetching remote object {}", self.0.as_str());
self.dereference_remotely(context, request_counter).await
}
}
/// returning none means the object was not found in local db
async fn dereference_locally(&self, pool: &DbPool) -> Result<Option<Kind>, LemmyError> {
let id: DbUrl = self.0.clone().into();
let object = blocking(pool, move |conn| ApubObject::read_from_apub_id(conn, &id)).await?;
match object {
Ok(o) => Ok(Some(o)),
Err(NotFound {}) => Ok(None),
Err(e) => Err(e.into()),
}
}
async fn dereference_remotely(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Kind, LemmyError> {
// dont fetch local objects this way
debug_assert!(self.0.domain() != Some(&Settings::get().hostname));
*request_counter += 1;
if *request_counter > REQUEST_LIMIT {
return Err(LemmyError::from(anyhow!("Request limit reached")));
}
let res = retry(|| {
context
.client()
.get(self.0.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE)
.timeout(Duration::from_secs(60))
.send()
})
.await?;
if res.status() == StatusCode::GONE {
self.mark_object_deleted(context).await?;
return Err(
anyhow!(
"Fetched remote object {} which was deleted",
self.0.as_str()
)
.into(),
);
}
let res2: Kind::ApubType = res.json().await?;
Ok(Kind::from_apub(&res2, context, self.inner(), request_counter).await?)
}
async fn mark_object_deleted(&self, _context: &LemmyContext) -> Result<(), LemmyError> {
// TODO: need to move methods update_deleted, update_removed etc into a trait to use them here.
// also, how do we know if the object was deleted vs removed?
todo!()
}
}
impl<Kind> Display for ObjectId<Kind>
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_string())
}
}
impl<Kind> From<ObjectId<Kind>> for Url
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
fn from(id: ObjectId<Kind>) -> Self {
id.0
}
}
impl<Kind> From<ObjectId<Kind>> for DbUrl
where
Kind: FromApub + ApubObject + Send + 'static,
for<'de> <Kind as FromApub>::ApubType: serde::Deserialize<'de>,
{
fn from(id: ObjectId<Kind>) -> Self {
id.0.into()
}
}

View File

@ -14,6 +14,7 @@ use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
#[derive(Clone, Debug)]
pub enum PostOrComment {
Comment(Box<Comment>),
Post(Box<Post>),

View File

@ -1,5 +1,5 @@
use crate::{
fetcher::{dereference_object_id::dereference, fetch::fetch_remote_object, is_deleted},
fetcher::{fetch::fetch_remote_object, is_deleted, object_id::ObjectId},
find_object_by_id,
objects::{comment::Note, community::Group, person::Person as ApubPerson, post::Page, FromApub},
Object,
@ -124,9 +124,9 @@ async fn build_response(
use ResolveObjectResponse as ROR;
Ok(match fetch_response {
SearchAcceptedObjects::Person(p) => {
let person_uri = p.id(&query_url)?;
let person_uri: ObjectId<Person> = ObjectId::<Person>::new(p.id(&query_url)?.clone());
let person = dereference::<Person>(person_uri, context, recursion_counter).await?;
let person = person_uri.dereference(context, recursion_counter).await?;
ROR {
person: blocking(context.pool(), move |conn| {
PersonViewSafe::read(conn, person.id)
@ -137,8 +137,11 @@ async fn build_response(
}
}
SearchAcceptedObjects::Group(g) => {
let community_uri = g.id(&query_url)?;
let community = dereference::<Community>(community_uri, context, recursion_counter).await?;
let community_uri: ObjectId<Community> =
ObjectId::<Community>::new(g.id(&query_url)?.clone());
let community = community_uri
.dereference(context, recursion_counter)
.await?;
ROR {
community: blocking(context.pool(), move |conn| {
CommunityView::read(conn, community.id, None)

View File

@ -90,7 +90,8 @@ where
+ 'static,
{
let request_counter = &mut 0;
let actor = get_or_fetch_and_upsert_actor(activity.actor(), context, request_counter).await?;
let actor =
get_or_fetch_and_upsert_actor(activity.actor().clone(), context, request_counter).await?;
verify_signature(&request, &actor.public_key().context(location_info!())?)?;
// Do nothing if we received the same activity before

View File

@ -1,3 +1,4 @@
use crate::fetcher::{object_id::ObjectId, post_or_comment::PostOrComment};
use serde::{Deserialize, Serialize};
use url::Url;
@ -13,7 +14,7 @@ use url::Url;
#[serde(untagged)]
pub enum CommentInReplyToMigration {
Old(Vec<Url>),
New(Url),
New(ObjectId<PostOrComment>),
}
// Another migration we are doing is to handle all deletions and removals using Delete activity.

View File

@ -1,7 +1,7 @@
use crate::{
activities::verify_person_in_community,
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
migrations::CommentInReplyToMigration,
objects::{create_tombstone, FromApub, Source, ToApub},
ActorType,
@ -49,7 +49,7 @@ pub struct Note {
context: OneOrMany<AnyBase>,
r#type: NoteType,
id: Url,
pub(crate) attributed_to: Url,
pub(crate) attributed_to: ObjectId<Person>,
/// 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`]).
@ -82,18 +82,15 @@ impl Note {
CommentInReplyToMigration::Old(in_reply_to) => {
// This post, or the parent comment might not yet exist on this server yet, fetch them.
let post_id = in_reply_to.get(0).context(location_info!())?;
let post = Box::pin(dereference::<Post>(post_id, context, request_counter)).await?;
let post_id = ObjectId::<Post>::new(post_id.clone());
let post = Box::pin(post_id.dereference(context, request_counter)).await?;
// The 2nd item, if it exists, is the parent comment apub_id
// Nested comments will automatically get fetched recursively
let parent_id: Option<CommentId> = match in_reply_to.get(1) {
Some(parent_comment_uri) => {
let parent_comment = Box::pin(dereference::<Comment>(
parent_comment_uri,
context,
request_counter,
))
.await?;
Some(comment_id) => {
let comment_id: ObjectId<Comment> = ObjectId::<Comment>::new(comment_id.clone());
let parent_comment = Box::pin(comment_id.dereference(context, request_counter)).await?;
Some(parent_comment.id)
}
@ -103,8 +100,7 @@ impl Note {
Ok((post, parent_id))
}
CommentInReplyToMigration::New(in_reply_to) => {
let parent =
Box::pin(dereference::<PostOrComment>(in_reply_to, context, request_counter).await?);
let parent = Box::pin(in_reply_to.dereference(context, request_counter).await?);
match parent.deref() {
PostOrComment::Post(p) => {
// Workaround because I cant figure ut how to get the post out of the box (and we dont
@ -138,10 +134,10 @@ impl Note {
if post.locked {
return Err(anyhow!("Post is locked").into());
}
verify_domains_match(&self.attributed_to, &self.id)?;
verify_domains_match(self.attributed_to.inner(), &self.id)?;
verify_person_in_community(
&self.attributed_to,
&community.actor_id(),
&ObjectId::<Community>::new(community.actor_id()),
context,
request_counter,
)
@ -175,7 +171,7 @@ impl ToApub for Comment {
context: lemmy_context(),
r#type: NoteType::Note,
id: self.ap_id.to_owned().into_inner(),
attributed_to: creator.actor_id.into_inner(),
attributed_to: ObjectId::<Person>::new(creator.actor_id),
to: PublicUrl::Public,
content: self.content.clone(),
media_type: MediaTypeHtml::Html,
@ -216,7 +212,10 @@ impl FromApub for Comment {
request_counter: &mut i32,
) -> Result<Comment, LemmyError> {
let ap_id = Some(note.id(expected_domain)?.clone().into());
let creator = dereference::<Person>(&note.attributed_to, context, request_counter).await?;
let creator = note
.attributed_to
.dereference(context, request_counter)
.await?;
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
if post.locked {
return Err(anyhow!("Post is locked").into());

View File

@ -1,6 +1,6 @@
use crate::{
extensions::{context::lemmy_context, signatures::PublicKey},
fetcher::community::{fetch_community_mods, fetch_community_outbox, update_community_mods},
fetcher::community::{fetch_community_outbox, update_community_mods},
generate_moderators_url,
objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
ActorType,
@ -175,7 +175,6 @@ impl FromApub for Community {
expected_domain: &Url,
request_counter: &mut i32,
) -> Result<Community, LemmyError> {
fetch_community_mods(context, group, request_counter).await?;
let form = Group::from_apub_to_form(group, expected_domain).await?;
let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??;

View File

@ -25,7 +25,7 @@ pub(crate) trait ToApub {
}
#[async_trait::async_trait(?Send)]
pub(crate) trait FromApub {
pub trait FromApub {
type ApubType;
/// Converts an object from ActivityPub type to Lemmy internal type.
///

View File

@ -1,7 +1,7 @@
use crate::{
activities::{extract_community, verify_person_in_community},
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
ActorType,
};
@ -48,7 +48,7 @@ pub struct Page {
context: OneOrMany<AnyBase>,
r#type: PageType,
id: Url,
pub(crate) attributed_to: Url,
pub(crate) attributed_to: ObjectId<Person>,
to: [Url; 2],
name: String,
content: Option<String>,
@ -101,10 +101,10 @@ impl Page {
let community = extract_community(&self.to, context, request_counter).await?;
check_slurs(&self.name)?;
verify_domains_match(&self.attributed_to, &self.id)?;
verify_domains_match(self.attributed_to.inner(), &self.id)?;
verify_person_in_community(
&self.attributed_to,
&community.actor_id(),
&ObjectId::<Community>::new(community.actor_id()),
context,
request_counter,
)
@ -137,7 +137,7 @@ impl ToApub for Post {
context: lemmy_context(),
r#type: PageType::Page,
id: self.ap_id.clone().into(),
attributed_to: creator.actor_id.into(),
attributed_to: ObjectId::<Person>::new(creator.actor_id),
to: [community.actor_id.into(), public()],
name: self.name.clone(),
content: self.body.as_ref().map(|b| markdown_to_html(b)),
@ -183,7 +183,10 @@ impl FromApub for Post {
page.id(expected_domain)?
};
let ap_id = Some(ap_id.clone().into());
let creator = dereference::<Person>(&page.attributed_to, context, request_counter).await?;
let creator = page
.attributed_to
.dereference(context, request_counter)
.await?;
let community = extract_community(&page.to, context, request_counter).await?;
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);

View File

@ -1,6 +1,6 @@
use crate::{
extensions::context::lemmy_context,
fetcher::dereference_object_id::dereference,
fetcher::object_id::ObjectId,
objects::{create_tombstone, FromApub, Source, ToApub},
};
use activitystreams::{
@ -35,8 +35,8 @@ pub struct Note {
context: OneOrMany<AnyBase>,
r#type: NoteType,
id: Url,
pub(crate) attributed_to: Url,
to: Url,
pub(crate) attributed_to: ObjectId<Person>,
to: ObjectId<Person>,
content: String,
media_type: MediaTypeHtml,
source: Source,
@ -60,8 +60,11 @@ impl Note {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_domains_match(&self.attributed_to, &self.id)?;
let person = dereference::<Person>(&self.attributed_to, context, request_counter).await?;
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());
}
@ -84,8 +87,8 @@ impl ToApub for PrivateMessage {
context: lemmy_context(),
r#type: NoteType::Note,
id: self.ap_id.clone().into(),
attributed_to: creator.actor_id.into_inner(),
to: recipient.actor_id.into(),
attributed_to: ObjectId::<Person>::new(creator.actor_id),
to: ObjectId::<Person>::new(recipient.actor_id),
content: self.content.clone(),
media_type: MediaTypeHtml::Html,
source: Source {
@ -120,8 +123,11 @@ impl FromApub for PrivateMessage {
request_counter: &mut i32,
) -> Result<PrivateMessage, LemmyError> {
let ap_id = Some(note.id(expected_domain)?.clone().into());
let creator = dereference::<Person>(&note.attributed_to, context, request_counter).await?;
let recipient = dereference::<Person>(&note.to, context, request_counter).await?;
let creator = note
.attributed_to
.dereference(context, request_counter)
.await?;
let recipient = note.to.dereference(context, request_counter).await?;
let form = PrivateMessageForm {
creator_id: creator.id,

View File

@ -145,14 +145,14 @@ pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::Tok
unimplemented!()
};
let cc_impl = if has_cc {
quote! {self.cc.clone().into()}
quote! {self.cc.iter().map(|i| i.clone().into()).collect()}
} else {
quote! {vec![]}
};
quote! {
impl #impl_generics lemmy_apub_lib::ActivityFields for #name #ty_generics #where_clause {
fn id_unchecked(&self) -> &url::Url { &self.id }
fn actor(&self) -> &url::Url { &self.actor }
fn actor(&self) -> &url::Url { &self.actor.inner() }
fn cc(&self) -> Vec<url::Url> { #cc_impl }
}
}