Add security checks and slur checks for activitypub inbox

This commit is contained in:
Felix Ableitner 2020-08-06 14:53:58 +02:00
parent 81d4922740
commit cc2c7db9fe
21 changed files with 299 additions and 158 deletions

View file

@ -116,10 +116,7 @@ impl Comment {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::comment::dsl::*; use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id)) diesel::update(comment.find(comment_id))
.set(( .set((deleted.eq(new_deleted), updated.eq(naive_now())))
deleted.eq(new_deleted),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
@ -130,10 +127,7 @@ impl Comment {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::comment::dsl::*; use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id)) diesel::update(comment.find(comment_id))
.set(( .set((removed.eq(new_removed), updated.eq(naive_now())))
removed.eq(new_removed),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }

View file

@ -107,10 +107,7 @@ impl Community {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
diesel::update(community.find(community_id)) diesel::update(community.find(community_id))
.set(( .set((deleted.eq(new_deleted), updated.eq(naive_now())))
deleted.eq(new_deleted),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
@ -121,10 +118,7 @@ impl Community {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
diesel::update(community.find(community_id)) diesel::update(community.find(community_id))
.set(( .set((removed.eq(new_removed), updated.eq(naive_now())))
removed.eq(new_removed),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }

View file

@ -119,10 +119,7 @@ impl Post {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::post::dsl::*; use crate::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set(( .set((deleted.eq(new_deleted), updated.eq(naive_now())))
deleted.eq(new_deleted),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
@ -133,10 +130,7 @@ impl Post {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::post::dsl::*; use crate::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set(( .set((removed.eq(new_removed), updated.eq(naive_now())))
removed.eq(new_removed),
updated.eq(naive_now())
))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }

View file

@ -65,6 +65,7 @@ pub async fn is_mod_or_admin(
}) })
.await?; .await?;
if !is_mod_or_admin { if !is_mod_or_admin {
// TODO: more accurately, not_a_mod_or_admin?
return Err(APIError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
Ok(()) Ok(())
@ -104,14 +105,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) { if let Err(slurs) = slur_check(text) {
Err(APIError::err(&slurs_vec_to_str(slurs))) Err(APIError::err(&slurs_vec_to_str(slurs)))
} else { } else {
Ok(()) 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 { match text {
Some(t) => check_slurs(t), Some(t) => check_slurs(t),
None => Ok(()), None => Ok(()),

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
api::check_slurs,
apub::{ apub::{
activities::{generate_activity_id, send_activity_to_community}, activities::{generate_activity_id, send_activity_to_community},
check_is_apub_id_valid, check_actor_domain,
create_apub_response, create_apub_response,
create_apub_tombstone_response, create_apub_tombstone_response,
create_tombstone, create_tombstone,
@ -132,6 +133,7 @@ impl FromApub for CommentForm {
note: &Note, note: &Note,
client: &Client, client: &Client,
pool: &DbPool, pool: &DbPool,
expected_domain: Option<Url>,
) -> Result<CommentForm, LemmyError> { ) -> Result<CommentForm, LemmyError> {
let creator_actor_id = &note let creator_actor_id = &note
.attributed_to() .attributed_to()
@ -166,26 +168,25 @@ impl FromApub for CommentForm {
} }
None => None, None => None,
}; };
let content = note
let ap_id = note.id_unchecked().unwrap().to_string(); .content()
check_is_apub_id_valid(&Url::parse(&ap_id)?)?; .unwrap()
.as_single_xsd_string()
.unwrap()
.to_string();
check_slurs(&content)?;
Ok(CommentForm { Ok(CommentForm {
creator_id: creator.id, creator_id: creator.id,
post_id: post.id, post_id: post.id,
parent_id, parent_id,
content: note content,
.content()
.unwrap()
.as_single_xsd_string()
.unwrap()
.to_string(),
removed: None, removed: None,
read: None, read: None,
published: note.published().map(|u| u.to_owned().naive_local()), published: note.published().map(|u| u.to_owned().naive_local()),
updated: note.updated().map(|u| u.to_owned().naive_local()), updated: note.updated().map(|u| u.to_owned().naive_local()),
deleted: None, deleted: None,
ap_id, ap_id: check_actor_domain(note, expected_domain)?,
local: false, local: false,
}) })
} }

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
api::{check_slurs, check_slurs_opt},
apub::{ apub::{
activities::{generate_activity_id, send_activity}, activities::{generate_activity_id, send_activity},
check_is_apub_id_valid, check_actor_domain,
create_apub_response, create_apub_response,
create_apub_tombstone_response, create_apub_tombstone_response,
create_tombstone, create_tombstone,
@ -323,7 +324,12 @@ impl FromApub for CommunityForm {
type ApubType = GroupExt; type ApubType = GroupExt;
/// Parse an ActivityPub group received from another instance into a Lemmy community. /// 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_and_moderator_uris = group.inner.attributed_to().unwrap();
let creator_uri = creator_and_moderator_uris let creator_uri = creator_and_moderator_uris
.as_many() .as_many()
@ -335,26 +341,30 @@ impl FromApub for CommunityForm {
.unwrap(); .unwrap();
let creator = get_or_fetch_and_upsert_user(creator_uri, client, pool).await?; let creator = get_or_fetch_and_upsert_user(creator_uri, client, pool).await?;
let actor_id = group.inner.id_unchecked().unwrap().to_string(); let name = group
check_is_apub_id_valid(&Url::parse(&actor_id)?)?; .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)?;
Ok(CommunityForm { Ok(CommunityForm {
name: group name,
.inner title,
.name() description,
.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()),
category_id: group.ext_one.category.identifier.parse::<i32>()?, category_id: group.ext_one.category.identifier.parse::<i32>()?,
creator_id: creator.id, creator_id: creator.id,
removed: None, removed: None,
@ -362,7 +372,7 @@ impl FromApub for CommunityForm {
updated: group.inner.updated().map(|u| u.to_owned().naive_local()), updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
deleted: None, deleted: None,
nsfw: group.ext_one.sensitive, nsfw: group.ext_one.sensitive,
actor_id, actor_id: check_actor_domain(group, expected_domain)?,
local: false, local: false,
private_key: None, private_key: None,
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem), public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),

View file

@ -172,7 +172,7 @@ pub async fn search_by_apub_id(
response response
} }
SearchAcceptedObjects::Page(p) => { 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??; 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??]; 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 // TODO: also fetch parent comments if any
let x = post_url.first().unwrap().as_xsd_any_uri().unwrap(); let x = post_url.first().unwrap().as_xsd_any_uri().unwrap();
let post = fetch_remote_object(client, x).await?; let post = fetch_remote_object(client, x).await?;
let post_form = PostForm::from_apub(&post, 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).await?; let comment_form = CommentForm::from_apub(&c, client, pool, Some(query_url)).await?;
blocking(pool, move |conn| upsert_post(&post_form, conn)).await??; blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
let c = blocking(pool, move |conn| upsert_comment(&comment_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> { ) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned(); let apub_id_owned = apub_id.to_owned();
let user = blocking(pool, move |conn| { 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?; .await?;
@ -231,7 +231,7 @@ pub async fn get_or_fetch_and_upsert_user(
debug!("Fetching and updating from remote user: {}", apub_id); debug!("Fetching and updating from remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(client, apub_id).await?; 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()); uf.last_refreshed_at = Some(naive_now());
let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??; 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); debug!("Fetching and creating remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(client, apub_id).await?; 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??; let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
Ok(user) Ok(user)
@ -300,7 +300,7 @@ async fn fetch_remote_community(
) -> Result<Community, LemmyError> { ) -> Result<Community, LemmyError> {
let group = fetch_remote_object::<GroupExt>(client, apub_id).await?; 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| { let community = blocking(pool, move |conn| {
if let Some(cid) = community_id { if let Some(cid) = community_id {
Community::update(conn, cid, &cf) Community::update(conn, cid, &cf)
@ -350,7 +350,7 @@ async fn fetch_remote_community(
let outbox_items = outbox.items().unwrap().clone(); let outbox_items = outbox.items().unwrap().clone();
for o in outbox_items.many().unwrap() { for o in outbox_items.many().unwrap() {
let page = PageExt::from_any_base(o)?.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, Some(apub_id.to_owned())).await?;
let post_ap_id = post.ap_id.clone(); let post_ap_id = post.ap_id.clone();
// Check whether the post already exists in the local db // 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?; let existing = blocking(pool, move |conn| Post::read_from_apub_id(conn, &post_ap_id)).await?;
@ -388,7 +388,7 @@ pub async fn get_or_fetch_and_insert_post(
Err(NotFound {}) => { Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id); debug!("Fetching and creating remote post: {}", post_ap_id);
let post = fetch_remote_object::<PageExt>(client, post_ap_id).await?; 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??; let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
@ -426,7 +426,8 @@ pub async fn get_or_fetch_and_insert_comment(
comment_ap_id comment_ap_id
); );
let comment = fetch_remote_object::<Note>(client, comment_ap_id).await?; 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??; let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;

View file

@ -1,21 +1,28 @@
use crate::{ use crate::{
apub::inbox::{ apub::{
activities::{ inbox::{
create::receive_create, activities::{
delete::receive_delete, create::receive_create,
dislike::receive_dislike, delete::receive_delete,
like::receive_like, dislike::receive_dislike,
remove::receive_remove, like::receive_like,
undo::receive_undo, remove::receive_remove,
update::receive_update, undo::receive_undo,
update::receive_update,
},
shared_inbox::{get_community_from_activity, receive_unhandled_activity},
}, },
shared_inbox::receive_unhandled_activity, ActorType,
}, },
routes::ChatServerParam, routes::ChatServerParam,
DbPool, DbPool,
LemmyError, LemmyError,
}; };
use activitystreams::{activity::*, base::AnyBase, prelude::ExtendsExt}; use activitystreams::{
activity::*,
base::{AnyBase, BaseExt},
prelude::ExtendsExt,
};
use actix_web::{client::Client, HttpResponse}; use actix_web::{client::Client, HttpResponse};
pub async fn receive_announce( pub async fn receive_announce(
@ -25,6 +32,11 @@ pub async fn receive_announce(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let announce = Announce::from_any_base(activity)?.unwrap(); let announce = Announce::from_any_base(activity)?.unwrap();
// ensure that announce and community come from the same instance
let community = get_community_from_activity(&announce, client, pool).await?;
announce.id(community.actor_id()?.domain().unwrap())?;
let kind = announce.object().as_single_kind_str(); let kind = announce.object().as_single_kind_str();
let object = announce.object(); let object = announce.object();
let object2 = object.clone().one().unwrap(); let object2 = object.clone().one().unwrap();

View file

@ -9,6 +9,7 @@ use crate::{
get_user_from_activity, get_user_from_activity,
receive_unhandled_activity, receive_unhandled_activity,
}, },
ActorType,
FromApub, FromApub,
PageExt, PageExt,
}, },
@ -23,6 +24,7 @@ use crate::{
}; };
use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*}; use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*};
use actix_web::{client::Client, HttpResponse}; use actix_web::{client::Client, HttpResponse};
use anyhow::anyhow;
use lemmy_db::{ use lemmy_db::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
comment_view::CommentView, comment_view::CommentView,
@ -39,6 +41,11 @@ pub async fn receive_create(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let create = Create::from_any_base(activity)?.unwrap(); 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() { match create.object().as_single_kind_str() {
Some("Page") => receive_create_post(create, client, pool, chat_server).await, Some("Page") => receive_create_post(create, client, pool, chat_server).await,
Some("Note") => receive_create_comment(create, client, pool, chat_server).await, Some("Note") => receive_create_comment(create, client, pool, chat_server).await,
@ -55,7 +62,11 @@ async fn receive_create_post(
let user = get_user_from_activity(&create, client, pool).await?; let user = get_user_from_activity(&create, client, pool).await?;
let page = PageExt::from_any_base(create.object().to_owned().one().unwrap())?.unwrap(); 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?;
// TODO: not sure if it makes sense to check for the exact user, seeing as we already check the domain
if post.creator_id != user.id {
return Err(anyhow!("Actor for create activity and post creator need to be identical").into());
}
let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??; let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??;
@ -87,7 +98,12 @@ async fn receive_create_comment(
let user = get_user_from_activity(&create, client, pool).await?; let user = get_user_from_activity(&create, client, pool).await?;
let note = Note::from_any_base(create.object().to_owned().one().unwrap())?.unwrap(); 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?;
if comment.creator_id != user.id {
return Err(
anyhow!("Actor for create activity and comment creator need to be identical").into(),
);
}
let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??; let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;

View file

@ -7,6 +7,7 @@ use crate::{
get_user_from_activity, get_user_from_activity,
receive_unhandled_activity, receive_unhandled_activity,
}, },
ActorType,
FromApub, FromApub,
GroupExt, GroupExt,
PageExt, PageExt,
@ -57,7 +58,7 @@ async fn receive_delete_post(
let user = get_user_from_activity(&delete, client, pool).await?; let user = get_user_from_activity(&delete, client, pool).await?;
let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .get_ap_id()?;
@ -111,7 +112,7 @@ async fn receive_delete_comment(
let user = get_user_from_activity(&delete, client, pool).await?; let user = get_user_from_activity(&delete, client, pool).await?;
let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .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 group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
let user = get_user_from_activity(&delete, client, pool).await?; 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? .await?
.actor_id; .actor_id;

View file

@ -52,7 +52,7 @@ async fn receive_dislike_post(
let user = get_user_from_activity(&dislike, client, pool).await?; let user = get_user_from_activity(&dislike, client, pool).await?;
let page = PageExt::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap(); 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) let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
.await? .await?
@ -93,7 +93,7 @@ async fn receive_dislike_comment(
let note = Note::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap(); let note = Note::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap();
let user = get_user_from_activity(&dislike, client, pool).await?; 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) let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
.await? .await?

View file

@ -52,7 +52,7 @@ async fn receive_like_post(
let user = get_user_from_activity(&like, client, pool).await?; let user = get_user_from_activity(&like, client, pool).await?;
let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); 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) let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
.await? .await?
@ -93,7 +93,7 @@ async fn receive_like_comment(
let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
let user = get_user_from_activity(&like, client, pool).await?; 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) let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
.await? .await?

View file

@ -1,12 +1,19 @@
use crate::{ use crate::{
api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse}, api::{
comment::CommentResponse,
community::CommunityResponse,
is_mod_or_admin,
post::PostResponse,
},
apub::{ apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{ inbox::shared_inbox::{
announce_if_community_is_local, announce_if_community_is_local,
get_community_from_activity,
get_user_from_activity, get_user_from_activity,
receive_unhandled_activity, receive_unhandled_activity,
}, },
ActorType,
FromApub, FromApub,
GroupExt, GroupExt,
PageExt, PageExt,
@ -40,6 +47,11 @@ pub async fn receive_remove(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let remove = Remove::from_any_base(activity)?.unwrap(); let remove = Remove::from_any_base(activity)?.unwrap();
let actor = get_user_from_activity(&remove, client, pool).await?;
let community = get_community_from_activity(&remove, client, pool).await?;
// TODO: we dont federate remote admins at all, and remote mods arent federated properly
is_mod_or_admin(pool, actor.id, community.id).await?;
match remove.object().as_single_kind_str() { match remove.object().as_single_kind_str() {
Some("Page") => receive_remove_post(remove, client, pool, chat_server).await, Some("Page") => receive_remove_post(remove, client, pool, chat_server).await,
Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await, Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await,
@ -57,7 +69,7 @@ async fn receive_remove_post(
let mod_ = get_user_from_activity(&remove, client, pool).await?; let mod_ = get_user_from_activity(&remove, client, pool).await?;
let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .get_ap_id()?;
@ -111,7 +123,7 @@ async fn receive_remove_comment(
let mod_ = get_user_from_activity(&remove, client, pool).await?; let mod_ = get_user_from_activity(&remove, client, pool).await?;
let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .get_ap_id()?;
@ -168,7 +180,7 @@ async fn receive_remove_community(
let mod_ = get_user_from_activity(&remove, client, pool).await?; let mod_ = get_user_from_activity(&remove, client, pool).await?;
let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.actor_id; .actor_id;

View file

@ -7,6 +7,7 @@ use crate::{
get_user_from_activity, get_user_from_activity,
receive_unhandled_activity, receive_unhandled_activity,
}, },
ActorType,
FromApub, FromApub,
GroupExt, GroupExt,
PageExt, PageExt,
@ -20,7 +21,12 @@ use crate::{
DbPool, DbPool,
LemmyError, 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 actix_web::{client::Client, HttpResponse};
use anyhow::anyhow; use anyhow::anyhow;
use lemmy_db::{ use lemmy_db::{
@ -47,11 +53,27 @@ pub async fn receive_undo(
Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await, Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await,
Some("Like") => receive_undo_like(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, Some("Dislike") => receive_undo_dislike(undo, client, pool, chat_server).await,
// TODO: handle undo_dislike?
_ => receive_unhandled_activity(undo), _ => 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 != inner_actor_uri {
Err(anyhow!("An actor can only undo its own activities").into())
} else {
Ok(())
}
}
async fn receive_undo_delete( async fn receive_undo_delete(
undo: Undo, undo: Undo,
client: &Client, client: &Client,
@ -59,6 +81,7 @@ async fn receive_undo_delete(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let delete = Delete::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); 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(); let type_ = delete.object().as_single_kind_str().unwrap();
match type_ { match type_ {
"Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await, "Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await,
@ -75,6 +98,7 @@ async fn receive_undo_remove(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let remove = Remove::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); 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(); let type_ = remove.object().as_single_kind_str().unwrap();
match type_ { match type_ {
@ -92,6 +116,7 @@ async fn receive_undo_like(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let like = Like::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); 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(); let type_ = like.object().as_single_kind_str().unwrap();
match type_ { match type_ {
@ -108,6 +133,9 @@ async fn receive_undo_dislike(
_chat_server: ChatServerParam, _chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); 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(); let type_ = dislike.object().as_single_kind_str().unwrap();
Err(anyhow!("Undo Delete type {} not supported", type_).into()) 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 user = get_user_from_activity(delete, client, pool).await?;
let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .get_ap_id()?;
@ -181,7 +209,7 @@ async fn receive_undo_remove_comment(
let mod_ = get_user_from_activity(remove, client, pool).await?; let mod_ = get_user_from_activity(remove, client, pool).await?;
let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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, Some(mod_.actor_id()?))
.await? .await?
.get_ap_id()?; .get_ap_id()?;
@ -239,7 +267,7 @@ async fn receive_undo_delete_post(
let user = get_user_from_activity(delete, client, pool).await?; let user = get_user_from_activity(delete, client, pool).await?;
let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.get_ap_id()?; .get_ap_id()?;
@ -294,7 +322,7 @@ async fn receive_undo_remove_post(
let mod_ = get_user_from_activity(remove, client, pool).await?; let mod_ = get_user_from_activity(remove, client, pool).await?;
let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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, Some(mod_.actor_id()?))
.await? .await?
.get_ap_id()?; .get_ap_id()?;
@ -349,7 +377,7 @@ async fn receive_undo_delete_community(
let user = get_user_from_activity(delete, client, pool).await?; let user = get_user_from_activity(delete, client, pool).await?;
let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.actor_id; .actor_id;
@ -413,7 +441,7 @@ async fn receive_undo_remove_community(
let mod_ = get_user_from_activity(remove, client, pool).await?; let mod_ = get_user_from_activity(remove, client, pool).await?;
let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); 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? .await?
.actor_id; .actor_id;
@ -477,7 +505,7 @@ async fn receive_undo_like_comment(
let user = get_user_from_activity(like, client, pool).await?; let user = get_user_from_activity(like, client, pool).await?;
let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); 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) let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool)
.await? .await?
@ -523,7 +551,7 @@ async fn receive_undo_like_post(
let user = get_user_from_activity(like, client, pool).await?; let user = get_user_from_activity(like, client, pool).await?;
let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); 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) let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
.await? .await?

View file

@ -10,6 +10,7 @@ use crate::{
get_user_from_activity, get_user_from_activity,
receive_unhandled_activity, receive_unhandled_activity,
}, },
ActorType,
FromApub, FromApub,
PageExt, PageExt,
}, },
@ -24,6 +25,7 @@ use crate::{
}; };
use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*}; use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*};
use actix_web::{client::Client, HttpResponse}; use actix_web::{client::Client, HttpResponse};
use anyhow::anyhow;
use lemmy_db::{ use lemmy_db::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
comment_view::CommentView, comment_view::CommentView,
@ -40,6 +42,11 @@ pub async fn receive_update(
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let update = Update::from_any_base(activity)?.unwrap(); 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() { match update.object().as_single_kind_str() {
Some("Page") => receive_update_post(update, client, pool, chat_server).await, Some("Page") => receive_update_post(update, client, pool, chat_server).await,
Some("Note") => receive_update_comment(update, client, pool, chat_server).await, Some("Note") => receive_update_comment(update, client, pool, chat_server).await,
@ -56,16 +63,27 @@ async fn receive_update_post(
let user = get_user_from_activity(&update, client, pool).await?; let user = get_user_from_activity(&update, client, pool).await?;
let page = PageExt::from_any_base(update.object().to_owned().one().unwrap())?.unwrap(); 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?;
if post.creator_id != user.id {
return Err(anyhow!("Actor for update activity and post creator need to be identical").into());
}
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool) let original_post = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool).await?;
.await? if post.ap_id != original_post.ap_id {
.id; return Err(anyhow!("Updated post ID needs to be identical to the original ID").into());
}
blocking(pool, move |conn| Post::update(conn, post_id, &post)).await??; let original_post_id = original_post.id;
blocking(pool, move |conn| {
Post::update(conn, original_post_id, &post)
})
.await??;
// Refetch the view // 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 }; let res = PostResponse { post: post_view };
@ -88,14 +106,20 @@ async fn receive_update_comment(
let note = Note::from_any_base(update.object().to_owned().one().unwrap())?.unwrap(); let note = Note::from_any_base(update.object().to_owned().one().unwrap())?.unwrap();
let user = get_user_from_activity(&update, client, pool).await?; 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?;
if comment.creator_id != user.id {
return Err(anyhow!("Actor for update activity and post creator need to be identical").into());
}
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool) let original_comment =
.await? get_or_fetch_and_insert_comment(&comment.get_ap_id()?, client, pool).await?;
.id; if comment.ap_id != original_comment.ap_id {
return Err(anyhow!("Updated post ID needs to be identical to the original ID").into());
}
let original_comment_id = original_comment.id;
let updated_comment = blocking(pool, move |conn| { let updated_comment = blocking(pool, move |conn| {
Comment::update(conn, comment_id, &comment) Comment::update(conn, original_comment_id, &comment)
}) })
.await??; .await??;
@ -107,8 +131,10 @@ async fn receive_update_comment(
send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?;
// Refetch the view // Refetch the view
let comment_view = let comment_view = blocking(pool, move |conn| {
blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; CommentView::read(conn, original_comment_id, None)
})
.await??;
let res = CommentResponse { let res = CommentResponse {
comment: comment_view, comment: comment_view,

View file

@ -31,7 +31,7 @@ use activitystreams::{
prelude::*, prelude::*,
}; };
use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use lemmy_db::user::User_; use lemmy_db::{community::Community, user::User_};
use log::debug; use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
@ -71,12 +71,13 @@ pub async fn shared_inbox(
// TODO: pass this actor in instead of using get_user_from_activity() // 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 actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?;
let community = get_community_id_from_activity(&activity).await; // TODO: i dont think this works for Announce/Undo activities
//let community = get_community_id_from_activity(&activity).await;
check_is_apub_id_valid(sender)?; check_is_apub_id_valid(sender)?;
check_is_apub_id_valid(&community)?;
verify(&request, actor.as_ref())?; verify(&request, actor.as_ref())?;
// TODO: probably better to do this after, so we dont store activities that fail a check somewhere
insert_activity(actor.user_id(), activity.clone(), false, &pool).await?; insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
let any_base = activity.clone().into_any_base()?; let any_base = activity.clone().into_any_base()?;
@ -116,13 +117,18 @@ where
get_or_fetch_and_upsert_user(&user_uri, client, pool).await 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) async fn get_community_from_activity<T, A>(
activity: &T,
client: &Client,
pool: &DbPool,
) -> Result<Community, LemmyError>
where where
T: AsBase<A> + ActorAndObjectRef + AsObject<A>, T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
{ {
let cc = activity.cc().unwrap(); let cc = activity.cc().unwrap();
let cc = cc.as_many().unwrap(); let cc = cc.as_many().unwrap();
cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned() let community_uri = cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned();
get_or_fetch_and_upsert_community(&community_uri, client, pool).await
} }
pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>( pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(

View file

@ -125,7 +125,8 @@ async fn receive_create_private_message(
let create = Create::from_any_base(activity)?.unwrap(); let create = Create::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(create.object().as_one().unwrap().to_owned())?.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| { let inserted_private_message = blocking(pool, move |conn| {
PrivateMessage::create(conn, &private_message) PrivateMessage::create(conn, &private_message)
@ -160,7 +161,8 @@ async fn receive_update_private_message(
let update = Update::from_any_base(activity)?.unwrap(); let update = Update::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(update.object().as_one().unwrap().to_owned())?.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_ap_id = private_message_form.ap_id.clone();
let private_message = blocking(pool, move |conn| { let private_message = blocking(pool, move |conn| {
@ -203,7 +205,8 @@ async fn receive_delete_private_message(
let delete = Delete::from_any_base(activity)?.unwrap(); let delete = Delete::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.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_ap_id = private_message_form.ap_id;
let private_message = blocking(pool, move |conn| { let private_message = blocking(pool, move |conn| {
@ -259,7 +262,8 @@ async fn receive_undo_delete_private_message(
let delete = Delete::from_any_base(undo.object().as_one().unwrap().to_owned())?.unwrap(); 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 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_ap_id = private_message.ap_id.clone();
let private_message_id = blocking(pool, move |conn| { let private_message_id = blocking(pool, move |conn| {

View file

@ -23,6 +23,8 @@ use crate::{
use activitystreams::{ use activitystreams::{
activity::Follow, activity::Follow,
actor::{ApActor, Group, Person}, actor::{ApActor, Group, Person},
base::AsBase,
markers::Base,
object::{Page, Tombstone}, object::{Page, Tombstone},
prelude::*, prelude::*,
}; };
@ -129,10 +131,19 @@ where
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait FromApub { pub trait FromApub {
type ApubType; 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( async fn from_apub(
apub: &Self::ApubType, apub: &Self::ApubType,
client: &Client, client: &Client,
pool: &DbPool, pool: &DbPool,
expected_domain: Option<Url>,
) -> Result<Self, LemmyError> ) -> Result<Self, LemmyError>
where where
Self: Sized; Self: Sized;
@ -178,6 +189,24 @@ pub trait ApubObjectType {
) -> Result<(), LemmyError>; ) -> 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)] #[async_trait::async_trait(?Send)]
pub trait ApubLikeableType { pub trait ApubLikeableType {
async fn send_like( async fn send_like(

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
api::{check_slurs, check_slurs_opt},
apub::{ apub::{
activities::{generate_activity_id, send_activity_to_community}, activities::{generate_activity_id, send_activity_to_community},
check_is_apub_id_valid, check_actor_domain,
create_apub_response, create_apub_response,
create_apub_tombstone_response, create_apub_tombstone_response,
create_tombstone, create_tombstone,
@ -155,6 +156,7 @@ impl FromApub for PostForm {
page: &PageExt, page: &PageExt,
client: &Client, client: &Client,
pool: &DbPool, pool: &DbPool,
expected_domain: Option<Url>,
) -> Result<PostForm, LemmyError> { ) -> Result<PostForm, LemmyError> {
let ext = &page.ext_one; let ext = &page.ext_one;
let creator_actor_id = page let creator_actor_id = page
@ -204,9 +206,14 @@ impl FromApub for PostForm {
None => (None, None, None), None => (None, None, None),
}; };
let ap_id = page.inner.id_unchecked().unwrap().to_string(); let name = page
check_is_apub_id_valid(&Url::parse(&ap_id)?)?; .inner
.summary()
.as_ref()
.unwrap()
.as_single_xsd_string()
.unwrap()
.to_string();
let url = page let url = page
.inner .inner
.url() .url()
@ -217,15 +224,10 @@ impl FromApub for PostForm {
.content() .content()
.as_ref() .as_ref()
.map(|c| c.as_single_xsd_string().unwrap().to_string()); .map(|c| c.as_single_xsd_string().unwrap().to_string());
check_slurs(&name)?;
check_slurs_opt(&body)?;
Ok(PostForm { Ok(PostForm {
name: page name,
.inner
.summary()
.as_ref()
.unwrap()
.as_single_xsd_string()
.unwrap()
.to_string(),
url, url,
body, body,
creator_id: creator.id, creator_id: creator.id,
@ -249,7 +251,7 @@ impl FromApub for PostForm {
embed_description, embed_description,
embed_html, embed_html,
thumbnail_url, thumbnail_url,
ap_id, ap_id: check_actor_domain(page, expected_domain)?,
local: false, local: false,
}) })
} }

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
apub::{ apub::{
activities::{generate_activity_id, send_activity}, activities::{generate_activity_id, send_activity},
check_actor_domain,
check_is_apub_id_valid, check_is_apub_id_valid,
create_tombstone, create_tombstone,
fetcher::get_or_fetch_and_upsert_user, fetcher::get_or_fetch_and_upsert_user,
@ -76,6 +77,7 @@ impl FromApub for PrivateMessageForm {
note: &Note, note: &Note,
client: &Client, client: &Client,
pool: &DbPool, pool: &DbPool,
expected_domain: Option<Url>,
) -> Result<PrivateMessageForm, LemmyError> { ) -> Result<PrivateMessageForm, LemmyError> {
let creator_actor_id = note let creator_actor_id = note
.attributed_to() .attributed_to()
@ -103,7 +105,7 @@ impl FromApub for PrivateMessageForm {
updated: note.updated().map(|u| u.to_owned().naive_local()), updated: note.updated().map(|u| u.to_owned().naive_local()),
deleted: None, deleted: None,
read: None, read: None,
ap_id, ap_id: check_actor_domain(note, expected_domain)?,
local: false, local: false,
}) })
} }

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
api::{check_slurs, check_slurs_opt},
apub::{ apub::{
activities::{generate_activity_id, send_activity}, activities::{generate_activity_id, send_activity},
check_is_apub_id_valid, check_actor_domain,
create_apub_response, create_apub_response,
insert_activity, insert_activity,
ActorType, ActorType,
@ -206,7 +207,12 @@ impl ActorType for User_ {
impl FromApub for UserForm { impl FromApub for UserForm {
type ApubType = PersonExt; type ApubType = PersonExt;
/// Parse an ActivityPub person received from another instance into a Lemmy user. /// 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() { let avatar = match person.icon() {
Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone()) Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
.unwrap() .unwrap()
@ -218,21 +224,26 @@ impl FromApub for UserForm {
None => None, None => None,
}; };
// TODO: here and in community we could actually check against the exact domain where we fetched let name = person
// the actor from, if we can pass it in somehow .name()
let actor_id = person.id_unchecked().unwrap().to_string(); .unwrap()
check_is_apub_id_valid(&Url::parse(&actor_id)?)?; .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 { Ok(UserForm {
name: person name,
.name() preferred_username,
.unwrap()
.one()
.unwrap()
.as_xsd_string()
.unwrap()
.to_string(),
preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
password_encrypted: "".to_string(), password_encrypted: "".to_string(),
admin: false, admin: false,
banned: false, banned: false,
@ -247,11 +258,8 @@ impl FromApub for UserForm {
show_avatars: false, show_avatars: false,
send_notifications_to_email: false, send_notifications_to_email: false,
matrix_user_id: None, matrix_user_id: None,
actor_id, actor_id: check_actor_domain(person, expected_domain)?,
bio: person bio,
.inner
.summary()
.map(|s| s.as_single_xsd_string().unwrap().into()),
local: false, local: false,
private_key: None, private_key: None,
public_key: Some(person.ext_one.public_key.to_owned().public_key_pem), public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),