]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/mod.rs
Merge different delete activities for better compatibility (fixes #2066) (#2073)
[lemmy.git] / crates / apub / src / activities / mod.rs
1 use crate::{
2   check_is_apub_id_valid,
3   context::WithContext,
4   generate_moderators_url,
5   insert_activity,
6   objects::{community::ApubCommunity, person::ApubPerson},
7 };
8 use activitystreams_kinds::public;
9 use lemmy_api_common::blocking;
10 use lemmy_apub_lib::{
11   activity_queue::send_activity,
12   object_id::ObjectId,
13   traits::ActorType,
14   verify::verify_domains_match,
15 };
16 use lemmy_db_schema::source::community::Community;
17 use lemmy_db_views_actor::{
18   community_person_ban_view::CommunityPersonBanView,
19   community_view::CommunityView,
20 };
21 use lemmy_utils::{settings::structs::Settings, LemmyError};
22 use lemmy_websocket::LemmyContext;
23 use serde::Serialize;
24 use tracing::info;
25 use url::{ParseError, Url};
26 use uuid::Uuid;
27
28 pub mod block;
29 pub mod community;
30 pub mod create_or_update;
31 pub mod deletion;
32 pub mod following;
33 pub mod voting;
34
35 /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
36 /// doesn't have a site ban.
37 #[tracing::instrument(skip_all)]
38 async fn verify_person(
39   person_id: &ObjectId<ApubPerson>,
40   context: &LemmyContext,
41   request_counter: &mut i32,
42 ) -> Result<(), LemmyError> {
43   let person = person_id
44     .dereference(context, context.client(), request_counter)
45     .await?;
46   if person.banned {
47     let error = LemmyError::from(anyhow::anyhow!("Person {} is banned", person_id));
48     return Err(error.with_message("banned"));
49   }
50   Ok(())
51 }
52
53 /// Fetches the person and community to verify their type, then checks if person is banned from site
54 /// or community.
55 #[tracing::instrument(skip_all)]
56 pub(crate) async fn verify_person_in_community(
57   person_id: &ObjectId<ApubPerson>,
58   community: &ApubCommunity,
59   context: &LemmyContext,
60   request_counter: &mut i32,
61 ) -> Result<(), LemmyError> {
62   let person = person_id
63     .dereference(context, context.client(), request_counter)
64     .await?;
65   if person.banned {
66     return Err(LemmyError::from_message("Person is banned from site"));
67   }
68   let person_id = person.id;
69   let community_id = community.id;
70   let is_banned =
71     move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
72   if blocking(context.pool(), is_banned).await? {
73     return Err(LemmyError::from_message("Person is banned from community"));
74   }
75
76   Ok(())
77 }
78
79 fn verify_activity(id: &Url, actor: &Url, settings: &Settings) -> Result<(), LemmyError> {
80   check_is_apub_id_valid(actor, false, settings)?;
81   verify_domains_match(id, actor)?;
82   Ok(())
83 }
84
85 /// Verify that the actor is a community mod. This check is only run if the community is local,
86 /// because in case of remote communities, admins can also perform mod actions. As admin status
87 /// is not federated, we cant verify their actions remotely.
88 #[tracing::instrument(skip_all)]
89 pub(crate) async fn verify_mod_action(
90   actor_id: &ObjectId<ApubPerson>,
91   community: &ApubCommunity,
92   context: &LemmyContext,
93   request_counter: &mut i32,
94 ) -> Result<(), LemmyError> {
95   if community.local {
96     let actor = actor_id
97       .dereference(context, context.client(), request_counter)
98       .await?;
99
100     // Note: this will also return true for admins in addition to mods, but as we dont know about
101     //       remote admins, it doesnt make any difference.
102     let community_id = community.id;
103     let actor_id = actor.id;
104     let is_mod_or_admin = blocking(context.pool(), move |conn| {
105       CommunityView::is_mod_or_admin(conn, actor_id, community_id)
106     })
107     .await?;
108     if !is_mod_or_admin {
109       return Err(LemmyError::from_message("Not a mod"));
110     }
111   }
112   Ok(())
113 }
114
115 /// For Add/Remove community moderator activities, check that the target field actually contains
116 /// /c/community/moderators. Any different values are unsupported.
117 fn verify_add_remove_moderator_target(
118   target: &Url,
119   community: &ApubCommunity,
120 ) -> Result<(), LemmyError> {
121   if target != &generate_moderators_url(&community.actor_id)?.into() {
122     return Err(LemmyError::from_message("Unkown target url"));
123   }
124   Ok(())
125 }
126
127 pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
128   if ![to, cc].iter().any(|set| set.contains(&public())) {
129     return Err(LemmyError::from_message("Object is not public"));
130   }
131   Ok(())
132 }
133
134 pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> {
135   if community.deleted || community.removed {
136     Err(LemmyError::from_message(
137       "New post or comment cannot be created in deleted or removed community",
138     ))
139   } else {
140     Ok(())
141   }
142 }
143
144 /// Generate a unique ID for an activity, in the format:
145 /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
146 fn generate_activity_id<T>(kind: T, protocol_and_hostname: &str) -> Result<Url, ParseError>
147 where
148   T: ToString,
149 {
150   let id = format!(
151     "{}/activities/{}/{}",
152     protocol_and_hostname,
153     kind.to_string().to_lowercase(),
154     Uuid::new_v4()
155   );
156   Url::parse(&id)
157 }
158
159 #[tracing::instrument(skip_all)]
160 async fn send_lemmy_activity<T: Serialize>(
161   context: &LemmyContext,
162   activity: &T,
163   activity_id: &Url,
164   actor: &dyn ActorType,
165   inboxes: Vec<Url>,
166   sensitive: bool,
167 ) -> Result<(), LemmyError> {
168   if !context.settings().federation.enabled || inboxes.is_empty() {
169     return Ok(());
170   }
171   let activity = WithContext::new(activity);
172
173   info!("Sending activity {}", activity_id.to_string());
174
175   // Don't send anything to ourselves
176   // TODO: this should be a debug assert
177   let hostname = context.settings().get_hostname_without_port()?;
178   let inboxes: Vec<&Url> = inboxes
179     .iter()
180     .filter(|i| i.domain().expect("valid inbox url") != hostname)
181     .collect();
182
183   let serialised_activity = serde_json::to_string(&activity)?;
184
185   let object_value = serde_json::to_value(&activity)?;
186   insert_activity(activity_id, object_value, true, sensitive, context.pool()).await?;
187
188   send_activity(
189     activity_id,
190     actor,
191     inboxes,
192     serialised_activity,
193     context.client(),
194     context.activity_queue(),
195   )
196   .await
197 }