Merge pull request #1064 from LemmyNet/apub_security_checks
Apub security checks
This commit is contained in:
commit
ded7650a60
22 changed files with 363 additions and 154 deletions
13
docs/src/contributing_federation_development.md
vendored
13
docs/src/contributing_federation_development.md
vendored
|
@ -68,3 +68,16 @@ cd /lemmy/
|
||||||
sudo docker-compose pull
|
sudo docker-compose pull
|
||||||
sudo docker-compose up -d
|
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.
|
|
@ -55,7 +55,7 @@ pub trait Perform {
|
||||||
) -> Result<Self::Response, LemmyError>;
|
) -> Result<Self::Response, LemmyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_mod_or_admin(
|
pub(in crate::api) async fn is_mod_or_admin(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
user_id: i32,
|
user_id: i32,
|
||||||
community_id: i32,
|
community_id: i32,
|
||||||
|
@ -65,7 +65,7 @@ pub async fn is_mod_or_admin(
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
if !is_mod_or_admin {
|
if !is_mod_or_admin {
|
||||||
return Err(APIError::err("not_an_admin").into());
|
return Err(APIError::err("not_a_mod_or_admin").into());
|
||||||
}
|
}
|
||||||
Ok(())
|
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) {
|
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(()),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
activities::{generate_activity_id, send_activity_to_community},
|
activities::{generate_activity_id, send_activity_to_community},
|
||||||
|
check_actor_domain,
|
||||||
create_apub_response,
|
create_apub_response,
|
||||||
create_apub_tombstone_response,
|
create_apub_tombstone_response,
|
||||||
create_tombstone,
|
create_tombstone,
|
||||||
|
@ -48,7 +49,7 @@ use lemmy_db::{
|
||||||
user::User_,
|
user::User_,
|
||||||
Crud,
|
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 log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Error;
|
use serde_json::Error;
|
||||||
|
@ -131,6 +132,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 = ¬e
|
let creator_actor_id = ¬e
|
||||||
.attributed_to()
|
.attributed_to()
|
||||||
|
@ -165,23 +167,25 @@ impl FromApub for CommentForm {
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
let content = note
|
||||||
|
.content()
|
||||||
|
.unwrap()
|
||||||
|
.as_single_xsd_string()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
let content_slurs_removed = remove_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_slurs_removed,
|
||||||
.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: note.id_unchecked().unwrap().to_string(),
|
ap_id: check_actor_domain(note, expected_domain)?,
|
||||||
local: false,
|
local: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +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_actor_domain,
|
||||||
create_apub_response,
|
create_apub_response,
|
||||||
create_apub_tombstone_response,
|
create_apub_tombstone_response,
|
||||||
create_tombstone,
|
create_tombstone,
|
||||||
|
@ -322,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()
|
||||||
|
@ -334,6 +341,25 @@ 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 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() {
|
let icon = match group.icon() {
|
||||||
Some(any_image) => Some(
|
Some(any_image) => Some(
|
||||||
|
@ -362,22 +388,9 @@ impl FromApub for CommunityForm {
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
@ -385,7 +398,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: group.inner.id_unchecked().unwrap().to_string(),
|
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),
|
||||||
|
@ -486,12 +499,13 @@ pub async fn do_announce(
|
||||||
|
|
||||||
insert_activity(community.creator_id, announce.clone(), true, pool).await?;
|
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?;
|
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()
|
// this seems to be the "easiest" stable alternative for remove_item()
|
||||||
to.retain(|x| *x != sender.get_shared_inbox_url());
|
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?;
|
send_activity(client, &announce.into_any_base()?, community, to).await?;
|
||||||
|
|
||||||
|
|
|
@ -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, None).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?;
|
||||||
|
@ -358,6 +358,7 @@ async fn fetch_remote_community(
|
||||||
Ok(e) => blocking(pool, move |conn| Post::update(conn, e.id, &post)).await??,
|
Ok(e) => blocking(pool, move |conn| Post::update(conn, e.id, &post)).await??,
|
||||||
Err(_) => blocking(pool, move |conn| Post::create(conn, &post)).await??,
|
Err(_) => blocking(pool, move |conn| Post::create(conn, &post)).await??,
|
||||||
};
|
};
|
||||||
|
// TODO: we need to send a websocket update here
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(community)
|
Ok(community)
|
||||||
|
@ -388,7 +389,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 +427,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??;
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,17 @@ use crate::{
|
||||||
undo::receive_undo,
|
undo::receive_undo,
|
||||||
update::receive_update,
|
update::receive_update,
|
||||||
},
|
},
|
||||||
shared_inbox::receive_unhandled_activity,
|
shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
|
||||||
},
|
},
|
||||||
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 +29,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_id_from_activity(&announce)?;
|
||||||
|
announce.id(community.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();
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -39,6 +40,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 +61,7 @@ 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?;
|
||||||
|
|
||||||
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 +93,7 @@ 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(¬e, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool, Some(user.actor_id()?)).await?;
|
||||||
|
|
||||||
let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
|
let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
|
||||||
|
|
||||||
|
|
|
@ -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(¬e, client, pool)
|
let comment_ap_id = CommentForm::from_apub(¬e, 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;
|
||||||
|
|
||||||
|
|
|
@ -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(¬e, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, 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?
|
||||||
|
|
|
@ -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(¬e, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, 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?
|
||||||
|
|
|
@ -4,9 +4,11 @@ use crate::{
|
||||||
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_id_from_activity,
|
||||||
get_user_from_activity,
|
get_user_from_activity,
|
||||||
receive_unhandled_activity,
|
receive_unhandled_activity,
|
||||||
},
|
},
|
||||||
|
ActorType,
|
||||||
FromApub,
|
FromApub,
|
||||||
GroupExt,
|
GroupExt,
|
||||||
PageExt,
|
PageExt,
|
||||||
|
@ -22,6 +24,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Remove, 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 +43,12 @@ 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_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() {
|
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 +66,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 +120,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(¬e, client, pool)
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool, None)
|
||||||
.await?
|
.await?
|
||||||
.get_ap_id()?;
|
.get_ap_id()?;
|
||||||
|
|
||||||
|
@ -168,7 +177,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;
|
||||||
|
|
||||||
|
|
|
@ -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.domain() != inner_actor_uri.domain() {
|
||||||
|
Err(anyhow!("Cant undo activities from a different instance").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(¬e, client, pool)
|
let comment_ap_id = CommentForm::from_apub(¬e, 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(¬e, client, pool)
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool, None)
|
||||||
.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, None)
|
||||||
.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;
|
||||||
|
|
||||||
|
@ -415,7 +443,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;
|
||||||
|
|
||||||
|
@ -481,7 +509,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(¬e, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, 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?
|
||||||
|
@ -527,7 +555,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?
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -40,6 +41,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 +62,22 @@ 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?;
|
||||||
|
|
||||||
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?
|
.await?
|
||||||
.id;
|
.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
|
// 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 +100,14 @@ 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(¬e, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, 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?
|
.await?
|
||||||
.id;
|
.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 +119,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,
|
||||||
|
|
|
@ -69,14 +69,16 @@ pub async fn community_inbox(
|
||||||
|
|
||||||
verify(&request, &user)?;
|
verify(&request, &user)?;
|
||||||
|
|
||||||
insert_activity(user.id, activity.clone(), false, &db).await?;
|
|
||||||
|
|
||||||
let any_base = activity.clone().into_any_base()?;
|
let any_base = activity.clone().into_any_base()?;
|
||||||
let kind = activity.kind().unwrap();
|
let kind = activity.kind().unwrap();
|
||||||
match kind {
|
let user_id = user.id;
|
||||||
ValidTypes::Follow => handle_follow(any_base, user, community, &client, db).await,
|
let res = match kind {
|
||||||
ValidTypes::Undo => handle_undo_follow(any_base, user, community, db).await,
|
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
|
/// 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_,
|
user: User_,
|
||||||
community: Community,
|
community: Community,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
db: DbPoolParam,
|
db: &DbPoolParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let follow = Follow::from_any_base(activity)?.unwrap();
|
let follow = Follow::from_any_base(activity)?.unwrap();
|
||||||
let community_follower_form = CommunityFollowerForm {
|
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.
|
// 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()
|
CommunityFollower::follow(&conn, &community_follower_form).ok()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
community.send_accept_follow(follow, &client, &db).await?;
|
community.send_accept_follow(follow, &client, db).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
@ -109,7 +111,7 @@ async fn handle_undo_follow(
|
||||||
activity: AnyBase,
|
activity: AnyBase,
|
||||||
user: User_,
|
user: User_,
|
||||||
community: Community,
|
community: Community,
|
||||||
db: DbPoolParam,
|
db: &DbPoolParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let _undo = Undo::from_any_base(activity)?.unwrap();
|
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.
|
// 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()
|
CommunityFollower::unfollow(&conn, &community_follower_form).ok()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -68,20 +68,17 @@ pub async fn shared_inbox(
|
||||||
debug!("Shared inbox received activity: {}", json);
|
debug!("Shared inbox received activity: {}", json);
|
||||||
|
|
||||||
let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap();
|
let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap();
|
||||||
// TODO: pass this actor in instead of using get_user_from_activity()
|
let community = get_community_id_from_activity(&activity)?;
|
||||||
let actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?;
|
|
||||||
|
|
||||||
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)?;
|
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 any_base = activity.clone().into_any_base()?;
|
||||||
let kind = activity.kind().unwrap();
|
let kind = activity.kind().unwrap();
|
||||||
match kind {
|
let res = match kind {
|
||||||
ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await,
|
ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await,
|
||||||
ValidTypes::Create => receive_create(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,
|
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::Remove => receive_remove(any_base, &client, &pool, chat_server).await,
|
||||||
ValidTypes::Delete => receive_delete(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,
|
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>(
|
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
|
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
|
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()
|
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>(
|
pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(
|
||||||
|
|
|
@ -65,11 +65,9 @@ pub async fn user_inbox(
|
||||||
let actor = get_or_fetch_and_upsert_actor(actor_uri, &client, &pool).await?;
|
let actor = get_or_fetch_and_upsert_actor(actor_uri, &client, &pool).await?;
|
||||||
verify(&request, actor.as_ref())?;
|
verify(&request, actor.as_ref())?;
|
||||||
|
|
||||||
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()?;
|
||||||
let kind = activity.kind().unwrap();
|
let kind = activity.kind().unwrap();
|
||||||
match kind {
|
let res = match kind {
|
||||||
ValidTypes::Accept => receive_accept(any_base, username, &client, &pool).await,
|
ValidTypes::Accept => receive_accept(any_base, username, &client, &pool).await,
|
||||||
ValidTypes::Create => {
|
ValidTypes::Create => {
|
||||||
receive_create_private_message(any_base, &client, &pool, chat_server).await
|
receive_create_private_message(any_base, &client, &pool, chat_server).await
|
||||||
|
@ -83,7 +81,10 @@ pub async fn user_inbox(
|
||||||
ValidTypes::Undo => {
|
ValidTypes::Undo => {
|
||||||
receive_undo_delete_private_message(any_base, &client, &pool, chat_server).await
|
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.
|
/// Handle accepted follows.
|
||||||
|
@ -125,7 +126,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(¬e, client, pool).await?;
|
let domain = Some(create.id_unchecked().unwrap().to_owned());
|
||||||
|
let private_message = PrivateMessageForm::from_apub(¬e, 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 +162,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(¬e, client, pool).await?;
|
let domain = Some(update.id_unchecked().unwrap().to_owned());
|
||||||
|
let private_message_form = PrivateMessageForm::from_apub(¬e, 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 +206,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(¬e, client, pool).await?;
|
let domain = Some(delete.id_unchecked().unwrap().to_owned());
|
||||||
|
let private_message_form = PrivateMessageForm::from_apub(¬e, 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 +263,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(¬e, client, pool).await?;
|
let domain = Some(undo.id_unchecked().unwrap().to_owned());
|
||||||
|
let private_message = PrivateMessageForm::from_apub(¬e, 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| {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -1,6 +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_actor_domain,
|
||||||
create_apub_response,
|
create_apub_response,
|
||||||
create_apub_tombstone_response,
|
create_apub_tombstone_response,
|
||||||
create_tombstone,
|
create_tombstone,
|
||||||
|
@ -42,7 +44,7 @@ use lemmy_db::{
|
||||||
user::User_,
|
user::User_,
|
||||||
Crud,
|
Crud,
|
||||||
};
|
};
|
||||||
use lemmy_utils::convert_datetime;
|
use lemmy_utils::{convert_datetime, remove_slurs};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -154,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
|
||||||
|
@ -203,6 +206,14 @@ impl FromApub for PostForm {
|
||||||
None => (None, None, None),
|
None => (None, None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let name = page
|
||||||
|
.inner
|
||||||
|
.summary()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_single_xsd_string()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
let url = page
|
let url = page
|
||||||
.inner
|
.inner
|
||||||
.url()
|
.url()
|
||||||
|
@ -213,17 +224,12 @@ 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)?;
|
||||||
|
let body_slurs_removed = body.map(|b| remove_slurs(&b));
|
||||||
Ok(PostForm {
|
Ok(PostForm {
|
||||||
name: page
|
name,
|
||||||
.inner
|
|
||||||
.summary()
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.as_single_xsd_string()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
url,
|
url,
|
||||||
body,
|
body: body_slurs_removed,
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
removed: None,
|
removed: None,
|
||||||
|
@ -245,7 +251,7 @@ impl FromApub for PostForm {
|
||||||
embed_description,
|
embed_description,
|
||||||
embed_html,
|
embed_html,
|
||||||
thumbnail_url,
|
thumbnail_url,
|
||||||
ap_id: page.inner.id_unchecked().unwrap().to_string(),
|
ap_id: check_actor_domain(page, expected_domain)?,
|
||||||
local: false,
|
local: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
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,
|
||||||
create_tombstone,
|
create_tombstone,
|
||||||
fetcher::get_or_fetch_and_upsert_user,
|
fetcher::get_or_fetch_and_upsert_user,
|
||||||
insert_activity,
|
insert_activity,
|
||||||
|
@ -75,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()
|
||||||
|
@ -84,10 +87,10 @@ impl FromApub for PrivateMessageForm {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let creator = get_or_fetch_and_upsert_user(&creator_actor_id, client, pool).await?;
|
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_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 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 {
|
Ok(PrivateMessageForm {
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
|
@ -102,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: note.id_unchecked().unwrap().to_string(),
|
ap_id: check_actor_domain(note, expected_domain)?,
|
||||||
local: false,
|
local: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +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_actor_domain,
|
||||||
create_apub_response,
|
create_apub_response,
|
||||||
insert_activity,
|
insert_activity,
|
||||||
ActorType,
|
ActorType,
|
||||||
|
@ -211,7 +213,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) => Some(
|
Some(any_image) => Some(
|
||||||
Image::from_any_base(any_image.as_one().unwrap().clone())
|
Image::from_any_base(any_image.as_one().unwrap().clone())
|
||||||
|
@ -238,16 +245,26 @@ impl FromApub for UserForm {
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(UserForm {
|
let name = person
|
||||||
name: person
|
|
||||||
.name()
|
.name()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.one()
|
.one()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_xsd_string()
|
.as_xsd_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string(),
|
.to_string();
|
||||||
preferred_username: person.inner.preferred_username().map(|u| u.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,
|
||||||
|
preferred_username,
|
||||||
password_encrypted: "".to_string(),
|
password_encrypted: "".to_string(),
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
@ -263,11 +280,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: person.id_unchecked().unwrap().to_string(),
|
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),
|
||||||
|
|
59
ui/src/api_tests/comment.spec.ts
vendored
59
ui/src/api_tests/comment.spec.ts
vendored
|
@ -16,6 +16,9 @@ import {
|
||||||
getMentions,
|
getMentions,
|
||||||
searchPost,
|
searchPost,
|
||||||
unfollowRemotes,
|
unfollowRemotes,
|
||||||
|
createCommunity,
|
||||||
|
registerUser,
|
||||||
|
API,
|
||||||
} from './shared';
|
} from './shared';
|
||||||
|
|
||||||
import { PostResponse } from '../interfaces';
|
import { PostResponse } from '../interfaces';
|
||||||
|
@ -102,31 +105,51 @@ test('Delete a comment', async () => {
|
||||||
expect(betaComment2.deleted).toBe(false);
|
expect(betaComment2.deleted).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove a comment', async () => {
|
test('Remove a comment from admin and community on the same instance', async () => {
|
||||||
let commentRes = await createComment(alpha, postRes.post.id);
|
let commentRes = await createComment(alpha, postRes.post.id);
|
||||||
let removeCommentRes = await removeComment(
|
|
||||||
alpha,
|
// Get the id for beta
|
||||||
true,
|
let betaCommentId = (await searchComment(beta, commentRes.comment))
|
||||||
commentRes.comment.id
|
.comments[0].id;
|
||||||
);
|
|
||||||
|
// The beta admin removes it (the community lives on beta)
|
||||||
|
let removeCommentRes = await removeComment(beta, true, betaCommentId);
|
||||||
expect(removeCommentRes.comment.removed).toBe(true);
|
expect(removeCommentRes.comment.removed).toBe(true);
|
||||||
|
|
||||||
// Make sure that comment is removed on beta
|
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
|
||||||
let searchBeta = await searchComment(beta, commentRes.comment);
|
let refetchedPost = await getPost(alpha, postRes.post.id);
|
||||||
let betaComment = searchBeta.comments[0];
|
expect(refetchedPost.comments[0].removed).toBe(true);
|
||||||
expect(betaComment.removed).toBe(true);
|
|
||||||
|
|
||||||
let unremoveCommentRes = await removeComment(
|
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
|
||||||
alpha,
|
|
||||||
false,
|
|
||||||
commentRes.comment.id
|
|
||||||
);
|
|
||||||
expect(unremoveCommentRes.comment.removed).toBe(false);
|
expect(unremoveCommentRes.comment.removed).toBe(false);
|
||||||
|
|
||||||
// Make sure that comment is unremoved on beta
|
// Make sure that comment is unremoved on beta
|
||||||
let searchBeta2 = await searchComment(beta, commentRes.comment);
|
let refetchedPost2 = await getPost(alpha, postRes.post.id);
|
||||||
let betaComment2 = searchBeta2.comments[0];
|
expect(refetchedPost2.comments[0].removed).toBe(false);
|
||||||
expect(betaComment2.removed).toBe(false);
|
});
|
||||||
|
|
||||||
|
test('Remove a comment from admin and community on different instance', async () => {
|
||||||
|
let alphaUser = await registerUser(alpha);
|
||||||
|
let newAlphaApi: API = {
|
||||||
|
url: alpha.url,
|
||||||
|
auth: alphaUser.jwt,
|
||||||
|
};
|
||||||
|
|
||||||
|
// New alpha user creates a community, post, and comment.
|
||||||
|
let newCommunity = await createCommunity(newAlphaApi);
|
||||||
|
let newPost = await createPost(newAlphaApi, newCommunity.community.id);
|
||||||
|
let commentRes = await createComment(newAlphaApi, newPost.post.id);
|
||||||
|
expect(commentRes.comment.content).toBeDefined();
|
||||||
|
|
||||||
|
// Beta searches that to cache it, then removes it
|
||||||
|
let searchBeta = await searchComment(beta, commentRes.comment);
|
||||||
|
let betaComment = searchBeta.comments[0];
|
||||||
|
let removeCommentRes = await removeComment(beta, true, betaComment.id);
|
||||||
|
expect(removeCommentRes.comment.removed).toBe(true);
|
||||||
|
|
||||||
|
// Make sure its not removed on alpha
|
||||||
|
let refetchedPost = await getPost(newAlphaApi, newPost.post.id);
|
||||||
|
expect(refetchedPost.comments[0].removed).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unlike a comment', async () => {
|
test('Unlike a comment', async () => {
|
||||||
|
|
31
ui/src/api_tests/post.spec.ts
vendored
31
ui/src/api_tests/post.spec.ts
vendored
|
@ -154,17 +154,17 @@ test('Delete a post', async () => {
|
||||||
expect(betaPost2.post.deleted).toBe(false);
|
expect(betaPost2.post.deleted).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove a post', async () => {
|
test('Remove a post from admin and community on different instance', async () => {
|
||||||
let search = await searchForBetaCommunity(alpha);
|
let search = await searchForBetaCommunity(alpha);
|
||||||
let postRes = await createPost(alpha, search.communities[0].id);
|
let postRes = await createPost(alpha, search.communities[0].id);
|
||||||
|
|
||||||
let removedPost = await removePost(alpha, true, postRes.post);
|
let removedPost = await removePost(alpha, true, postRes.post);
|
||||||
expect(removedPost.post.removed).toBe(true);
|
expect(removedPost.post.removed).toBe(true);
|
||||||
|
|
||||||
// Make sure lemmy beta sees post is removed
|
// Make sure lemmy beta sees post is NOT removed
|
||||||
let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
|
let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
|
||||||
let betaPost = await getPost(beta, createFakeBetaPostToGetId);
|
let betaPost = await getPost(beta, createFakeBetaPostToGetId);
|
||||||
expect(betaPost.post.removed).toBe(true);
|
expect(betaPost.post.removed).toBe(false);
|
||||||
|
|
||||||
// Undelete
|
// Undelete
|
||||||
let undeletedPost = await removePost(alpha, false, postRes.post);
|
let undeletedPost = await removePost(alpha, false, postRes.post);
|
||||||
|
@ -175,6 +175,31 @@ test('Remove a post', async () => {
|
||||||
expect(betaPost2.post.removed).toBe(false);
|
expect(betaPost2.post.removed).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Remove a post from admin and community on same instance', async () => {
|
||||||
|
let search = await searchForBetaCommunity(alpha);
|
||||||
|
let postRes = await createPost(alpha, search.communities[0].id);
|
||||||
|
|
||||||
|
// Get the id for beta
|
||||||
|
let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
|
||||||
|
let betaPost = await getPost(beta, createFakeBetaPostToGetId);
|
||||||
|
|
||||||
|
// The beta admin removes it (the community lives on beta)
|
||||||
|
let removePostRes = await removePost(beta, true, betaPost.post);
|
||||||
|
expect(removePostRes.post.removed).toBe(true);
|
||||||
|
|
||||||
|
// Make sure lemmy alpha sees post is removed
|
||||||
|
let alphaPost = await getPost(alpha, postRes.post.id);
|
||||||
|
expect(alphaPost.post.removed).toBe(true);
|
||||||
|
|
||||||
|
// Undelete
|
||||||
|
let undeletedPost = await removePost(beta, false, betaPost.post);
|
||||||
|
expect(undeletedPost.post.removed).toBe(false);
|
||||||
|
|
||||||
|
// Make sure lemmy alpha sees post is undeleted
|
||||||
|
let alphaPost2 = await getPost(alpha, postRes.post.id);
|
||||||
|
expect(alphaPost2.post.removed).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('Search for a post', async () => {
|
test('Search for a post', async () => {
|
||||||
let search = await searchForBetaCommunity(alpha);
|
let search = await searchForBetaCommunity(alpha);
|
||||||
let postRes = await createPost(alpha, search.communities[0].id);
|
let postRes = await createPost(alpha, search.communities[0].id);
|
||||||
|
|
Loading…
Reference in a new issue