]> Untitled Git - lemmy.git/blob - server/src/apub/user.rs
Add security checks and slur checks for activitypub inbox
[lemmy.git] / server / src / apub / user.rs
1 use crate::{
2   api::{check_slurs, check_slurs_opt},
3   apub::{
4     activities::{generate_activity_id, send_activity},
5     check_actor_domain,
6     create_apub_response,
7     insert_activity,
8     ActorType,
9     FromApub,
10     PersonExt,
11     ToApub,
12   },
13   blocking,
14   routes::DbPoolParam,
15   DbPool,
16   LemmyError,
17 };
18 use activitystreams::{
19   activity::{
20     kind::{FollowType, UndoType},
21     Follow,
22     Undo,
23   },
24   actor::{ApActor, Endpoints, Person},
25   context,
26   object::{Image, Tombstone},
27   prelude::*,
28 };
29 use activitystreams_ext::Ext1;
30 use actix_web::{body::Body, client::Client, web, HttpResponse};
31 use lemmy_db::{
32   naive_now,
33   user::{UserForm, User_},
34 };
35 use lemmy_utils::convert_datetime;
36 use serde::Deserialize;
37 use url::Url;
38
39 #[derive(Deserialize)]
40 pub struct UserQuery {
41   user_name: String,
42 }
43
44 #[async_trait::async_trait(?Send)]
45 impl ToApub for User_ {
46   type Response = PersonExt;
47
48   // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
49   async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
50     // TODO go through all these to_string and to_owned()
51     let mut person = Person::new();
52     person
53       .set_context(context())
54       .set_id(Url::parse(&self.actor_id)?)
55       .set_name(self.name.to_owned())
56       .set_published(convert_datetime(self.published));
57
58     if let Some(u) = self.updated {
59       person.set_updated(convert_datetime(u));
60     }
61
62     if let Some(avatar_url) = &self.avatar {
63       let mut image = Image::new();
64       image.set_url(avatar_url.to_owned());
65       person.set_icon(image.into_any_base()?);
66     }
67
68     if let Some(bio) = &self.bio {
69       person.set_summary(bio.to_owned());
70     }
71
72     let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
73     ap_actor
74       .set_outbox(self.get_outbox_url()?)
75       .set_followers(self.get_followers_url().parse()?)
76       .set_following(self.get_following_url().parse()?)
77       .set_liked(self.get_liked_url().parse()?)
78       .set_endpoints(Endpoints {
79         shared_inbox: Some(self.get_shared_inbox_url().parse()?),
80         ..Default::default()
81       });
82
83     if let Some(i) = &self.preferred_username {
84       ap_actor.set_preferred_username(i.to_owned());
85     }
86
87     Ok(Ext1::new(ap_actor, self.get_public_key_ext()))
88   }
89   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
90     unimplemented!()
91   }
92 }
93
94 #[async_trait::async_trait(?Send)]
95 impl ActorType for User_ {
96   fn actor_id_str(&self) -> String {
97     self.actor_id.to_owned()
98   }
99
100   fn public_key(&self) -> String {
101     self.public_key.to_owned().unwrap()
102   }
103
104   fn private_key(&self) -> String {
105     self.private_key.to_owned().unwrap()
106   }
107
108   /// As a given local user, send out a follow request to a remote community.
109   async fn send_follow(
110     &self,
111     follow_actor_id: &str,
112     client: &Client,
113     pool: &DbPool,
114   ) -> Result<(), LemmyError> {
115     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
116     follow
117       .set_context(context())
118       .set_id(generate_activity_id(FollowType::Follow)?);
119     let to = format!("{}/inbox", follow_actor_id);
120
121     insert_activity(self.id, follow.clone(), true, pool).await?;
122
123     send_activity(client, &follow.into_any_base()?, self, vec![to]).await?;
124     Ok(())
125   }
126
127   async fn send_unfollow(
128     &self,
129     follow_actor_id: &str,
130     client: &Client,
131     pool: &DbPool,
132   ) -> Result<(), LemmyError> {
133     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
134     follow
135       .set_context(context())
136       .set_id(generate_activity_id(FollowType::Follow)?);
137
138     let to = format!("{}/inbox", follow_actor_id);
139
140     // Undo that fake activity
141     let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
142     undo
143       .set_context(context())
144       .set_id(generate_activity_id(UndoType::Undo)?);
145
146     insert_activity(self.id, undo.clone(), true, pool).await?;
147
148     send_activity(client, &undo.into_any_base()?, self, vec![to]).await?;
149     Ok(())
150   }
151
152   async fn send_delete(
153     &self,
154     _creator: &User_,
155     _client: &Client,
156     _pool: &DbPool,
157   ) -> Result<(), LemmyError> {
158     unimplemented!()
159   }
160
161   async fn send_undo_delete(
162     &self,
163     _creator: &User_,
164     _client: &Client,
165     _pool: &DbPool,
166   ) -> Result<(), LemmyError> {
167     unimplemented!()
168   }
169
170   async fn send_remove(
171     &self,
172     _creator: &User_,
173     _client: &Client,
174     _pool: &DbPool,
175   ) -> Result<(), LemmyError> {
176     unimplemented!()
177   }
178
179   async fn send_undo_remove(
180     &self,
181     _creator: &User_,
182     _client: &Client,
183     _pool: &DbPool,
184   ) -> Result<(), LemmyError> {
185     unimplemented!()
186   }
187
188   async fn send_accept_follow(
189     &self,
190     _follow: Follow,
191     _client: &Client,
192     _pool: &DbPool,
193   ) -> Result<(), LemmyError> {
194     unimplemented!()
195   }
196
197   async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
198     unimplemented!()
199   }
200
201   fn user_id(&self) -> i32 {
202     self.id
203   }
204 }
205
206 #[async_trait::async_trait(?Send)]
207 impl FromApub for UserForm {
208   type ApubType = PersonExt;
209   /// Parse an ActivityPub person received from another instance into a Lemmy user.
210   async fn from_apub(
211     person: &PersonExt,
212     _: &Client,
213     _: &DbPool,
214     expected_domain: Option<Url>,
215   ) -> Result<Self, LemmyError> {
216     let avatar = match person.icon() {
217       Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
218         .unwrap()
219         .unwrap()
220         .url()
221         .unwrap()
222         .as_single_xsd_any_uri()
223         .map(|u| u.to_string()),
224       None => None,
225     };
226
227     let name = person
228       .name()
229       .unwrap()
230       .one()
231       .unwrap()
232       .as_xsd_string()
233       .unwrap()
234       .to_string();
235     let preferred_username = person.inner.preferred_username().map(|u| u.to_string());
236     let bio = person
237       .inner
238       .summary()
239       .map(|s| s.as_single_xsd_string().unwrap().into());
240     check_slurs(&name)?;
241     check_slurs_opt(&preferred_username)?;
242     check_slurs_opt(&bio)?;
243
244     Ok(UserForm {
245       name,
246       preferred_username,
247       password_encrypted: "".to_string(),
248       admin: false,
249       banned: false,
250       email: None,
251       avatar,
252       updated: person.updated().map(|u| u.to_owned().naive_local()),
253       show_nsfw: false,
254       theme: "".to_string(),
255       default_sort_type: 0,
256       default_listing_type: 0,
257       lang: "".to_string(),
258       show_avatars: false,
259       send_notifications_to_email: false,
260       matrix_user_id: None,
261       actor_id: check_actor_domain(person, expected_domain)?,
262       bio,
263       local: false,
264       private_key: None,
265       public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
266       last_refreshed_at: Some(naive_now()),
267     })
268   }
269 }
270
271 /// Return the user json over HTTP.
272 pub async fn get_apub_user_http(
273   info: web::Path<UserQuery>,
274   db: DbPoolParam,
275 ) -> Result<HttpResponse<Body>, LemmyError> {
276   let user_name = info.into_inner().user_name;
277   let user = blocking(&db, move |conn| {
278     User_::find_by_email_or_username(conn, &user_name)
279   })
280   .await??;
281   let u = user.to_apub(&db).await?;
282   Ok(create_apub_response(&u))
283 }