use crate::{
+ api::{check_slurs, check_slurs_opt},
apub::{
activities::{generate_activity_id, send_activity},
+ check_actor_domain,
create_apub_response,
create_apub_tombstone_response,
create_tombstone,
base::{AnyBase, BaseExt},
collection::{OrderedCollection, UnorderedCollection},
context,
- object::Tombstone,
+ object::{Image, Tombstone},
prelude::*,
public,
};
type ApubType = GroupExt;
/// Parse an ActivityPub group received from another instance into a Lemmy community.
- async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result<Self, LemmyError> {
+ async fn from_apub(
+ group: &GroupExt,
+ client: &Client,
+ pool: &DbPool,
+ expected_domain: Option<Url>,
+ ) -> Result<Self, LemmyError> {
let creator_and_moderator_uris = group.inner.attributed_to().unwrap();
let creator_uri = creator_and_moderator_uris
.as_many()
.unwrap();
let creator = get_or_fetch_and_upsert_user(creator_uri, client, pool).await?;
+ let name = group
+ .inner
+ .name()
+ .unwrap()
+ .as_one()
+ .unwrap()
+ .as_xsd_string()
+ .unwrap()
+ .to_string();
+ let title = group.inner.preferred_username().unwrap().to_string();
+ // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
+ // -> same for post.content etc
+ let description = group
+ .inner
+ .content()
+ .map(|s| s.as_single_xsd_string().unwrap().into());
+ check_slurs(&name)?;
+ check_slurs(&title)?;
+ check_slurs_opt(&description)?;
+ let icon = match group.icon() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().unwrap().clone())
+ .unwrap()
+ .unwrap()
+ .url()
+ .unwrap()
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
+
+ let banner = match group.image() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().unwrap().clone())
+ .unwrap()
+ .unwrap()
+ .url()
+ .unwrap()
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
+
Ok(CommunityForm {
- name: group
- .inner
- .name()
- .unwrap()
- .as_one()
- .unwrap()
- .as_xsd_string()
- .unwrap()
- .into(),
- title: group.inner.preferred_username().unwrap().to_string(),
- // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
- // -> same for post.content etc
- description: group
- .inner
- .content()
- .map(|s| s.as_single_xsd_string().unwrap().into()),
+ name,
+ title,
+ description,
category_id: group.ext_one.category.identifier.parse::<i32>()?,
creator_id: creator.id,
removed: None,
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
deleted: None,
nsfw: group.ext_one.sensitive,
- actor_id: group.inner.id_unchecked().unwrap().to_string(),
+ actor_id: check_actor_domain(group, expected_domain)?,
local: false,
private_key: None,
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
last_refreshed_at: Some(naive_now()),
+ icon,
+ banner,
})
}
}
insert_activity(community.creator_id, announce.clone(), true, pool).await?;
- // dont send to the instance where the activity originally came from, because that would result
- // in a database error (same data inserted twice)
let mut to = community.get_follower_inboxes(pool).await?;
+ // dont send to the local instance, nor to the instance where the activity originally came from,
+ // because that would result in a database error (same data inserted twice)
// this seems to be the "easiest" stable alternative for remove_item()
to.retain(|x| *x != sender.get_shared_inbox_url());
+ to.retain(|x| *x != community.get_shared_inbox_url());
send_activity(client, &announce.into_any_base()?, community, to).await?;
get_user_from_activity,
receive_unhandled_activity,
},
+ ActorType,
FromApub,
GroupExt,
PageExt,
let user = get_user_from_activity(&delete, client, pool).await?;
let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
- let post_ap_id = PostForm::from_apub(&page, client, pool)
+ let post_ap_id = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?))
.await?
.get_ap_id()?;
let user = get_user_from_activity(&delete, client, pool).await?;
let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool)
+ let comment_ap_id = CommentForm::from_apub(¬e, client, pool, Some(user.actor_id()?))
.await?
.get_ap_id()?;
let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
let user = get_user_from_activity(&delete, client, pool).await?;
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+ let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(user.actor_id()?))
.await?
.actor_id;
private_key: community.private_key,
public_key: community.public_key,
last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
};
let community_id = community.id;
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
+ get_community_id_from_activity,
get_user_from_activity,
receive_unhandled_activity,
},
+ ActorType,
FromApub,
GroupExt,
PageExt,
};
use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
use actix_web::{client::Client, HttpResponse};
+use anyhow::anyhow;
use lemmy_db::{
comment::{Comment, CommentForm},
comment_view::CommentView,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let remove = Remove::from_any_base(activity)?.unwrap();
+ let actor = get_user_from_activity(&remove, client, pool).await?;
+ let community = get_community_id_from_activity(&remove)?;
+ if actor.actor_id()?.domain() != community.domain() {
+ return Err(anyhow!("Remove activities are only allowed on local objects").into());
+ }
+
match remove.object().as_single_kind_str() {
Some("Page") => receive_remove_post(remove, client, pool, chat_server).await,
Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await,
let mod_ = get_user_from_activity(&remove, client, pool).await?;
let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let post_ap_id = PostForm::from_apub(&page, client, pool)
+ let post_ap_id = PostForm::from_apub(&page, client, pool, None)
.await?
.get_ap_id()?;
let mod_ = get_user_from_activity(&remove, client, pool).await?;
let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool)
+ let comment_ap_id = CommentForm::from_apub(¬e, client, pool, None)
.await?
.get_ap_id()?;
let mod_ = get_user_from_activity(&remove, client, pool).await?;
let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+ let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(mod_.actor_id()?))
.await?
.actor_id;
private_key: community.private_key,
public_key: community.public_key,
last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
};
let community_id = community.id;
get_user_from_activity,
receive_unhandled_activity,
},
+ ActorType,
FromApub,
GroupExt,
PageExt,
DbPool,
LemmyError,
};
-use activitystreams::{activity::*, base::AnyBase, object::Note, prelude::*};
+use activitystreams::{
+ activity::*,
+ base::{AnyBase, AsBase},
+ object::Note,
+ prelude::*,
+};
use actix_web::{client::Client, HttpResponse};
use anyhow::anyhow;
use lemmy_db::{
Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await,
Some("Like") => receive_undo_like(undo, client, pool, chat_server).await,
Some("Dislike") => receive_undo_dislike(undo, client, pool, chat_server).await,
- // TODO: handle undo_dislike?
_ => receive_unhandled_activity(undo),
}
}
+fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
+where
+ T: AsBase<A> + ActorAndObjectRef,
+{
+ let outer_actor = outer_activity.actor()?;
+ let outer_actor_uri = outer_actor.as_single_xsd_any_uri().unwrap();
+
+ let inner_actor = inner_activity.actor()?;
+ let inner_actor_uri = inner_actor.as_single_xsd_any_uri().unwrap();
+
+ if outer_actor_uri.domain() != inner_actor_uri.domain() {
+ Err(anyhow!("Cant undo activities from a different instance").into())
+ } else {
+ Ok(())
+ }
+}
+
async fn receive_undo_delete(
undo: Undo,
client: &Client,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let delete = Delete::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+ check_is_undo_valid(&undo, &delete)?;
let type_ = delete.object().as_single_kind_str().unwrap();
match type_ {
"Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let remove = Remove::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+ check_is_undo_valid(&undo, &remove)?;
let type_ = remove.object().as_single_kind_str().unwrap();
match type_ {
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let like = Like::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+ check_is_undo_valid(&undo, &like)?;
let type_ = like.object().as_single_kind_str().unwrap();
match type_ {
_chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
+ check_is_undo_valid(&undo, &dislike)?;
+
+ // TODO: need to implement Undo<Dislike>
let type_ = dislike.object().as_single_kind_str().unwrap();
Err(anyhow!("Undo Delete type {} not supported", type_).into())
let user = get_user_from_activity(delete, client, pool).await?;
let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool)
+ let comment_ap_id = CommentForm::from_apub(¬e, client, pool, Some(user.actor_id()?))
.await?
.get_ap_id()?;
let mod_ = get_user_from_activity(remove, client, pool).await?;
let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool)
+ let comment_ap_id = CommentForm::from_apub(¬e, client, pool, None)
.await?
.get_ap_id()?;
let user = get_user_from_activity(delete, client, pool).await?;
let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
- let post_ap_id = PostForm::from_apub(&page, client, pool)
+ let post_ap_id = PostForm::from_apub(&page, client, pool, Some(user.actor_id()?))
.await?
.get_ap_id()?;
let mod_ = get_user_from_activity(remove, client, pool).await?;
let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let post_ap_id = PostForm::from_apub(&page, client, pool)
+ let post_ap_id = PostForm::from_apub(&page, client, pool, None)
.await?
.get_ap_id()?;
let user = get_user_from_activity(delete, client, pool).await?;
let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap();
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+ let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(user.actor_id()?))
.await?
.actor_id;
private_key: community.private_key,
public_key: community.public_key,
last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
};
let community_id = community.id;
let mod_ = get_user_from_activity(remove, client, pool).await?;
let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap();
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
+ let community_actor_id = CommunityForm::from_apub(&group, client, pool, Some(mod_.actor_id()?))
.await?
.actor_id;
private_key: community.private_key,
public_key: community.public_key,
last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
};
let community_id = community.id;
let user = get_user_from_activity(like, client, pool).await?;
let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap();
- let comment = CommentForm::from_apub(¬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)
.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 post = PostForm::from_apub(&page, client, pool).await?;
+ let post = PostForm::from_apub(&page, client, pool, None).await?;
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, client, pool)
.await?
use crate::{
+ api::{check_slurs, check_slurs_opt},
apub::{
activities::{generate_activity_id, send_activity},
+ check_actor_domain,
create_apub_response,
insert_activity,
ActorType,
person.set_icon(image.into_any_base()?);
}
+ if let Some(banner_url) = &self.banner {
+ let mut image = Image::new();
+ image.set_url(banner_url.to_owned());
+ person.set_image(image.into_any_base()?);
+ }
+
if let Some(bio) = &self.bio {
person.set_summary(bio.to_owned());
}
impl FromApub for UserForm {
type ApubType = PersonExt;
/// Parse an ActivityPub person received from another instance into a Lemmy user.
- async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
+ async fn from_apub(
+ person: &PersonExt,
+ _: &Client,
+ _: &DbPool,
+ expected_domain: Option<Url>,
+ ) -> Result<Self, LemmyError> {
let avatar = match person.icon() {
- Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
- .unwrap()
- .unwrap()
- .url()
- .unwrap()
- .as_single_xsd_any_uri()
- .map(|u| u.to_string()),
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().unwrap().clone())
+ .unwrap()
+ .unwrap()
+ .url()
+ .unwrap()
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
+
+ let banner = match person.image() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().unwrap().clone())
+ .unwrap()
+ .unwrap()
+ .url()
+ .unwrap()
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
None => None,
};
+ let name = person
+ .name()
+ .unwrap()
+ .one()
+ .unwrap()
+ .as_xsd_string()
+ .unwrap()
+ .to_string();
+ let preferred_username = person.inner.preferred_username().map(|u| u.to_string());
+ let bio = person
+ .inner
+ .summary()
+ .map(|s| s.as_single_xsd_string().unwrap().into());
+ check_slurs(&name)?;
+ check_slurs_opt(&preferred_username)?;
+ check_slurs_opt(&bio)?;
+
Ok(UserForm {
- name: person
- .name()
- .unwrap()
- .one()
- .unwrap()
- .as_xsd_string()
- .unwrap()
- .to_string(),
- preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
+ name,
+ preferred_username,
password_encrypted: "".to_string(),
admin: false,
banned: false,
email: None,
avatar,
+ banner,
updated: person.updated().map(|u| u.to_owned().naive_local()),
show_nsfw: false,
theme: "".to_string(),
show_avatars: false,
send_notifications_to_email: false,
matrix_user_id: None,
- actor_id: person.id_unchecked().unwrap().to_string(),
- bio: person
- .inner
- .summary()
- .map(|s| s.as_single_xsd_string().unwrap().into()),
+ actor_id: check_actor_domain(person, expected_domain)?,
+ bio,
local: false,
private_key: None,
public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),