]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/mod.rs
8205967f72ddf13e8de4c5a8dc602a946d6ddb39
[lemmy.git] / crates / apub / src / activities / mod.rs
1 use crate::{
2   generate_moderators_url,
3   insert_activity,
4   local_instance,
5   objects::{community::ApubCommunity, person::ApubPerson},
6   ActorType,
7   CONTEXT,
8 };
9 use activitypub_federation::{
10   core::{activity_queue::send_activity, object_id::ObjectId},
11   deser::context::WithContext,
12   traits::{ActivityHandler, Actor},
13 };
14 use activitystreams_kinds::public;
15 use anyhow::anyhow;
16 use lemmy_api_common::utils::blocking;
17 use lemmy_db_schema::{newtypes::CommunityId, source::community::Community};
18 use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
19 use lemmy_utils::error::LemmyError;
20 use lemmy_websocket::LemmyContext;
21 use serde::Serialize;
22 use std::ops::Deref;
23 use tracing::info;
24 use url::{ParseError, Url};
25 use uuid::Uuid;
26
27 pub mod block;
28 pub mod community;
29 pub mod create_or_update;
30 pub mod deletion;
31 pub mod following;
32 pub mod voting;
33
34 /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
35 /// doesn't have a site ban.
36 #[tracing::instrument(skip_all)]
37 async fn verify_person(
38   person_id: &ObjectId<ApubPerson>,
39   context: &LemmyContext,
40   request_counter: &mut i32,
41 ) -> Result<(), LemmyError> {
42   let person = person_id
43     .dereference(context, local_instance(context), request_counter)
44     .await?;
45   if person.banned {
46     let err = anyhow!("Person {} is banned", person_id);
47     return Err(LemmyError::from_error_message(err, "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
62     .dereference(context, local_instance(context), request_counter)
63     .await?;
64   if person.banned {
65     return Err(LemmyError::from_message("Person is banned from site"));
66   }
67   let person_id = person.id;
68   let community_id = community.id;
69   let is_banned =
70     move |conn: &mut _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
71   if blocking(context.pool(), is_banned).await? {
72     return Err(LemmyError::from_message("Person is banned from community"));
73   }
74
75   Ok(())
76 }
77
78 /// Verify that mod action in community was performed by a moderator.
79 ///
80 /// * `mod_id` - Activitypub ID of the mod or admin who performed the action
81 /// * `object_id` - Activitypub ID of the actor or object that is being moderated
82 /// * `community` - The community inside which moderation is happening
83 #[tracing::instrument(skip_all)]
84 pub(crate) async fn verify_mod_action(
85   mod_id: &ObjectId<ApubPerson>,
86   object_id: &Url,
87   community_id: CommunityId,
88   context: &LemmyContext,
89   request_counter: &mut i32,
90 ) -> Result<(), LemmyError> {
91   let mod_ = mod_id
92     .dereference(context, local_instance(context), request_counter)
93     .await?;
94
95   let is_mod_or_admin = blocking(context.pool(), move |conn| {
96     CommunityView::is_mod_or_admin(conn, mod_.id, community_id)
97   })
98   .await?;
99   if is_mod_or_admin {
100     return Ok(());
101   }
102
103   // mod action comes from the same instance as the moderated object, so it was presumably done
104   // by an instance admin.
105   // TODO: federate instance admin status and check it here
106   if mod_id.inner().domain() == object_id.domain() {
107     return Ok(());
108   }
109
110   Err(LemmyError::from_message("Not a mod"))
111 }
112
113 /// For Add/Remove community moderator activities, check that the target field actually contains
114 /// /c/community/moderators. Any different values are unsupported.
115 fn verify_add_remove_moderator_target(
116   target: &Url,
117   community: &ApubCommunity,
118 ) -> Result<(), LemmyError> {
119   if target != &generate_moderators_url(&community.actor_id)?.into() {
120     return Err(LemmyError::from_message("Unkown target url"));
121   }
122   Ok(())
123 }
124
125 pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
126   if ![to, cc].iter().any(|set| set.contains(&public())) {
127     return Err(LemmyError::from_message("Object is not public"));
128   }
129   Ok(())
130 }
131
132 pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> {
133   if community.deleted || community.removed {
134     Err(LemmyError::from_message(
135       "New post or comment cannot be created in deleted or removed community",
136     ))
137   } else {
138     Ok(())
139   }
140 }
141
142 /// Generate a unique ID for an activity, in the format:
143 /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
144 fn generate_activity_id<T>(kind: T, protocol_and_hostname: &str) -> Result<Url, ParseError>
145 where
146   T: ToString,
147 {
148   let id = format!(
149     "{}/activities/{}/{}",
150     protocol_and_hostname,
151     kind.to_string().to_lowercase(),
152     Uuid::new_v4()
153   );
154   Url::parse(&id)
155 }
156
157 #[tracing::instrument(skip_all)]
158 async fn send_lemmy_activity<Activity, ActorT>(
159   context: &LemmyContext,
160   activity: Activity,
161   actor: &ActorT,
162   inbox: Vec<Url>,
163   sensitive: bool,
164 ) -> Result<(), LemmyError>
165 where
166   Activity: ActivityHandler + Serialize,
167   ActorT: Actor + ActorType,
168   Activity: ActivityHandler<Error = LemmyError>,
169 {
170   if !context.settings().federation.enabled {
171     return Ok(());
172   }
173   info!("Sending activity {}", activity.id().to_string());
174   let activity = WithContext::new(activity, CONTEXT.deref().clone());
175
176   let object_value = serde_json::to_value(&activity)?;
177   insert_activity(activity.id(), object_value, true, sensitive, context.pool()).await?;
178
179   send_activity(
180     activity,
181     actor.get_public_key(),
182     actor.private_key().expect("actor has private key"),
183     inbox,
184     local_instance(context),
185   )
186   .await?;
187
188   Ok(())
189 }