]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/mod.rs
Move code to apub library (#1795)
[lemmy.git] / crates / apub / src / activities / mod.rs
1 use crate::{
2   check_community_or_site_ban,
3   check_is_apub_id_valid,
4   fetcher::object_id::ObjectId,
5   generate_moderators_url,
6 };
7 use anyhow::anyhow;
8 use lemmy_api_common::blocking;
9 use lemmy_apub_lib::{traits::ActivityFields, verify::verify_domains_match};
10 use lemmy_db_schema::source::{community::Community, person::Person};
11 use lemmy_db_views_actor::community_view::CommunityView;
12 use lemmy_utils::{settings::structs::Settings, LemmyError};
13 use lemmy_websocket::LemmyContext;
14 use serde::{Deserialize, Serialize};
15 use strum_macros::ToString;
16 use url::{ParseError, Url};
17 use uuid::Uuid;
18
19 pub mod comment;
20 pub mod community;
21 pub mod deletion;
22 pub mod following;
23 pub mod post;
24 pub mod private_message;
25 pub mod undo_remove;
26 pub mod voting;
27
28 #[derive(Clone, Debug, ToString, Deserialize, Serialize)]
29 pub enum CreateOrUpdateType {
30   Create,
31   Update,
32 }
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 async fn verify_person(
37   person_id: &ObjectId<Person>,
38   context: &LemmyContext,
39   request_counter: &mut i32,
40 ) -> Result<(), LemmyError> {
41   let person = person_id.dereference(context, request_counter).await?;
42   if person.banned {
43     return Err(anyhow!("Person {} is banned", person_id).into());
44   }
45   Ok(())
46 }
47
48 pub(crate) async fn extract_community(
49   cc: &[Url],
50   context: &LemmyContext,
51   request_counter: &mut i32,
52 ) -> Result<Community, LemmyError> {
53   let mut cc_iter = cc.iter();
54   loop {
55     if let Some(cid) = cc_iter.next() {
56       let cid = ObjectId::new(cid.clone());
57       if let Ok(c) = cid.dereference(context, request_counter).await {
58         break Ok(c);
59       }
60     } else {
61       return Err(anyhow!("No community found in cc").into());
62     }
63   }
64 }
65
66 /// Fetches the person and community to verify their type, then checks if person is banned from site
67 /// or community.
68 pub(crate) async fn verify_person_in_community(
69   person_id: &ObjectId<Person>,
70   community_id: &ObjectId<Community>,
71   context: &LemmyContext,
72   request_counter: &mut i32,
73 ) -> Result<(), LemmyError> {
74   let community = community_id.dereference(context, request_counter).await?;
75   let person = person_id.dereference(context, request_counter).await?;
76   check_community_or_site_ban(&person, community.id, context.pool()).await
77 }
78
79 /// Simply check that the url actually refers to a valid group.
80 async fn verify_community(
81   community_id: &ObjectId<Community>,
82   context: &LemmyContext,
83   request_counter: &mut i32,
84 ) -> Result<(), LemmyError> {
85   community_id.dereference(context, request_counter).await?;
86   Ok(())
87 }
88
89 fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result<(), LemmyError> {
90   check_is_apub_id_valid(activity.actor(), false, settings)?;
91   verify_domains_match(activity.id_unchecked(), activity.actor())?;
92   Ok(())
93 }
94
95 /// Verify that the actor is a community mod. This check is only run if the community is local,
96 /// because in case of remote communities, admins can also perform mod actions. As admin status
97 /// is not federated, we cant verify their actions remotely.
98 pub(crate) async fn verify_mod_action(
99   actor_id: &ObjectId<Person>,
100   community_id: ObjectId<Community>,
101   context: &LemmyContext,
102   request_counter: &mut i32,
103 ) -> Result<(), LemmyError> {
104   let community = community_id.dereference_local(context).await?;
105
106   if community.local {
107     let actor = actor_id.dereference(context, request_counter).await?;
108
109     // Note: this will also return true for admins in addition to mods, but as we dont know about
110     //       remote admins, it doesnt make any difference.
111     let community_id = community.id;
112     let actor_id = actor.id;
113     let is_mod_or_admin = blocking(context.pool(), move |conn| {
114       CommunityView::is_mod_or_admin(conn, actor_id, community_id)
115     })
116     .await?;
117     if !is_mod_or_admin {
118       return Err(anyhow!("Not a mod").into());
119     }
120   }
121   Ok(())
122 }
123
124 /// For Add/Remove community moderator activities, check that the target field actually contains
125 /// /c/community/moderators. Any different values are unsupported.
126 fn verify_add_remove_moderator_target(
127   target: &Url,
128   community: &ObjectId<Community>,
129 ) -> Result<(), LemmyError> {
130   if target != &generate_moderators_url(&community.clone().into())?.into_inner() {
131     return Err(anyhow!("Unkown target url").into());
132   }
133   Ok(())
134 }
135
136 /// Generate a unique ID for an activity, in the format:
137 /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
138 fn generate_activity_id<T>(kind: T, protocol_and_hostname: &str) -> Result<Url, ParseError>
139 where
140   T: ToString,
141 {
142   let id = format!(
143     "{}/activities/{}/{}",
144     protocol_and_hostname,
145     kind.to_string().to_lowercase(),
146     Uuid::new_v4()
147   );
148   Url::parse(&id)
149 }