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