4 receive_create_comment,
5 receive_delete_comment,
6 receive_dislike_comment,
8 receive_remove_comment,
9 receive_update_comment,
12 receive_undo_delete_comment,
13 receive_undo_dislike_comment,
14 receive_undo_like_comment,
15 receive_undo_remove_comment,
18 receive_delete_community,
19 receive_remove_community,
20 receive_undo_delete_community,
21 receive_undo_remove_community,
33 receive_undo_delete_post,
34 receive_undo_dislike_post,
35 receive_undo_like_post,
36 receive_undo_remove_post,
38 receive_unhandled_activity,
39 verify_activity_domains_valid,
42 check_is_apub_id_valid,
43 extensions::signatures::verify_signature,
44 fetcher::get_or_fetch_and_upsert_actor,
45 inbox::{get_activity_id, is_activity_already_known},
49 use activitystreams::{
50 activity::{ActorAndObject, Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
54 use actix_web::{web, HttpRequest, HttpResponse};
55 use anyhow::{anyhow, Context};
56 use lemmy_db::{site::Site, Crud};
57 use lemmy_structs::blocking;
58 use lemmy_utils::{location_info, LemmyError};
59 use lemmy_websocket::LemmyContext;
61 use serde::{Deserialize, Serialize};
65 /// Allowed activity types for shared inbox.
66 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
67 #[serde(rename_all = "PascalCase")]
79 // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
80 // but it might still work due to the anybase conversion
81 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
83 /// Handler for all incoming requests to shared inbox.
84 pub async fn shared_inbox(
86 input: web::Json<AcceptedActivities>,
87 context: web::Data<LemmyContext>,
88 ) -> Result<HttpResponse, LemmyError> {
89 let activity = input.into_inner();
91 let actor_id = activity
95 .context(location_info!())?;
97 "Shared inbox received activity {:?} from {}",
98 &activity.id_unchecked(),
102 check_is_apub_id_valid(&actor_id)?;
104 let request_counter = &mut 0;
105 let actor = get_or_fetch_and_upsert_actor(&actor_id, &context, request_counter).await?;
106 verify_signature(&request, actor.as_ref())?;
108 let activity_id = get_activity_id(&activity, &actor_id)?;
109 if is_activity_already_known(context.pool(), &activity_id).await? {
110 return Ok(HttpResponse::Ok().finish());
113 let any_base = activity.clone().into_any_base()?;
114 let kind = activity.kind().context(location_info!())?;
115 let res = match kind {
116 ValidTypes::Announce => {
117 receive_announce(&context, any_base, actor.as_ref(), request_counter).await
119 ValidTypes::Create => receive_create(&context, any_base, actor_id, request_counter).await,
120 ValidTypes::Update => receive_update(&context, any_base, actor_id, request_counter).await,
121 ValidTypes::Like => receive_like(&context, any_base, actor_id, request_counter).await,
122 ValidTypes::Dislike => receive_dislike(&context, any_base, actor_id, request_counter).await,
123 ValidTypes::Remove => receive_remove(&context, any_base, actor_id).await,
124 ValidTypes::Delete => receive_delete(&context, any_base, actor_id, request_counter).await,
125 ValidTypes::Undo => receive_undo(&context, any_base, actor_id, request_counter).await,
139 /// Takes an announce and passes the inner activity to the appropriate handler.
140 async fn receive_announce(
141 context: &LemmyContext,
143 actor: &dyn ActorType,
144 request_counter: &mut i32,
145 ) -> Result<HttpResponse, LemmyError> {
146 let announce = Announce::from_any_base(activity)?.context(location_info!())?;
147 verify_activity_domains_valid(&announce, actor.actor_id()?, false)?;
149 let kind = announce.object().as_single_kind_str();
150 let object = announce
154 .context(location_info!())?;
156 let inner_id = object.id().context(location_info!())?.to_owned();
157 check_is_apub_id_valid(&inner_id)?;
158 if is_activity_already_known(context.pool(), &inner_id).await? {
159 return Ok(HttpResponse::Ok().finish());
163 Some("Create") => receive_create(context, object, inner_id, request_counter).await,
164 Some("Update") => receive_update(context, object, inner_id, request_counter).await,
165 Some("Like") => receive_like(context, object, inner_id, request_counter).await,
166 Some("Dislike") => receive_dislike(context, object, inner_id, request_counter).await,
167 Some("Delete") => receive_delete(context, object, inner_id, request_counter).await,
168 Some("Remove") => receive_remove(context, object, inner_id).await,
169 Some("Undo") => receive_undo(context, object, inner_id, request_counter).await,
170 _ => receive_unhandled_activity(announce),
174 async fn receive_create(
175 context: &LemmyContext,
177 expected_domain: Url,
178 request_counter: &mut i32,
179 ) -> Result<HttpResponse, LemmyError> {
180 let create = Create::from_any_base(activity)?.context(location_info!())?;
181 verify_activity_domains_valid(&create, expected_domain, true)?;
183 match create.object().as_single_kind_str() {
184 Some("Page") => receive_create_post(create, context, request_counter).await,
185 Some("Note") => receive_create_comment(create, context, request_counter).await,
186 _ => receive_unhandled_activity(create),
190 async fn receive_update(
191 context: &LemmyContext,
193 expected_domain: Url,
194 request_counter: &mut i32,
195 ) -> Result<HttpResponse, LemmyError> {
196 let update = Update::from_any_base(activity)?.context(location_info!())?;
197 verify_activity_domains_valid(&update, expected_domain, true)?;
199 match update.object().as_single_kind_str() {
200 Some("Page") => receive_update_post(update, context, request_counter).await,
201 Some("Note") => receive_update_comment(update, context, request_counter).await,
202 _ => receive_unhandled_activity(update),
206 async fn receive_like(
207 context: &LemmyContext,
209 expected_domain: Url,
210 request_counter: &mut i32,
211 ) -> Result<HttpResponse, LemmyError> {
212 let like = Like::from_any_base(activity)?.context(location_info!())?;
213 verify_activity_domains_valid(&like, expected_domain, false)?;
215 match like.object().as_single_kind_str() {
216 Some("Page") => receive_like_post(like, context, request_counter).await,
217 Some("Note") => receive_like_comment(like, context, request_counter).await,
218 _ => receive_unhandled_activity(like),
222 async fn receive_dislike(
223 context: &LemmyContext,
225 expected_domain: Url,
226 request_counter: &mut i32,
227 ) -> Result<HttpResponse, LemmyError> {
228 let enable_downvotes = blocking(context.pool(), move |conn| {
229 Site::read(conn, 1).map(|s| s.enable_downvotes)
232 if !enable_downvotes {
233 return Ok(HttpResponse::Ok().finish());
236 let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
237 verify_activity_domains_valid(&dislike, expected_domain, false)?;
239 match dislike.object().as_single_kind_str() {
240 Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
241 Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
242 _ => receive_unhandled_activity(dislike),
246 pub async fn receive_delete(
247 context: &LemmyContext,
249 expected_domain: Url,
250 request_counter: &mut i32,
251 ) -> Result<HttpResponse, LemmyError> {
252 let delete = Delete::from_any_base(activity)?.context(location_info!())?;
253 verify_activity_domains_valid(&delete, expected_domain, true)?;
258 .single_xsd_any_uri()
259 .context(location_info!())?;
261 match find_by_id(context, object).await {
262 Ok(FindResults::Post(p)) => receive_delete_post(context, delete, p, request_counter).await,
263 Ok(FindResults::Comment(c)) => {
264 receive_delete_comment(context, delete, c, request_counter).await
266 Ok(FindResults::Community(c)) => {
267 receive_delete_community(context, delete, c, request_counter).await
269 // if we dont have the object, no need to do anything
270 Err(_) => Ok(HttpResponse::Ok().finish()),
274 async fn receive_remove(
275 context: &LemmyContext,
277 expected_domain: Url,
278 ) -> Result<HttpResponse, LemmyError> {
279 let remove = Remove::from_any_base(activity)?.context(location_info!())?;
280 verify_activity_domains_valid(&remove, expected_domain, false)?;
284 .map(|c| c.as_many())
286 .context(location_info!())?;
287 let community_id = cc
289 .map(|c| c.as_xsd_any_uri())
291 .context(location_info!())?;
296 .single_xsd_any_uri()
297 .context(location_info!())?;
299 // Ensure that remove activity comes from the same domain as the community
300 remove.id(community_id.domain().context(location_info!())?)?;
302 match find_by_id(context, object).await {
303 Ok(FindResults::Post(p)) => receive_remove_post(context, remove, p).await,
304 Ok(FindResults::Comment(c)) => receive_remove_comment(context, remove, c).await,
305 Ok(FindResults::Community(c)) => receive_remove_community(context, remove, c).await,
306 // if we dont have the object, no need to do anything
307 Err(_) => Ok(HttpResponse::Ok().finish()),
311 async fn receive_undo(
312 context: &LemmyContext,
314 expected_domain: Url,
315 request_counter: &mut i32,
316 ) -> Result<HttpResponse, LemmyError> {
317 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
318 verify_activity_domains_valid(&undo, expected_domain.to_owned(), true)?;
320 match undo.object().as_single_kind_str() {
321 Some("Delete") => receive_undo_delete(context, undo, expected_domain, request_counter).await,
322 Some("Remove") => receive_undo_remove(context, undo, expected_domain, request_counter).await,
323 Some("Like") => receive_undo_like(context, undo, expected_domain, request_counter).await,
324 Some("Dislike") => receive_undo_dislike(context, undo, expected_domain, request_counter).await,
325 _ => receive_unhandled_activity(undo),
329 async fn receive_undo_delete(
330 context: &LemmyContext,
332 expected_domain: Url,
333 request_counter: &mut i32,
334 ) -> Result<HttpResponse, LemmyError> {
335 let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
336 .context(location_info!())?;
337 verify_activity_domains_valid(&delete, expected_domain, true)?;
342 .single_xsd_any_uri()
343 .context(location_info!())?;
344 match find_by_id(context, object).await {
345 Ok(FindResults::Post(p)) => receive_undo_delete_post(context, undo, p, request_counter).await,
346 Ok(FindResults::Comment(c)) => {
347 receive_undo_delete_comment(context, undo, c, request_counter).await
349 Ok(FindResults::Community(c)) => {
350 receive_undo_delete_community(context, undo, c, request_counter).await
352 // if we dont have the object, no need to do anything
353 Err(_) => Ok(HttpResponse::Ok().finish()),
357 async fn receive_undo_remove(
358 context: &LemmyContext,
360 expected_domain: Url,
361 request_counter: &mut i32,
362 ) -> Result<HttpResponse, LemmyError> {
363 let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
364 .context(location_info!())?;
365 verify_activity_domains_valid(&remove, expected_domain, false)?;
370 .single_xsd_any_uri()
371 .context(location_info!())?;
372 match find_by_id(context, object).await {
373 Ok(FindResults::Post(p)) => receive_undo_remove_post(context, undo, p, request_counter).await,
374 Ok(FindResults::Comment(c)) => {
375 receive_undo_remove_comment(context, undo, c, request_counter).await
377 Ok(FindResults::Community(c)) => {
378 receive_undo_remove_community(context, undo, c, request_counter).await
380 // if we dont have the object, no need to do anything
381 Err(_) => Ok(HttpResponse::Ok().finish()),
385 async fn receive_undo_like(
386 context: &LemmyContext,
388 expected_domain: Url,
389 request_counter: &mut i32,
390 ) -> Result<HttpResponse, LemmyError> {
391 let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
392 .context(location_info!())?;
393 verify_activity_domains_valid(&like, expected_domain, false)?;
397 .as_single_kind_str()
398 .context(location_info!())?;
400 "Note" => receive_undo_like_comment(undo, &like, context, request_counter).await,
401 "Page" => receive_undo_like_post(undo, &like, context, request_counter).await,
402 d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
406 async fn receive_undo_dislike(
407 context: &LemmyContext,
409 expected_domain: Url,
410 request_counter: &mut i32,
411 ) -> Result<HttpResponse, LemmyError> {
412 let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
413 .context(location_info!())?;
414 verify_activity_domains_valid(&dislike, expected_domain, false)?;
418 .as_single_kind_str()
419 .context(location_info!())?;
421 "Note" => receive_undo_dislike_comment(undo, &dislike, context, request_counter).await,
422 "Page" => receive_undo_dislike_post(undo, &dislike, context, request_counter).await,
423 d => Err(anyhow!("Undo Delete type {} not supported", d).into()),