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