]> Untitled Git - lemmy.git/commitdiff
Merge branch 'main' into federation-authorisation
authorFelix Ableitner <me@nutomic.com>
Fri, 7 Aug 2020 13:19:08 +0000 (15:19 +0200)
committerFelix Ableitner <me@nutomic.com>
Fri, 7 Aug 2020 13:19:08 +0000 (15:19 +0200)
20 files changed:
docs/src/contributing_federation_development.md
server/src/api/mod.rs
server/src/apub/comment.rs
server/src/apub/community.rs
server/src/apub/fetcher.rs
server/src/apub/inbox/activities/announce.rs
server/src/apub/inbox/activities/create.rs
server/src/apub/inbox/activities/delete.rs
server/src/apub/inbox/activities/dislike.rs
server/src/apub/inbox/activities/like.rs
server/src/apub/inbox/activities/remove.rs
server/src/apub/inbox/activities/undo.rs
server/src/apub/inbox/activities/update.rs
server/src/apub/inbox/community_inbox.rs
server/src/apub/inbox/shared_inbox.rs
server/src/apub/inbox/user_inbox.rs
server/src/apub/mod.rs
server/src/apub/post.rs
server/src/apub/private_message.rs
server/src/apub/user.rs

index 143ae9f8bd1e0532bcab644fd8d09c08ca4c7e21..8af38a077d11cfe6a6ba4ae7409f20897e8b05e9 100644 (file)
@@ -68,3 +68,16 @@ cd /lemmy/
 sudo docker-compose pull
 sudo docker-compose up -d
 ```
+
+## Security Model
+
+- HTTP signature verify: This ensures that activity really comes from the activity that it claims
+- check_is_apub_valid : Makes sure its in our allowed instances list
+- Lower level checks: To make sure that the user that creates/updates/removes a post is actually on the same instance as that post
+
+For the last point, note that we are *not* checking whether the actor that sends the create activity for a post is
+actually identical to the post's creator, or that the user that removes a post is a mod/admin. These things are checked
+by the API code, and its the responsibility of each instance to check user permissions. This does not leave any attack
+vector, as a normal instance user cant do actions that violate the API rules. The only one who could do that is the
+admin (and the software deployed by the admin). But the admin can do anything on the instance, including send activities
+from other user accounts. So we wouldnt actually gain any security by checking mod permissions or similar.
\ No newline at end of file
index a9aae823a5b53cee0c930ef58b25b9750375a7f9..8124cd4a1a0f74f9d44b4f1749e67612684d5d64 100644 (file)
@@ -55,7 +55,7 @@ pub trait Perform {
   ) -> Result<Self::Response, LemmyError>;
 }
 
-pub async fn is_mod_or_admin(
+pub(in crate::api) async fn is_mod_or_admin(
   pool: &DbPool,
   user_id: i32,
   community_id: i32,
@@ -65,7 +65,7 @@ pub async fn is_mod_or_admin(
   })
   .await?;
   if !is_mod_or_admin {
-    return Err(APIError::err("not_an_admin").into());
+    return Err(APIError::err("not_a_mod_or_admin").into());
   }
   Ok(())
 }
@@ -104,14 +104,14 @@ pub(in crate::api) async fn get_user_from_jwt_opt(
   }
 }
 
-pub(in crate::api) fn check_slurs(text: &str) -> Result<(), APIError> {
+pub(in crate) fn check_slurs(text: &str) -> Result<(), APIError> {
   if let Err(slurs) = slur_check(text) {
     Err(APIError::err(&slurs_vec_to_str(slurs)))
   } else {
     Ok(())
   }
 }
-pub(in crate::api) fn check_slurs_opt(text: &Option<String>) -> Result<(), APIError> {
+pub(in crate) fn check_slurs_opt(text: &Option<String>) -> Result<(), APIError> {
   match text {
     Some(t) => check_slurs(t),
     None => Ok(()),
index 05b40dbe5d242d7f16ee8411cb6bb5fb0c0b8b98..fbec59051733e8610439e6d5d2c529795b88d2ef 100644 (file)
@@ -1,6 +1,7 @@
 use crate::{
   apub::{
     activities::{generate_activity_id, send_activity_to_community},
+    check_actor_domain,
     create_apub_response,
     create_apub_tombstone_response,
     create_tombstone,
@@ -48,7 +49,7 @@ use lemmy_db::{
   user::User_,
   Crud,
 };
-use lemmy_utils::{convert_datetime, scrape_text_for_mentions, MentionData};
+use lemmy_utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData};
 use log::debug;
 use serde::Deserialize;
 use serde_json::Error;
@@ -131,6 +132,7 @@ impl FromApub for CommentForm {
     note: &Note,
     client: &Client,
     pool: &DbPool,
+    expected_domain: Option<Url>,
   ) -> Result<CommentForm, LemmyError> {
     let creator_actor_id = &note
       .attributed_to()
@@ -165,23 +167,25 @@ impl FromApub for CommentForm {
       }
       None => None,
     };
+    let content = note
+      .content()
+      .unwrap()
+      .as_single_xsd_string()
+      .unwrap()
+      .to_string();
+    let content_slurs_removed = remove_slurs(&content);
 
     Ok(CommentForm {
       creator_id: creator.id,
       post_id: post.id,
       parent_id,
-      content: note
-        .content()
-        .unwrap()
-        .as_single_xsd_string()
-        .unwrap()
-        .to_string(),
+      content: content_slurs_removed,
       removed: None,
       read: None,
       published: note.published().map(|u| u.to_owned().naive_local()),
       updated: note.updated().map(|u| u.to_owned().naive_local()),
       deleted: None,
-      ap_id: note.id_unchecked().unwrap().to_string(),
+      ap_id: check_actor_domain(note, expected_domain)?,
       local: false,
     })
   }
index 549d9349f6bf67881a20eca94ae143ecf915e3f2..8b522b447a6dcd6bd20e66a84c96f74fff27d0c9 100644 (file)
@@ -1,6 +1,8 @@
 use crate::{
+  api::{check_slurs, check_slurs_opt},
   apub::{
     activities::{generate_activity_id, send_activity},
+    check_actor_domain,
     create_apub_response,
     create_apub_tombstone_response,
     create_tombstone,
@@ -322,7 +324,12 @@ impl FromApub for CommunityForm {
   type ApubType = GroupExt;
 
   /// Parse an ActivityPub group received from another instance into a Lemmy community.
-  async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result<Self, LemmyError> {
+  async fn from_apub(
+    group: &GroupExt,
+    client: &Client,
+    pool: &DbPool,
+    expected_domain: Option<Url>,
+  ) -> Result<Self, LemmyError> {
     let creator_and_moderator_uris = group.inner.attributed_to().unwrap();
     let creator_uri = creator_and_moderator_uris
       .as_many()
@@ -334,6 +341,25 @@ impl FromApub for CommunityForm {
       .unwrap();
 
     let creator = get_or_fetch_and_upsert_user(creator_uri, client, pool).await?;
+    let name = group
+      .inner
+      .name()
+      .unwrap()
+      .as_one()
+      .unwrap()
+      .as_xsd_string()
+      .unwrap()
+      .to_string();
+    let title = group.inner.preferred_username().unwrap().to_string();
+    // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
+    //       -> same for post.content etc
+    let description = group
+      .inner
+      .content()
+      .map(|s| s.as_single_xsd_string().unwrap().into());
+    check_slurs(&name)?;
+    check_slurs(&title)?;
+    check_slurs_opt(&description)?;
 
     let icon = match group.icon() {
       Some(any_image) => Some(
@@ -362,22 +388,9 @@ impl FromApub for CommunityForm {
     };
 
     Ok(CommunityForm {
-      name: group
-        .inner
-        .name()
-        .unwrap()
-        .as_one()
-        .unwrap()
-        .as_xsd_string()
-        .unwrap()
-        .into(),
-      title: group.inner.preferred_username().unwrap().to_string(),
-      // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
-      //       -> same for post.content etc
-      description: group
-        .inner
-        .content()
-        .map(|s| s.as_single_xsd_string().unwrap().into()),
+      name,
+      title,
+      description,
       category_id: group.ext_one.category.identifier.parse::<i32>()?,
       creator_id: creator.id,
       removed: None,
@@ -385,7 +398,7 @@ impl FromApub for CommunityForm {
       updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
       deleted: None,
       nsfw: group.ext_one.sensitive,
-      actor_id: group.inner.id_unchecked().unwrap().to_string(),
+      actor_id: check_actor_domain(group, expected_domain)?,
       local: false,
       private_key: None,
       public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
@@ -486,12 +499,13 @@ pub async fn do_announce(
 
   insert_activity(community.creator_id, announce.clone(), true, pool).await?;
 
-  // dont send to the instance where the activity originally came from, because that would result
-  // in a database error (same data inserted twice)
   let mut to = community.get_follower_inboxes(pool).await?;
 
+  // dont send to the local instance, nor to the instance where the activity originally came from,
+  // because that would result in a database error (same data inserted twice)
   // this seems to be the "easiest" stable alternative for remove_item()
   to.retain(|x| *x != sender.get_shared_inbox_url());
+  to.retain(|x| *x != community.get_shared_inbox_url());
 
   send_activity(client, &announce.into_any_base()?, community, to).await?;
 
index 919b0e884aa9a2eb82b0824473ffd49c29e8b613..b59e9e321de8a24dcba8b1496142ff8c17969ce6 100644 (file)
@@ -172,7 +172,7 @@ pub async fn search_by_apub_id(
       response
     }
     SearchAcceptedObjects::Page(p) => {
-      let post_form = PostForm::from_apub(&p, client, pool).await?;
+      let post_form = PostForm::from_apub(&p, client, pool, Some(query_url)).await?;
 
       let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
       response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??];
@@ -185,8 +185,8 @@ pub async fn search_by_apub_id(
       // TODO: also fetch parent comments if any
       let x = post_url.first().unwrap().as_xsd_any_uri().unwrap();
       let post = fetch_remote_object(client, x).await?;
-      let post_form = PostForm::from_apub(&post, client, pool).await?;
-      let comment_form = CommentForm::from_apub(&c, client, pool).await?;
+      let post_form = PostForm::from_apub(&post, client, pool, Some(query_url.clone())).await?;
+      let comment_form = CommentForm::from_apub(&c, client, pool, Some(query_url)).await?;
 
       blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
       let c = blocking(pool, move |conn| upsert_comment(&comment_form, conn)).await??;
@@ -221,7 +221,7 @@ pub async fn get_or_fetch_and_upsert_user(
 ) -> Result<User_, LemmyError> {
   let apub_id_owned = apub_id.to_owned();
   let user = blocking(pool, move |conn| {
-    User_::read_from_actor_id(conn, apub_id_owned.as_str())
+    User_::read_from_actor_id(conn, apub_id_owned.as_ref())
   })
   .await?;
 
@@ -231,7 +231,7 @@ pub async fn get_or_fetch_and_upsert_user(
       debug!("Fetching and updating from remote user: {}", apub_id);
       let person = fetch_remote_object::<PersonExt>(client, apub_id).await?;
 
-      let mut uf = UserForm::from_apub(&person, client, pool).await?;
+      let mut uf = UserForm::from_apub(&person, client, pool, Some(apub_id.to_owned())).await?;
       uf.last_refreshed_at = Some(naive_now());
       let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??;
 
@@ -242,7 +242,7 @@ pub async fn get_or_fetch_and_upsert_user(
       debug!("Fetching and creating remote user: {}", apub_id);
       let person = fetch_remote_object::<PersonExt>(client, apub_id).await?;
 
-      let uf = UserForm::from_apub(&person, client, pool).await?;
+      let uf = UserForm::from_apub(&person, client, pool, Some(apub_id.to_owned())).await?;
       let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
 
       Ok(user)
@@ -300,7 +300,7 @@ async fn fetch_remote_community(
 ) -> Result<Community, LemmyError> {
   let group = fetch_remote_object::<GroupExt>(client, apub_id).await?;
 
-  let cf = CommunityForm::from_apub(&group, client, pool).await?;
+  let cf = CommunityForm::from_apub(&group, client, pool, Some(apub_id.to_owned())).await?;
   let community = blocking(pool, move |conn| {
     if let Some(cid) = community_id {
       Community::update(conn, cid, &cf)
@@ -350,7 +350,7 @@ async fn fetch_remote_community(
   let outbox_items = outbox.items().unwrap().clone();
   for o in outbox_items.many().unwrap() {
     let page = PageExt::from_any_base(o)?.unwrap();
-    let post = PostForm::from_apub(&page, client, pool).await?;
+    let post = PostForm::from_apub(&page, client, pool, None).await?;
     let post_ap_id = post.ap_id.clone();
     // Check whether the post already exists in the local db
     let existing = blocking(pool, move |conn| Post::read_from_apub_id(conn, &post_ap_id)).await?;
@@ -358,6 +358,7 @@ async fn fetch_remote_community(
       Ok(e) => blocking(pool, move |conn| Post::update(conn, e.id, &post)).await??,
       Err(_) => blocking(pool, move |conn| Post::create(conn, &post)).await??,
     };
+    // TODO: we need to send a websocket update here
   }
 
   Ok(community)
@@ -388,7 +389,7 @@ pub async fn get_or_fetch_and_insert_post(
     Err(NotFound {}) => {
       debug!("Fetching and creating remote post: {}", post_ap_id);
       let post = fetch_remote_object::<PageExt>(client, post_ap_id).await?;
-      let post_form = PostForm::from_apub(&post, client, pool).await?;
+      let post_form = PostForm::from_apub(&post, client, pool, Some(post_ap_id.to_owned())).await?;
 
       let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
 
@@ -426,7 +427,8 @@ pub async fn get_or_fetch_and_insert_comment(
         comment_ap_id
       );
       let comment = fetch_remote_object::<Note>(client, comment_ap_id).await?;
-      let comment_form = CommentForm::from_apub(&comment, client, pool).await?;
+      let comment_form =
+        CommentForm::from_apub(&comment, client, pool, Some(comment_ap_id.to_owned())).await?;
 
       let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;
 
index 8ca4856feffc4695134ca684f312e9d67d526668..0ed04835f22b8f79ae845e8fe4e228e7ab8479b0 100644 (file)
@@ -9,13 +9,17 @@ use crate::{
       undo::receive_undo,
       update::receive_update,
     },
-    shared_inbox::receive_unhandled_activity,
+    shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
   },
   routes::ChatServerParam,
   DbPool,
   LemmyError,
 };
-use activitystreams::{activity::*, base::AnyBase, prelude::ExtendsExt};
+use activitystreams::{
+  activity::*,
+  base::{AnyBase, BaseExt},
+  prelude::ExtendsExt,
+};
 use actix_web::{client::Client, HttpResponse};
 
 pub async fn receive_announce(
@@ -25,6 +29,11 @@ pub async fn receive_announce(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let announce = Announce::from_any_base(activity)?.unwrap();
+
+  // ensure that announce and community come from the same instance
+  let community = get_community_id_from_activity(&announce)?;
+  announce.id(community.domain().unwrap())?;
+
   let kind = announce.object().as_single_kind_str();
   let object = announce.object();
   let object2 = object.clone().one().unwrap();
index ceeef0ef76bfe2acba82ffc7ba430208cd5c6ae7..b45f489a44ec4db9774aa6e05a5aeaa5ecfb42d3 100644 (file)
@@ -9,6 +9,7 @@ use crate::{
       get_user_from_activity,
       receive_unhandled_activity,
     },
+    ActorType,
     FromApub,
     PageExt,
   },
@@ -39,6 +40,11 @@ pub async fn receive_create(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let create = Create::from_any_base(activity)?.unwrap();
+
+  // ensure that create and actor come from the same instance
+  let user = get_user_from_activity(&create, client, pool).await?;
+  create.id(user.actor_id()?.domain().unwrap())?;
+
   match create.object().as_single_kind_str() {
     Some("Page") => receive_create_post(create, client, pool, chat_server).await,
     Some("Note") => receive_create_comment(create, client, pool, chat_server).await,
@@ -55,7 +61,7 @@ async fn receive_create_post(
   let user = get_user_from_activity(&create, client, pool).await?;
   let page = PageExt::from_any_base(create.object().to_owned().one().unwrap())?.unwrap();
 
-  let post = PostForm::from_apub(&page, client, pool).await?;
+  let post = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?)).await?;
 
   let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??;
 
@@ -87,7 +93,7 @@ async fn receive_create_comment(
   let user = get_user_from_activity(&create, client, pool).await?;
   let note = Note::from_any_base(create.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment = CommentForm::from_apub(&note, client, pool).await?;
+  let comment = CommentForm::from_apub(&note, client, pool, Some(user.actor_id()?)).await?;
 
   let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
 
index 627d15f6a792c4d1c3be53b3cb082c025d204c57..1a48931026c73aafe7a4fb248520ed199bbc3412 100644 (file)
@@ -7,6 +7,7 @@ use crate::{
       get_user_from_activity,
       receive_unhandled_activity,
     },
+    ActorType,
     FromApub,
     GroupExt,
     PageExt,
@@ -57,7 +58,7 @@ async fn receive_delete_post(
   let user = get_user_from_activity(&delete, client, pool).await?;
   let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
 
-  let post_ap_id = PostForm::from_apub(&page, client, pool)
+  let post_ap_id = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?))
     .await?
     .get_ap_id()?;
 
@@ -111,7 +112,7 @@ async fn receive_delete_comment(
   let user = get_user_from_activity(&delete, client, pool).await?;
   let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment_ap_id = CommentForm::from_apub(&note, client, pool)
+  let comment_ap_id = CommentForm::from_apub(&note, client, pool, Some(user.actor_id()?))
     .await?
     .get_ap_id()?;
 
@@ -168,7 +169,7 @@ async fn receive_delete_community(
   let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
   let user = get_user_from_activity(&delete, client, pool).await?;
 
-  let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+  let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(user.actor_id()?))
     .await?
     .actor_id;
 
index 1e67d192231cf0cf0619fabdfaf524b494bcf01a..95e2438eff9ba7e07c57d7ad8958b98914aa894c 100644 (file)
@@ -52,7 +52,7 @@ async fn receive_dislike_post(
   let user = get_user_from_activity(&dislike, client, pool).await?;
   let page = PageExt::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap();
 
-  let post = PostForm::from_apub(&page, client, pool).await?;
+  let post = PostForm::from_apub(&page, client, pool, None).await?;
 
   let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
     .await?
@@ -93,7 +93,7 @@ async fn receive_dislike_comment(
   let note = Note::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap();
   let user = get_user_from_activity(&dislike, client, pool).await?;
 
-  let comment = CommentForm::from_apub(&note, client, pool).await?;
+  let comment = CommentForm::from_apub(&note, client, pool, None).await?;
 
   let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
     .await?
index 9061773d0cc60db678884b74ee5c3eaf24f90403..f2705f222f76461d1b68d22434bf7f2500c82116 100644 (file)
@@ -52,7 +52,7 @@ async fn receive_like_post(
   let user = get_user_from_activity(&like, client, pool).await?;
   let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
 
-  let post = PostForm::from_apub(&page, client, pool).await?;
+  let post = PostForm::from_apub(&page, client, pool, None).await?;
 
   let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
     .await?
@@ -93,7 +93,7 @@ async fn receive_like_comment(
   let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
   let user = get_user_from_activity(&like, client, pool).await?;
 
-  let comment = CommentForm::from_apub(&note, client, pool).await?;
+  let comment = CommentForm::from_apub(&note, client, pool, None).await?;
 
   let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
     .await?
index 91fca995d002f246d909d49bb903f7212e55ce42..f4869ae15929f5d0d56d40cb3d2fc29573ce3aea 100644 (file)
@@ -4,9 +4,11 @@ use crate::{
     fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
     inbox::shared_inbox::{
       announce_if_community_is_local,
+      get_community_id_from_activity,
       get_user_from_activity,
       receive_unhandled_activity,
     },
+    ActorType,
     FromApub,
     GroupExt,
     PageExt,
@@ -22,6 +24,7 @@ use crate::{
 };
 use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
 use actix_web::{client::Client, HttpResponse};
+use anyhow::anyhow;
 use lemmy_db::{
   comment::{Comment, CommentForm},
   comment_view::CommentView,
@@ -40,6 +43,12 @@ pub async fn receive_remove(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let remove = Remove::from_any_base(activity)?.unwrap();
+  let actor = get_user_from_activity(&remove, client, pool).await?;
+  let community = get_community_id_from_activity(&remove)?;
+  if actor.actor_id()?.domain() != community.domain() {
+    return Err(anyhow!("Remove activities are only allowed on local objects").into());
+  }
+
   match remove.object().as_single_kind_str() {
     Some("Page") => receive_remove_post(remove, client, pool, chat_server).await,
     Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await,
@@ -57,7 +66,7 @@ async fn receive_remove_post(
   let mod_ = get_user_from_activity(&remove, client, pool).await?;
   let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let post_ap_id = PostForm::from_apub(&page, client, pool)
+  let post_ap_id = PostForm::from_apub(&page, client, pool, None)
     .await?
     .get_ap_id()?;
 
@@ -111,7 +120,7 @@ async fn receive_remove_comment(
   let mod_ = get_user_from_activity(&remove, client, pool).await?;
   let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment_ap_id = CommentForm::from_apub(&note, client, pool)
+  let comment_ap_id = CommentForm::from_apub(&note, client, pool, None)
     .await?
     .get_ap_id()?;
 
@@ -168,7 +177,7 @@ async fn receive_remove_community(
   let mod_ = get_user_from_activity(&remove, client, pool).await?;
   let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+  let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(mod_.actor_id()?))
     .await?
     .actor_id;
 
index 87c78a03e3196c8a081e1eb9e1dfdad9ef684510..34e9e2109b15ebad44ca76438901df0c6a2efaf3 100644 (file)
@@ -7,6 +7,7 @@ use crate::{
       get_user_from_activity,
       receive_unhandled_activity,
     },
+    ActorType,
     FromApub,
     GroupExt,
     PageExt,
@@ -20,7 +21,12 @@ use crate::{
   DbPool,
   LemmyError,
 };
-use activitystreams::{activity::*, base::AnyBase, object::Note, prelude::*};
+use activitystreams::{
+  activity::*,
+  base::{AnyBase, AsBase},
+  object::Note,
+  prelude::*,
+};
 use actix_web::{client::Client, HttpResponse};
 use anyhow::anyhow;
 use lemmy_db::{
@@ -47,11 +53,27 @@ pub async fn receive_undo(
     Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await,
     Some("Like") => receive_undo_like(undo, client, pool, chat_server).await,
     Some("Dislike") => receive_undo_dislike(undo, client, pool, chat_server).await,
-    // TODO: handle undo_dislike?
     _ => receive_unhandled_activity(undo),
   }
 }
 
+fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
+where
+  T: AsBase<A> + ActorAndObjectRef,
+{
+  let outer_actor = outer_activity.actor()?;
+  let outer_actor_uri = outer_actor.as_single_xsd_any_uri().unwrap();
+
+  let inner_actor = inner_activity.actor()?;
+  let inner_actor_uri = inner_actor.as_single_xsd_any_uri().unwrap();
+
+  if outer_actor_uri.domain() != inner_actor_uri.domain() {
+    Err(anyhow!("Cant undo activities from a different instance").into())
+  } else {
+    Ok(())
+  }
+}
+
 async fn receive_undo_delete(
   undo: Undo,
   client: &Client,
@@ -59,6 +81,7 @@ async fn receive_undo_delete(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let delete = Delete::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+  check_is_undo_valid(&undo, &delete)?;
   let type_ = delete.object().as_single_kind_str().unwrap();
   match type_ {
     "Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await,
@@ -75,6 +98,7 @@ async fn receive_undo_remove(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let remove = Remove::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+  check_is_undo_valid(&undo, &remove)?;
 
   let type_ = remove.object().as_single_kind_str().unwrap();
   match type_ {
@@ -92,6 +116,7 @@ async fn receive_undo_like(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let like = Like::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+  check_is_undo_valid(&undo, &like)?;
 
   let type_ = like.object().as_single_kind_str().unwrap();
   match type_ {
@@ -108,6 +133,9 @@ async fn receive_undo_dislike(
   _chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+  check_is_undo_valid(&undo, &dislike)?;
+
+  // TODO: need to implement Undo<Dislike>
 
   let type_ = dislike.object().as_single_kind_str().unwrap();
   Err(anyhow!("Undo Delete type {} not supported", type_).into())
@@ -123,7 +151,7 @@ async fn receive_undo_delete_comment(
   let user = get_user_from_activity(delete, client, pool).await?;
   let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment_ap_id = CommentForm::from_apub(&note, client, pool)
+  let comment_ap_id = CommentForm::from_apub(&note, client, pool, Some(user.actor_id()?))
     .await?
     .get_ap_id()?;
 
@@ -181,7 +209,7 @@ async fn receive_undo_remove_comment(
   let mod_ = get_user_from_activity(remove, client, pool).await?;
   let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment_ap_id = CommentForm::from_apub(&note, client, pool)
+  let comment_ap_id = CommentForm::from_apub(&note, client, pool, None)
     .await?
     .get_ap_id()?;
 
@@ -239,7 +267,7 @@ async fn receive_undo_delete_post(
   let user = get_user_from_activity(delete, client, pool).await?;
   let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
 
-  let post_ap_id = PostForm::from_apub(&page, client, pool)
+  let post_ap_id = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?))
     .await?
     .get_ap_id()?;
 
@@ -294,7 +322,7 @@ async fn receive_undo_remove_post(
   let mod_ = get_user_from_activity(remove, client, pool).await?;
   let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let post_ap_id = PostForm::from_apub(&page, client, pool)
+  let post_ap_id = PostForm::from_apub(&page, client, pool, None)
     .await?
     .get_ap_id()?;
 
@@ -349,7 +377,7 @@ async fn receive_undo_delete_community(
   let user = get_user_from_activity(delete, client, pool).await?;
   let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
 
-  let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+  let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(user.actor_id()?))
     .await?
     .actor_id;
 
@@ -415,7 +443,7 @@ async fn receive_undo_remove_community(
   let mod_ = get_user_from_activity(remove, client, pool).await?;
   let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
 
-  let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+  let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(mod_.actor_id()?))
     .await?
     .actor_id;
 
@@ -481,7 +509,7 @@ async fn receive_undo_like_comment(
   let user = get_user_from_activity(like, client, pool).await?;
   let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
 
-  let comment = CommentForm::from_apub(&note, client, pool).await?;
+  let comment = CommentForm::from_apub(&note, client, pool, None).await?;
 
   let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
     .await?
@@ -527,7 +555,7 @@ async fn receive_undo_like_post(
   let user = get_user_from_activity(like, client, pool).await?;
   let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
 
-  let post = PostForm::from_apub(&page, client, pool).await?;
+  let post = PostForm::from_apub(&page, client, pool, None).await?;
 
   let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
     .await?
index 9af1a2d38c75e18e9b02c3c3ae0561aec5def439..6f2a6d56062a365007ecacbac51f159f2cb95af9 100644 (file)
@@ -10,6 +10,7 @@ use crate::{
       get_user_from_activity,
       receive_unhandled_activity,
     },
+    ActorType,
     FromApub,
     PageExt,
   },
@@ -40,6 +41,11 @@ pub async fn receive_update(
   chat_server: ChatServerParam,
 ) -> Result<HttpResponse, LemmyError> {
   let update = Update::from_any_base(activity)?.unwrap();
+
+  // ensure that update and actor come from the same instance
+  let user = get_user_from_activity(&update, client, pool).await?;
+  update.id(user.actor_id()?.domain().unwrap())?;
+
   match update.object().as_single_kind_str() {
     Some("Page") => receive_update_post(update, client, pool, chat_server).await,
     Some("Note") => receive_update_comment(update, client, pool, chat_server).await,
@@ -56,16 +62,22 @@ async fn receive_update_post(
   let user = get_user_from_activity(&update, client, pool).await?;
   let page = PageExt::from_any_base(update.object().to_owned().one().unwrap())?.unwrap();
 
-  let post = PostForm::from_apub(&page, client, pool).await?;
+  let post = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?)).await?;
 
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
+  let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
     .await?
     .id;
 
-  blocking(pool, move |conn| Post::update(conn, post_id, &post)).await??;
+  blocking(pool, move |conn| {
+    Post::update(conn, original_post_id, &post)
+  })
+  .await??;
 
   // Refetch the view
-  let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
+  let post_view = blocking(pool, move |conn| {
+    PostView::read(conn, original_post_id, None)
+  })
+  .await??;
 
   let res = PostResponse { post: post_view };
 
@@ -88,14 +100,14 @@ async fn receive_update_comment(
   let note = Note::from_any_base(update.object().to_owned().one().unwrap())?.unwrap();
   let user = get_user_from_activity(&update, client, pool).await?;
 
-  let comment = CommentForm::from_apub(&note, client, pool).await?;
+  let comment = CommentForm::from_apub(&note, client, pool, Some(user.actor_id()?)).await?;
 
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
+  let original_comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
     .await?
     .id;
 
   let updated_comment = blocking(pool, move |conn| {
-    Comment::update(conn, comment_id, &comment)
+    Comment::update(conn, original_comment_id, &comment)
   })
   .await??;
 
@@ -107,8 +119,10 @@ async fn receive_update_comment(
     send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?;
 
   // Refetch the view
-  let comment_view =
-    blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
+  let comment_view = blocking(pool, move |conn| {
+    CommentView::read(conn, original_comment_id, None)
+  })
+  .await??;
 
   let res = CommentResponse {
     comment: comment_view,
index 37e7c2895b8efc1970aac4db94bf643981eb6387..8b257d479bb740ebe62dbabc0f1fa9686f22fe3d 100644 (file)
@@ -69,14 +69,16 @@ pub async fn community_inbox(
 
   verify(&request, &user)?;
 
-  insert_activity(user.id, activity.clone(), false, &db).await?;
-
   let any_base = activity.clone().into_any_base()?;
   let kind = activity.kind().unwrap();
-  match kind {
-    ValidTypes::Follow => handle_follow(any_base, user, community, &client, db).await,
-    ValidTypes::Undo => handle_undo_follow(any_base, user, community, db).await,
-  }
+  let user_id = user.id;
+  let res = match kind {
+    ValidTypes::Follow => handle_follow(any_base, user, community, &client, &db).await,
+    ValidTypes::Undo => handle_undo_follow(any_base, user, community, &db).await,
+  };
+
+  insert_activity(user_id, activity.clone(), false, &db).await?;
+  res
 }
 
 /// Handle a follow request from a remote user, adding it to the local database and returning an
@@ -86,7 +88,7 @@ async fn handle_follow(
   user: User_,
   community: Community,
   client: &Client,
-  db: DbPoolParam,
+  db: &DbPoolParam,
 ) -> Result<HttpResponse, LemmyError> {
   let follow = Follow::from_any_base(activity)?.unwrap();
   let community_follower_form = CommunityFollowerForm {
@@ -95,12 +97,12 @@ async fn handle_follow(
   };
 
   // This will fail if they're already a follower, but ignore the error.
-  blocking(&db, move |conn| {
+  blocking(db, move |conn| {
     CommunityFollower::follow(&conn, &community_follower_form).ok()
   })
   .await?;
 
-  community.send_accept_follow(follow, &client, &db).await?;
+  community.send_accept_follow(follow, &client, db).await?;
 
   Ok(HttpResponse::Ok().finish())
 }
@@ -109,7 +111,7 @@ async fn handle_undo_follow(
   activity: AnyBase,
   user: User_,
   community: Community,
-  db: DbPoolParam,
+  db: &DbPoolParam,
 ) -> Result<HttpResponse, LemmyError> {
   let _undo = Undo::from_any_base(activity)?.unwrap();
 
@@ -119,7 +121,7 @@ async fn handle_undo_follow(
   };
 
   // This will fail if they aren't a follower, but ignore the error.
-  blocking(&db, move |conn| {
+  blocking(db, move |conn| {
     CommunityFollower::unfollow(&conn, &community_follower_form).ok()
   })
   .await?;
index db44a99da42f7314af160a45e089ef7cb74a921c..8e1e3c44443ce250e6d53dcd449462f1542960dc 100644 (file)
@@ -68,20 +68,17 @@ pub async fn shared_inbox(
   debug!("Shared inbox received activity: {}", json);
 
   let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap();
-  // TODO: pass this actor in instead of using get_user_from_activity()
-  let actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?;
-
-  let community = get_community_id_from_activity(&activity).await;
+  let community = get_community_id_from_activity(&activity)?;
 
   check_is_apub_id_valid(sender)?;
   check_is_apub_id_valid(&community)?;
-  verify(&request, actor.as_ref())?;
 
-  insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
+  let actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?;
+  verify(&request, actor.as_ref())?;
 
   let any_base = activity.clone().into_any_base()?;
   let kind = activity.kind().unwrap();
-  match kind {
+  let res = match kind {
     ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await,
     ValidTypes::Create => receive_create(any_base, &client, &pool, chat_server).await,
     ValidTypes::Update => receive_update(any_base, &client, &pool, chat_server).await,
@@ -90,7 +87,10 @@ pub async fn shared_inbox(
     ValidTypes::Remove => receive_remove(any_base, &client, &pool, chat_server).await,
     ValidTypes::Delete => receive_delete(any_base, &client, &pool, chat_server).await,
     ValidTypes::Undo => receive_undo(any_base, &client, &pool, chat_server).await,
-  }
+  };
+
+  insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
+  res
 }
 
 pub(in crate::apub::inbox) fn receive_unhandled_activity<A>(
@@ -116,13 +116,15 @@ where
   get_or_fetch_and_upsert_user(&user_uri, client, pool).await
 }
 
-pub(in crate::apub::inbox) async fn get_community_id_from_activity<T, A>(activity: &T) -> Url
+pub(in crate::apub::inbox) fn get_community_id_from_activity<T, A>(
+  activity: &T,
+) -> Result<Url, LemmyError>
 where
   T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
 {
   let cc = activity.cc().unwrap();
   let cc = cc.as_many().unwrap();
-  cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned()
+  Ok(cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned())
 }
 
 pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(
index 494fd9f5bb0ba1ada07260809e1c8fd24b077a29..b443b51c840fa870c2404847b42082fed9158683 100644 (file)
@@ -65,11 +65,9 @@ pub async fn user_inbox(
   let actor = get_or_fetch_and_upsert_actor(actor_uri, &client, &pool).await?;
   verify(&request, actor.as_ref())?;
 
-  insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
-
   let any_base = activity.clone().into_any_base()?;
   let kind = activity.kind().unwrap();
-  match kind {
+  let res = match kind {
     ValidTypes::Accept => receive_accept(any_base, username, &client, &pool).await,
     ValidTypes::Create => {
       receive_create_private_message(any_base, &client, &pool, chat_server).await
@@ -83,7 +81,10 @@ pub async fn user_inbox(
     ValidTypes::Undo => {
       receive_undo_delete_private_message(any_base, &client, &pool, chat_server).await
     }
-  }
+  };
+
+  insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
+  res
 }
 
 /// Handle accepted follows.
@@ -125,7 +126,8 @@ async fn receive_create_private_message(
   let create = Create::from_any_base(activity)?.unwrap();
   let note = Note::from_any_base(create.object().as_one().unwrap().to_owned())?.unwrap();
 
-  let private_message = PrivateMessageForm::from_apub(&note, client, pool).await?;
+  let domain = Some(create.id_unchecked().unwrap().to_owned());
+  let private_message = PrivateMessageForm::from_apub(&note, client, pool, domain).await?;
 
   let inserted_private_message = blocking(pool, move |conn| {
     PrivateMessage::create(conn, &private_message)
@@ -160,7 +162,8 @@ async fn receive_update_private_message(
   let update = Update::from_any_base(activity)?.unwrap();
   let note = Note::from_any_base(update.object().as_one().unwrap().to_owned())?.unwrap();
 
-  let private_message_form = PrivateMessageForm::from_apub(&note, client, pool).await?;
+  let domain = Some(update.id_unchecked().unwrap().to_owned());
+  let private_message_form = PrivateMessageForm::from_apub(&note, client, pool, domain).await?;
 
   let private_message_ap_id = private_message_form.ap_id.clone();
   let private_message = blocking(pool, move |conn| {
@@ -203,7 +206,8 @@ async fn receive_delete_private_message(
   let delete = Delete::from_any_base(activity)?.unwrap();
   let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap();
 
-  let private_message_form = PrivateMessageForm::from_apub(&note, client, pool).await?;
+  let domain = Some(delete.id_unchecked().unwrap().to_owned());
+  let private_message_form = PrivateMessageForm::from_apub(&note, client, pool, domain).await?;
 
   let private_message_ap_id = private_message_form.ap_id;
   let private_message = blocking(pool, move |conn| {
@@ -259,7 +263,8 @@ async fn receive_undo_delete_private_message(
   let delete = Delete::from_any_base(undo.object().as_one().unwrap().to_owned())?.unwrap();
   let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap();
 
-  let private_message = PrivateMessageForm::from_apub(&note, client, pool).await?;
+  let domain = Some(undo.id_unchecked().unwrap().to_owned());
+  let private_message = PrivateMessageForm::from_apub(&note, client, pool, domain).await?;
 
   let private_message_ap_id = private_message.ap_id.clone();
   let private_message_id = blocking(pool, move |conn| {
index 7f39afc7559ef7534a489de381d00a693f87e5e6..69272b49e27703696d05d897472bc4a047023bcf 100644 (file)
@@ -23,6 +23,8 @@ use crate::{
 use activitystreams::{
   activity::Follow,
   actor::{ApActor, Group, Person},
+  base::AsBase,
+  markers::Base,
   object::{Page, Tombstone},
   prelude::*,
 };
@@ -129,10 +131,19 @@ where
 #[async_trait::async_trait(?Send)]
 pub trait FromApub {
   type ApubType;
+  /// Converts an object from ActivityPub type to Lemmy internal type.
+  ///
+  /// * `apub` The object to read from
+  /// * `client` Web client to fetch remote actors with
+  /// * `pool` Database connection
+  /// * `expected_domain` If present, ensure that the apub object comes from the same domain as
+  ///                     this URL
+  ///
   async fn from_apub(
     apub: &Self::ApubType,
     client: &Client,
     pool: &DbPool,
+    expected_domain: Option<Url>,
   ) -> Result<Self, LemmyError>
   where
     Self: Sized;
@@ -178,6 +189,24 @@ pub trait ApubObjectType {
   ) -> Result<(), LemmyError>;
 }
 
+pub(in crate::apub) fn check_actor_domain<T, Kind>(
+  apub: &T,
+  expected_domain: Option<Url>,
+) -> Result<String, LemmyError>
+where
+  T: Base + AsBase<Kind>,
+{
+  let actor_id = if let Some(url) = expected_domain {
+    let domain = url.domain().unwrap();
+    apub.id(domain)?.unwrap()
+  } else {
+    let actor_id = apub.id_unchecked().unwrap();
+    check_is_apub_id_valid(&actor_id)?;
+    actor_id
+  };
+  Ok(actor_id.to_string())
+}
+
 #[async_trait::async_trait(?Send)]
 pub trait ApubLikeableType {
   async fn send_like(
index 4b687b0ae1e615b9b8bf29fa1c095959cff86da1..8bd8364f120a379fec9a46c9c048d7ae8bd6b43d 100644 (file)
@@ -1,6 +1,8 @@
 use crate::{
+  api::check_slurs,
   apub::{
     activities::{generate_activity_id, send_activity_to_community},
+    check_actor_domain,
     create_apub_response,
     create_apub_tombstone_response,
     create_tombstone,
@@ -42,7 +44,7 @@ use lemmy_db::{
   user::User_,
   Crud,
 };
-use lemmy_utils::convert_datetime;
+use lemmy_utils::{convert_datetime, remove_slurs};
 use serde::Deserialize;
 use url::Url;
 
@@ -154,6 +156,7 @@ impl FromApub for PostForm {
     page: &PageExt,
     client: &Client,
     pool: &DbPool,
+    expected_domain: Option<Url>,
   ) -> Result<PostForm, LemmyError> {
     let ext = &page.ext_one;
     let creator_actor_id = page
@@ -203,6 +206,14 @@ impl FromApub for PostForm {
       None => (None, None, None),
     };
 
+    let name = page
+      .inner
+      .summary()
+      .as_ref()
+      .unwrap()
+      .as_single_xsd_string()
+      .unwrap()
+      .to_string();
     let url = page
       .inner
       .url()
@@ -213,17 +224,12 @@ impl FromApub for PostForm {
       .content()
       .as_ref()
       .map(|c| c.as_single_xsd_string().unwrap().to_string());
+    check_slurs(&name)?;
+    let body_slurs_removed = body.map(|b| remove_slurs(&b));
     Ok(PostForm {
-      name: page
-        .inner
-        .summary()
-        .as_ref()
-        .unwrap()
-        .as_single_xsd_string()
-        .unwrap()
-        .to_string(),
+      name,
       url,
-      body,
+      body: body_slurs_removed,
       creator_id: creator.id,
       community_id: community.id,
       removed: None,
@@ -245,7 +251,7 @@ impl FromApub for PostForm {
       embed_description,
       embed_html,
       thumbnail_url,
-      ap_id: page.inner.id_unchecked().unwrap().to_string(),
+      ap_id: check_actor_domain(page, expected_domain)?,
       local: false,
     })
   }
index 69f552d3b4343f853a0a1aa613d10471e71995f9..23975c7c987f43039d4d206d9864dd8f4915d20c 100644 (file)
@@ -1,6 +1,8 @@
 use crate::{
   apub::{
     activities::{generate_activity_id, send_activity},
+    check_actor_domain,
+    check_is_apub_id_valid,
     create_tombstone,
     fetcher::get_or_fetch_and_upsert_user,
     insert_activity,
@@ -75,6 +77,7 @@ impl FromApub for PrivateMessageForm {
     note: &Note,
     client: &Client,
     pool: &DbPool,
+    expected_domain: Option<Url>,
   ) -> Result<PrivateMessageForm, LemmyError> {
     let creator_actor_id = note
       .attributed_to()
@@ -84,10 +87,10 @@ impl FromApub for PrivateMessageForm {
       .unwrap();
 
     let creator = get_or_fetch_and_upsert_user(&creator_actor_id, client, pool).await?;
-
     let recipient_actor_id = note.to().unwrap().clone().single_xsd_any_uri().unwrap();
-
     let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, client, pool).await?;
+    let ap_id = note.id_unchecked().unwrap().to_string();
+    check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
 
     Ok(PrivateMessageForm {
       creator_id: creator.id,
@@ -102,7 +105,7 @@ impl FromApub for PrivateMessageForm {
       updated: note.updated().map(|u| u.to_owned().naive_local()),
       deleted: None,
       read: None,
-      ap_id: note.id_unchecked().unwrap().to_string(),
+      ap_id: check_actor_domain(note, expected_domain)?,
       local: false,
     })
   }
index c20335fec8e0e03421e44672c90e9aaae3442f8b..58338ab4da3c93d77a25e6679fd9724af7fd9c39 100644 (file)
@@ -1,6 +1,8 @@
 use crate::{
+  api::{check_slurs, check_slurs_opt},
   apub::{
     activities::{generate_activity_id, send_activity},
+    check_actor_domain,
     create_apub_response,
     insert_activity,
     ActorType,
@@ -211,7 +213,12 @@ impl ActorType for User_ {
 impl FromApub for UserForm {
   type ApubType = PersonExt;
   /// Parse an ActivityPub person received from another instance into a Lemmy user.
-  async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
+  async fn from_apub(
+    person: &PersonExt,
+    _: &Client,
+    _: &DbPool,
+    expected_domain: Option<Url>,
+  ) -> Result<Self, LemmyError> {
     let avatar = match person.icon() {
       Some(any_image) => Some(
         Image::from_any_base(any_image.as_one().unwrap().clone())
@@ -238,16 +245,26 @@ impl FromApub for UserForm {
       None => None,
     };
 
+    let name = person
+      .name()
+      .unwrap()
+      .one()
+      .unwrap()
+      .as_xsd_string()
+      .unwrap()
+      .to_string();
+    let preferred_username = person.inner.preferred_username().map(|u| u.to_string());
+    let bio = person
+      .inner
+      .summary()
+      .map(|s| s.as_single_xsd_string().unwrap().into());
+    check_slurs(&name)?;
+    check_slurs_opt(&preferred_username)?;
+    check_slurs_opt(&bio)?;
+
     Ok(UserForm {
-      name: person
-        .name()
-        .unwrap()
-        .one()
-        .unwrap()
-        .as_xsd_string()
-        .unwrap()
-        .to_string(),
-      preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
+      name,
+      preferred_username,
       password_encrypted: "".to_string(),
       admin: false,
       banned: false,
@@ -263,11 +280,8 @@ impl FromApub for UserForm {
       show_avatars: false,
       send_notifications_to_email: false,
       matrix_user_id: None,
-      actor_id: person.id_unchecked().unwrap().to_string(),
-      bio: person
-        .inner
-        .summary()
-        .map(|s| s.as_single_xsd_string().unwrap().into()),
+      actor_id: check_actor_domain(person, expected_domain)?,
+      bio,
       local: false,
       private_key: None,
       public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),