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