]> Untitled Git - lemmy.git/blob - crates/apub_receive/src/inbox/receive_for_community.rs
970f80ddd210087771ee1d55ef5394538ebd5afc
[lemmy.git] / crates / apub_receive / src / inbox / receive_for_community.rs
1 use crate::{
2   activities::receive::{
3     comment::{
4       receive_create_comment,
5       receive_delete_comment,
6       receive_dislike_comment,
7       receive_like_comment,
8       receive_remove_comment,
9       receive_update_comment,
10     },
11     comment_undo::{
12       receive_undo_delete_comment,
13       receive_undo_dislike_comment,
14       receive_undo_like_comment,
15       receive_undo_remove_comment,
16     },
17     community::{
18       receive_remote_mod_delete_community,
19       receive_remote_mod_undo_delete_community,
20       receive_remote_mod_update_community,
21     },
22     post::{
23       receive_create_post,
24       receive_delete_post,
25       receive_dislike_post,
26       receive_like_post,
27       receive_remove_post,
28       receive_update_post,
29     },
30     post_undo::{
31       receive_undo_delete_post,
32       receive_undo_dislike_post,
33       receive_undo_like_post,
34       receive_undo_remove_post,
35     },
36     receive_unhandled_activity,
37     verify_activity_domains_valid,
38   },
39   inbox::verify_is_addressed_to_public,
40 };
41 use activitystreams::{
42   activity::{
43     ActorAndObjectRef,
44     Add,
45     Announce,
46     Block,
47     Create,
48     Delete,
49     Dislike,
50     Like,
51     OptTargetRef,
52     Remove,
53     Undo,
54     Update,
55   },
56   base::AnyBase,
57   object::AsObject,
58   prelude::*,
59 };
60 use anyhow::{anyhow, Context};
61 use diesel::result::Error::NotFound;
62 use lemmy_api_common::blocking;
63 use lemmy_apub::{
64   fetcher::{
65     objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
66     person::get_or_fetch_and_upsert_person,
67   },
68   find_object_by_id,
69   find_post_or_comment_by_id,
70   generate_moderators_url,
71   ActorType,
72   CommunityType,
73   Object,
74   PostOrComment,
75 };
76 use lemmy_db_queries::{
77   source::community::CommunityModerator_,
78   ApubObject,
79   Bannable,
80   Crud,
81   Followable,
82   Joinable,
83 };
84 use lemmy_db_schema::{
85   source::{
86     community::{
87       Community,
88       CommunityFollower,
89       CommunityFollowerForm,
90       CommunityModerator,
91       CommunityModeratorForm,
92       CommunityPersonBan,
93       CommunityPersonBanForm,
94     },
95     person::Person,
96     site::Site,
97   },
98   DbUrl,
99 };
100 use lemmy_db_views_actor::community_view::CommunityView;
101 use lemmy_utils::{location_info, LemmyError};
102 use lemmy_websocket::LemmyContext;
103 use strum_macros::EnumString;
104 use url::Url;
105
106 #[derive(EnumString)]
107 enum PageOrNote {
108   Page,
109   Note,
110 }
111
112 #[derive(EnumString)]
113 enum ObjectTypes {
114   Page,
115   Note,
116   Group,
117   Person,
118 }
119
120 /// This file is for post/comment activities received by the community, and for post/comment
121 ///       activities announced by the community and received by the person.
122
123 /// A post or comment being created
124 pub(in crate::inbox) async fn receive_create_for_community(
125   context: &LemmyContext,
126   activity: AnyBase,
127   expected_domain: &Url,
128   request_counter: &mut i32,
129 ) -> Result<(), LemmyError> {
130   let create = Create::from_any_base(activity)?.context(location_info!())?;
131   verify_activity_domains_valid(&create, &expected_domain, true)?;
132   verify_is_addressed_to_public(&create)?;
133
134   let kind = create
135     .object()
136     .as_single_kind_str()
137     .and_then(|s| s.parse().ok());
138   match kind {
139     Some(ObjectTypes::Page) => receive_create_post(create, context, request_counter).await,
140     Some(ObjectTypes::Note) => receive_create_comment(create, context, request_counter).await,
141     _ => receive_unhandled_activity(create),
142   }
143 }
144
145 /// A post or comment being edited
146 pub(in crate::inbox) async fn receive_update_for_community(
147   context: &LemmyContext,
148   activity: AnyBase,
149   announce: Option<Announce>,
150   expected_domain: &Url,
151   request_counter: &mut i32,
152 ) -> Result<(), LemmyError> {
153   let update = Update::from_any_base(activity.to_owned())?.context(location_info!())?;
154   verify_activity_domains_valid(&update, &expected_domain, false)?;
155   verify_is_addressed_to_public(&update)?;
156   verify_modification_actor_instance(&update, &announce, context, request_counter).await?;
157
158   let kind = update
159     .object()
160     .as_single_kind_str()
161     .and_then(|s| s.parse().ok());
162   match kind {
163     Some(ObjectTypes::Page) => {
164       receive_update_post(update, announce, context, request_counter).await
165     }
166     Some(ObjectTypes::Note) => receive_update_comment(update, context, request_counter).await,
167     Some(ObjectTypes::Group) => {
168       receive_remote_mod_update_community(update, context, request_counter).await
169     }
170     _ => receive_unhandled_activity(update),
171   }
172 }
173
174 /// A post or comment being upvoted
175 pub(in crate::inbox) async fn receive_like_for_community(
176   context: &LemmyContext,
177   activity: AnyBase,
178   expected_domain: &Url,
179   request_counter: &mut i32,
180 ) -> Result<(), LemmyError> {
181   let like = Like::from_any_base(activity)?.context(location_info!())?;
182   verify_activity_domains_valid(&like, &expected_domain, false)?;
183   verify_is_addressed_to_public(&like)?;
184
185   let object_id = like
186     .object()
187     .as_single_xsd_any_uri()
188     .context(location_info!())?;
189   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
190     PostOrComment::Post(post) => receive_like_post(like, *post, context, request_counter).await,
191     PostOrComment::Comment(comment) => {
192       receive_like_comment(like, *comment, context, request_counter).await
193     }
194   }
195 }
196
197 /// A post or comment being downvoted
198 pub(in crate::inbox) async fn receive_dislike_for_community(
199   context: &LemmyContext,
200   activity: AnyBase,
201   expected_domain: &Url,
202   request_counter: &mut i32,
203 ) -> Result<(), LemmyError> {
204   let enable_downvotes = blocking(context.pool(), move |conn| {
205     Site::read(conn, 1).map(|s| s.enable_downvotes)
206   })
207   .await??;
208   if !enable_downvotes {
209     return Ok(());
210   }
211
212   let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
213   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
214   verify_is_addressed_to_public(&dislike)?;
215
216   let object_id = dislike
217     .object()
218     .as_single_xsd_any_uri()
219     .context(location_info!())?;
220   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
221     PostOrComment::Post(post) => {
222       receive_dislike_post(dislike, *post, context, request_counter).await
223     }
224     PostOrComment::Comment(comment) => {
225       receive_dislike_comment(dislike, *comment, context, request_counter).await
226     }
227   }
228 }
229
230 /// A post or comment being deleted by its creator
231 pub(in crate::inbox) async fn receive_delete_for_community(
232   context: &LemmyContext,
233   activity: AnyBase,
234   announce: Option<Announce>,
235   expected_domain: &Url,
236   request_counter: &mut i32,
237 ) -> Result<(), LemmyError> {
238   let delete = Delete::from_any_base(activity)?.context(location_info!())?;
239   // TODO: skip this check if action is done by remote mod
240   verify_is_addressed_to_public(&delete)?;
241   verify_modification_actor_instance(&delete, &announce, context, request_counter).await?;
242
243   let object = delete
244     .object()
245     .to_owned()
246     .single_xsd_any_uri()
247     .context(location_info!())?;
248
249   match find_object_by_id(context, object).await {
250     Ok(Object::Post(p)) => {
251       verify_activity_domains_valid(&delete, &expected_domain, true)?;
252       receive_delete_post(context, *p).await
253     }
254     Ok(Object::Comment(c)) => {
255       verify_activity_domains_valid(&delete, &expected_domain, true)?;
256       receive_delete_comment(context, *c).await
257     }
258     Ok(Object::Community(c)) => {
259       receive_remote_mod_delete_community(delete, *c, context, request_counter).await
260     }
261     // if we dont have the object or dont support its deletion, no need to do anything
262     _ => Ok(()),
263   }
264 }
265
266 /// A post or comment being removed by a mod/admin
267 pub(in crate::inbox) async fn receive_remove_for_community(
268   context: &LemmyContext,
269   remove_any_base: AnyBase,
270   announce: Option<Announce>,
271   request_counter: &mut i32,
272 ) -> Result<(), LemmyError> {
273   let remove = Remove::from_any_base(remove_any_base.to_owned())?.context(location_info!())?;
274   let community = extract_community_from_cc(&remove, context).await?;
275
276   verify_mod_activity(&remove, announce, &community, context).await?;
277   verify_is_addressed_to_public(&remove)?;
278
279   if remove.target().is_some() {
280     let remove_mod = remove
281       .object()
282       .as_single_xsd_any_uri()
283       .context(location_info!())?;
284     let remove_mod = get_or_fetch_and_upsert_person(&remove_mod, context, request_counter).await?;
285     let form = CommunityModeratorForm {
286       community_id: community.id,
287       person_id: remove_mod.id,
288     };
289     blocking(context.pool(), move |conn| {
290       CommunityModerator::leave(conn, &form)
291     })
292     .await??;
293     community
294       .send_announce(
295         remove_any_base,
296         remove.object().clone().single_xsd_any_uri(),
297         context,
298       )
299       .await?;
300     // TODO: send websocket notification about removed mod
301     Ok(())
302   }
303   // Remove a post or comment
304   else {
305     let object = remove
306       .object()
307       .to_owned()
308       .single_xsd_any_uri()
309       .context(location_info!())?;
310
311     match find_post_or_comment_by_id(context, object).await {
312       Ok(PostOrComment::Post(p)) => receive_remove_post(context, *p).await,
313       Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, *c).await,
314       // if we dont have the object, no need to do anything
315       Err(_) => Ok(()),
316     }
317   }
318 }
319
320 #[derive(EnumString)]
321 enum UndoableActivities {
322   Delete,
323   Remove,
324   Like,
325   Dislike,
326   Block,
327 }
328
329 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
330 pub(in crate::inbox) async fn receive_undo_for_community(
331   context: &LemmyContext,
332   activity: AnyBase,
333   announce: Option<Announce>,
334   expected_domain: &Url,
335   request_counter: &mut i32,
336 ) -> Result<(), LemmyError> {
337   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
338   verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
339   verify_is_addressed_to_public(&undo)?;
340
341   use UndoableActivities::*;
342   match undo
343     .object()
344     .as_single_kind_str()
345     .and_then(|s| s.parse().ok())
346   {
347     Some(Delete) => {
348       receive_undo_delete_for_community(context, undo, expected_domain, request_counter).await
349     }
350     Some(Remove) => {
351       receive_undo_remove_for_community(context, undo, announce, expected_domain).await
352     }
353     Some(Like) => {
354       receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
355     }
356     Some(Dislike) => {
357       receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
358     }
359     Some(Block) => {
360       receive_undo_block_user_for_community(
361         context,
362         undo,
363         announce,
364         expected_domain,
365         request_counter,
366       )
367       .await
368     }
369     _ => receive_unhandled_activity(undo),
370   }
371 }
372
373 /// A post, comment or community deletion being reverted
374 pub(in crate::inbox) async fn receive_undo_delete_for_community(
375   context: &LemmyContext,
376   undo: Undo,
377   expected_domain: &Url,
378   request_counter: &mut i32,
379 ) -> Result<(), LemmyError> {
380   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
381     .context(location_info!())?;
382   verify_is_addressed_to_public(&delete)?;
383
384   let object = delete
385     .object()
386     .to_owned()
387     .single_xsd_any_uri()
388     .context(location_info!())?;
389   match find_object_by_id(context, object).await {
390     Ok(Object::Post(p)) => {
391       verify_activity_domains_valid(&delete, &expected_domain, true)?;
392       receive_undo_delete_post(context, *p).await
393     }
394     Ok(Object::Comment(c)) => {
395       verify_activity_domains_valid(&delete, &expected_domain, true)?;
396       receive_undo_delete_comment(context, *c).await
397     }
398     Ok(Object::Community(c)) => {
399       verify_actor_is_community_mod(&undo, &c, context).await?;
400       receive_remote_mod_undo_delete_community(undo, *c, context, request_counter).await
401     }
402     // if we dont have the object or dont support its deletion, no need to do anything
403     _ => Ok(()),
404   }
405 }
406
407 /// A post or comment removal being reverted
408 pub(in crate::inbox) async fn receive_undo_remove_for_community(
409   context: &LemmyContext,
410   undo: Undo,
411   announce: Option<Announce>,
412   expected_domain: &Url,
413 ) -> Result<(), LemmyError> {
414   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
415     .context(location_info!())?;
416   verify_activity_domains_valid(&remove, &expected_domain, false)?;
417   verify_is_addressed_to_public(&remove)?;
418   verify_undo_remove_actor_instance(&undo, &remove, &announce, context).await?;
419
420   let object = remove
421     .object()
422     .to_owned()
423     .single_xsd_any_uri()
424     .context(location_info!())?;
425   match find_post_or_comment_by_id(context, object).await {
426     Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).await,
427     Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, *c).await,
428     // if we dont have the object, no need to do anything
429     Err(_) => Ok(()),
430   }
431 }
432
433 /// A post or comment upvote being reverted
434 pub(in crate::inbox) async fn receive_undo_like_for_community(
435   context: &LemmyContext,
436   undo: Undo,
437   expected_domain: &Url,
438   request_counter: &mut i32,
439 ) -> Result<(), LemmyError> {
440   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
441     .context(location_info!())?;
442   verify_activity_domains_valid(&like, &expected_domain, false)?;
443   verify_is_addressed_to_public(&like)?;
444
445   let object_id = like
446     .object()
447     .as_single_xsd_any_uri()
448     .context(location_info!())?;
449   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
450     PostOrComment::Post(post) => {
451       receive_undo_like_post(&like, *post, context, request_counter).await
452     }
453     PostOrComment::Comment(comment) => {
454       receive_undo_like_comment(&like, *comment, context, request_counter).await
455     }
456   }
457 }
458
459 /// Add a new mod to the community (can only be done by an existing mod).
460 pub(in crate::inbox) async fn receive_add_for_community(
461   context: &LemmyContext,
462   add_any_base: AnyBase,
463   announce: Option<Announce>,
464   request_counter: &mut i32,
465 ) -> Result<(), LemmyError> {
466   let add = Add::from_any_base(add_any_base.to_owned())?.context(location_info!())?;
467   let community = extract_community_from_cc(&add, context).await?;
468
469   verify_mod_activity(&add, announce, &community, context).await?;
470   verify_is_addressed_to_public(&add)?;
471   verify_add_remove_moderator_target(&add, &community)?;
472
473   let new_mod = add
474     .object()
475     .as_single_xsd_any_uri()
476     .context(location_info!())?;
477   let new_mod = get_or_fetch_and_upsert_person(&new_mod, context, request_counter).await?;
478
479   // If we had to refetch the community while parsing the activity, then the new mod has already
480   // been added. Skip it here as it would result in a duplicate key error.
481   let new_mod_id = new_mod.id;
482   let moderated_communities = blocking(context.pool(), move |conn| {
483     CommunityModerator::get_person_moderated_communities(conn, new_mod_id)
484   })
485   .await??;
486   if !moderated_communities.contains(&community.id) {
487     let form = CommunityModeratorForm {
488       community_id: community.id,
489       person_id: new_mod.id,
490     };
491     blocking(context.pool(), move |conn| {
492       CommunityModerator::join(conn, &form)
493     })
494     .await??;
495   }
496   if community.local {
497     community
498       .send_announce(
499         add_any_base,
500         add.object().clone().single_xsd_any_uri(),
501         context,
502       )
503       .await?;
504   }
505   // TODO: send websocket notification about added mod
506   Ok(())
507 }
508
509 /// A post or comment downvote being reverted
510 pub(in crate::inbox) async fn receive_undo_dislike_for_community(
511   context: &LemmyContext,
512   undo: Undo,
513   expected_domain: &Url,
514   request_counter: &mut i32,
515 ) -> Result<(), LemmyError> {
516   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
517     .context(location_info!())?;
518   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
519   verify_is_addressed_to_public(&dislike)?;
520
521   let object_id = dislike
522     .object()
523     .as_single_xsd_any_uri()
524     .context(location_info!())?;
525   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
526     PostOrComment::Post(post) => {
527       receive_undo_dislike_post(&dislike, *post, context, request_counter).await
528     }
529     PostOrComment::Comment(comment) => {
530       receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await
531     }
532   }
533 }
534
535 pub(crate) async fn receive_block_user_for_community(
536   context: &LemmyContext,
537   block_any_base: AnyBase,
538   announce: Option<Announce>,
539   request_counter: &mut i32,
540 ) -> Result<(), LemmyError> {
541   let block = Block::from_any_base(block_any_base.to_owned())?.context(location_info!())?;
542   let community = extract_community_from_cc(&block, context).await?;
543
544   verify_mod_activity(&block, announce, &community, context).await?;
545   verify_is_addressed_to_public(&block)?;
546
547   let blocked_user = block
548     .object()
549     .as_single_xsd_any_uri()
550     .context(location_info!())?;
551   let blocked_user =
552     get_or_fetch_and_upsert_person(&blocked_user, context, request_counter).await?;
553
554   let community_user_ban_form = CommunityPersonBanForm {
555     community_id: community.id,
556     person_id: blocked_user.id,
557   };
558
559   blocking(context.pool(), move |conn: &'_ _| {
560     CommunityPersonBan::ban(conn, &community_user_ban_form)
561   })
562   .await??;
563
564   // Also unsubscribe them from the community, if they are subscribed
565   let community_follower_form = CommunityFollowerForm {
566     community_id: community.id,
567     person_id: blocked_user.id,
568     pending: false,
569   };
570   blocking(context.pool(), move |conn: &'_ _| {
571     CommunityFollower::unfollow(conn, &community_follower_form)
572   })
573   .await?
574   .ok();
575
576   Ok(())
577 }
578
579 pub(crate) async fn receive_undo_block_user_for_community(
580   context: &LemmyContext,
581   undo: Undo,
582   announce: Option<Announce>,
583   expected_domain: &Url,
584   request_counter: &mut i32,
585 ) -> Result<(), LemmyError> {
586   let object = undo.object().clone().one().context(location_info!())?;
587   let block = Block::from_any_base(object)?.context(location_info!())?;
588   let community = extract_community_from_cc(&block, context).await?;
589
590   verify_activity_domains_valid(&block, &expected_domain, false)?;
591   verify_is_addressed_to_public(&block)?;
592   verify_undo_remove_actor_instance(&undo, &block, &announce, context).await?;
593
594   let blocked_user = block
595     .object()
596     .as_single_xsd_any_uri()
597     .context(location_info!())?;
598   let blocked_user =
599     get_or_fetch_and_upsert_person(&blocked_user, context, request_counter).await?;
600
601   let community_user_ban_form = CommunityPersonBanForm {
602     community_id: community.id,
603     person_id: blocked_user.id,
604   };
605
606   blocking(context.pool(), move |conn: &'_ _| {
607     CommunityPersonBan::unban(conn, &community_user_ban_form)
608   })
609   .await??;
610
611   Ok(())
612 }
613
614 async fn fetch_post_or_comment_by_id(
615   apub_id: &Url,
616   context: &LemmyContext,
617   request_counter: &mut i32,
618 ) -> Result<PostOrComment, LemmyError> {
619   if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
620     return Ok(PostOrComment::Post(Box::new(post)));
621   }
622
623   if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
624     return Ok(PostOrComment::Comment(Box::new(comment)));
625   }
626
627   Err(NotFound.into())
628 }
629
630 /// Searches the activity's cc field for a Community ID, and returns the community.
631 async fn extract_community_from_cc<T, Kind>(
632   activity: &T,
633   context: &LemmyContext,
634 ) -> Result<Community, LemmyError>
635 where
636   T: AsObject<Kind>,
637 {
638   let cc = activity
639     .cc()
640     .map(|c| c.as_many())
641     .flatten()
642     .context(location_info!())?;
643   let community_id = cc
644     .first()
645     .map(|c| c.as_xsd_any_uri())
646     .flatten()
647     .context(location_info!())?;
648   let community_id: DbUrl = community_id.to_owned().into();
649   let community = blocking(&context.pool(), move |conn| {
650     Community::read_from_apub_id(&conn, &community_id)
651   })
652   .await??;
653   Ok(community)
654 }
655
656 /// Checks that a moderation activity was sent by a user who is listed as mod for the community.
657 /// This is only used in the case of remote mods, as local mod actions don't go through the
658 /// community inbox.
659 ///
660 /// This method should only be used for activities received by the community, not for activities
661 /// used by community followers.
662 pub(crate) async fn verify_actor_is_community_mod<T, Kind>(
663   activity: &T,
664   community: &Community,
665   context: &LemmyContext,
666 ) -> Result<(), LemmyError>
667 where
668   T: ActorAndObjectRef + BaseExt<Kind>,
669 {
670   let actor = activity
671     .actor()?
672     .as_single_xsd_any_uri()
673     .context(location_info!())?
674     .to_owned();
675   let actor = blocking(&context.pool(), move |conn| {
676     Person::read_from_apub_id(&conn, &actor.into())
677   })
678   .await??;
679
680   // Note: this will also return true for admins in addition to mods, but as we dont know about
681   //       remote admins, it doesnt make any difference.
682   let community_id = community.id;
683   let actor_id = actor.id;
684   let is_mod_or_admin = blocking(context.pool(), move |conn| {
685     CommunityView::is_mod_or_admin(conn, actor_id, community_id)
686   })
687   .await?;
688   if !is_mod_or_admin {
689     return Err(anyhow!("Not a mod").into());
690   }
691
692   Ok(())
693 }
694
695 /// This method behaves differently, depending if it is called via community inbox (activity
696 /// received by community from a remote user), or via user inbox (activity received by user from
697 /// community). We distinguish the cases by checking if the activity is wrapper in an announce
698 /// (only true when sent from user to community).
699 ///
700 /// In the first case, we check that the actor is listed as community mod. In the second case, we
701 /// only check that the announce comes from the same domain as the activity. We trust the
702 /// community's instance to have validated the inner activity correctly. We can't do this validation
703 /// here, because we don't know who the instance admins are. Plus this allows for compatibility with
704 /// software that uses different rules for mod actions.
705 pub(crate) async fn verify_mod_activity<T, Kind>(
706   mod_action: &T,
707   announce: Option<Announce>,
708   community: &Community,
709   context: &LemmyContext,
710 ) -> Result<(), LemmyError>
711 where
712   T: ActorAndObjectRef + BaseExt<Kind>,
713 {
714   match announce {
715     None => verify_actor_is_community_mod(mod_action, community, context).await?,
716     Some(a) => verify_activity_domains_valid(&a, &community.actor_id.to_owned().into(), false)?,
717   }
718
719   Ok(())
720 }
721
722 /// For Add/Remove community moderator activities, check that the target field actually contains
723 /// /c/community/moderators. Any different values are unsupported.
724 fn verify_add_remove_moderator_target<T, Kind>(
725   activity: &T,
726   community: &Community,
727 ) -> Result<(), LemmyError>
728 where
729   T: ActorAndObjectRef + BaseExt<Kind> + OptTargetRef,
730 {
731   let target = activity
732     .target()
733     .map(|t| t.as_single_xsd_any_uri())
734     .flatten()
735     .context(location_info!())?;
736   if target != &generate_moderators_url(&community.actor_id)?.into_inner() {
737     return Err(anyhow!("Unkown target url").into());
738   }
739   Ok(())
740 }
741
742 /// For activities like Update, Delete or Remove, check that the actor is from the same instance
743 /// as the original object itself (or is a remote mod).
744 ///
745 /// Note: This is only needed for mod actions. Normal user actions (edit post, undo vote etc) are
746 ///       already verified with `expected_domain`, so this serves as an additional check.
747 async fn verify_modification_actor_instance<T, Kind>(
748   activity: &T,
749   announce: &Option<Announce>,
750   context: &LemmyContext,
751   request_counter: &mut i32,
752 ) -> Result<(), LemmyError>
753 where
754   T: ActorAndObjectRef + BaseExt<Kind> + AsObject<Kind>,
755 {
756   let actor_id = activity
757     .actor()?
758     .to_owned()
759     .single_xsd_any_uri()
760     .context(location_info!())?;
761   let object_id = activity
762     .object()
763     .as_one()
764     .map(|o| o.id())
765     .flatten()
766     .context(location_info!())?;
767   let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await {
768     Ok(PostOrComment::Post(p)) => p.ap_id.into_inner(),
769     Ok(PostOrComment::Comment(c)) => c.ap_id.into_inner(),
770     Err(_) => {
771       // We can also receive Update activity from remote mod for local activity
772       let object_id = object_id.to_owned().into();
773       blocking(context.pool(), move |conn| {
774         Community::read_from_apub_id(conn, &object_id)
775       })
776       .await??
777       .actor_id()
778     }
779   };
780   if actor_id.domain() != original_id.domain() {
781     let community = extract_community_from_cc(activity, context).await?;
782     verify_mod_activity(activity, announce.to_owned(), &community, context).await?;
783   }
784
785   Ok(())
786 }
787
788 pub(crate) async fn verify_undo_remove_actor_instance<T, Kind>(
789   undo: &Undo,
790   inner: &T,
791   announce: &Option<Announce>,
792   context: &LemmyContext,
793 ) -> Result<(), LemmyError>
794 where
795   T: ActorAndObjectRef + BaseExt<Kind> + AsObject<Kind>,
796 {
797   if announce.is_none() {
798     let community = extract_community_from_cc(undo, context).await?;
799     verify_mod_activity(undo, announce.to_owned(), &community, context).await?;
800     verify_mod_activity(inner, announce.to_owned(), &community, context).await?;
801   }
802
803   Ok(())
804 }