hyaenidae/profiles/src/apub/actions/apub/note.rs
asonix 29bdf064e9 Profiles: Update profile delete to profile suspend
Clear profile data on suspend
Clear comment body on delete
Update Unfollow and Unblock operations to only delete apub IDs if present
2021-01-14 20:41:53 -06:00

351 lines
11 KiB
Rust

use crate::{
apub::actions::{
AnnounceComment, AnnounceSubmission, CreateComment, CreateSubmission, DeleteComment,
DeleteSubmission, UpdateComment, UpdateSubmission,
},
recover,
store::Visibility,
Action, Context, Error, RecoverableError, Required,
};
use activitystreams::{object::Image, prelude::*, public};
fn find_visibility(note: &activitystreams::object::Note) -> Visibility {
if note
.to()
.and_then(|one_or_many| {
one_or_many
.iter()
.find(|any_base| (*any_base).id() == Some(&public()))
})
.is_some()
{
return Visibility::Public;
}
if note
.cc()
.and_then(|one_or_many| {
one_or_many
.iter()
.find(|any_base| (*any_base).id() == Some(&public()))
})
.is_some()
{
return Visibility::Unlisted;
}
Visibility::Followers
}
pub(crate) fn note(
note: &activitystreams::object::Note,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let note_id = note.id_unchecked().req()?;
// Double create
if ctx.apub.object(note_id)?.is_some() {
return Err(Error::Invalid);
}
let published = note.published().req()?;
let attributed_to = note.attributed_to().req()?.as_single_id().req()?;
let profile_id = recover!(attributed_to, ctx.apub.id_for_apub(attributed_to)?);
let profile_id = profile_id.profile().req()?;
if let Some(actor_profile) = ctx.store.profiles.by_id(profile_id)? {
if actor_profile.is_suspended() {
return Err(Error::Invalid);
}
} else {
return Err(Error::Invalid);
}
if let Some(in_reply_to) = note.in_reply_to() {
let in_reply_to = in_reply_to.as_single_id().req()?;
let in_reply_to = recover!(in_reply_to, ctx.apub.id_for_apub(in_reply_to)?);
let body = note.content().req()?.as_single_xsd_string().req()?;
if let Some(submission_id) = in_reply_to.submission() {
return Ok(Ok(Box::new(CreateComment {
note_apub_id: Some(note_id.to_owned()),
submission_id,
profile_id,
comment_id: None,
body: body.to_owned(),
published: published.into(),
})));
}
if let Some(comment_id) = in_reply_to.comment() {
let submission_id = ctx.store.comments.by_id(comment_id)?.req()?.submission_id();
return Ok(Ok(Box::new(CreateComment {
note_apub_id: Some(note_id.to_owned()),
submission_id,
profile_id,
comment_id: Some(comment_id),
body: body.to_owned(),
published: published.into(),
})));
}
return Err(Error::Invalid);
}
let title = note.summary().req()?.as_single_xsd_string().req()?;
let description = note
.content()
.and_then(|c| c.as_single_xsd_string())
.map(|s| s.to_owned());
let mut missing_files = vec![];
let mut existing_files = vec![];
for attachment in note.attachment().req()?.iter() {
let image = if let Ok(Some(image)) = attachment.clone().extend::<Image, _>() {
image
} else {
continue;
};
let url = if let Some(url) = image.url().and_then(|u| u.as_single_id()) {
url
} else {
continue;
};
match ctx.apub.id_for_apub(url) {
Ok(Some(id)) => {
existing_files.push(id.file().req()?);
}
Ok(None) => {
missing_files.push(url.to_owned());
}
_ => (),
}
}
if !missing_files.is_empty() {
return Ok(Err(RecoverableError::MissingImages(missing_files)));
}
return Ok(Ok(Box::new(CreateSubmission {
note_apub_id: Some(note_id.to_owned()),
profile_id,
title: title.to_owned(),
description,
published: Some(published.into()),
files: existing_files,
visibility: find_visibility(note),
})));
}
pub(crate) fn create_note(
create: &activitystreams::activity::Create,
note_obj: &activitystreams::object::Note,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let actor_id = create.actor()?.as_single_id().req()?;
let attributed_to = note_obj.attributed_to().req()?.as_single_id().req()?;
if attributed_to != actor_id {
return Err(Error::Invalid);
}
note(note_obj, ctx)
}
pub(crate) fn announce_note(
announce: &activitystreams::activity::Announce,
note: &activitystreams::object::Note,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let actor_id = announce.actor()?.as_single_id().req()?;
let actor_id = recover!(actor_id, ctx.apub.id_for_apub(actor_id)?);
let actor_id = actor_id.profile().req()?;
if let Some(actor_profile) = ctx.store.profiles.by_id(actor_id)? {
if actor_profile.is_suspended() {
return Err(Error::Invalid);
}
} else {
return Err(Error::Invalid);
}
let note_id = note.id_unchecked().req()?;
// enforce that Announce doesn't create a note
if ctx.apub.object(note_id)?.is_none() {
return Ok(Err(RecoverableError::MissingApub(note_id.to_owned())));
}
if let Some(in_reply_to) = note.in_reply_to().and_then(|irt| irt.as_single_id()) {
let comment_id = recover!(in_reply_to, ctx.apub.id_for_apub(in_reply_to)?);
let comment_id = comment_id.comment().req()?;
let submission_id = ctx.store.comments.by_id(comment_id)?.req()?.submission_id();
let submission = ctx.store.submissions.by_id(submission_id)?.req()?;
// only authors of submissions are allowed to announce comments
if submission.profile_id() != actor_id {
return Err(Error::Invalid);
}
return Ok(Ok(Box::new(AnnounceComment)));
}
return Ok(Ok(Box::new(AnnounceSubmission)));
}
pub(crate) fn update_note(
update: &activitystreams::activity::Update,
note: &activitystreams::object::Note,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let update_id = update.id_unchecked().req()?;
let actor_id = update.actor()?.as_single_id().req()?;
let attributed_to = note.attributed_to().req()?.as_single_id().req()?;
if actor_id != attributed_to {
return Err(Error::Invalid);
}
let note_id = note.id_unchecked().req()?;
let note_id = recover!(note_id, ctx.apub.id_for_apub(note_id)?);
let profile_id = recover!(attributed_to, ctx.apub.id_for_apub(attributed_to)?);
let profile_id = profile_id.profile().req()?;
if let Some(actor_profile) = ctx.store.profiles.by_id(profile_id)? {
if actor_profile.is_suspended() {
return Err(Error::Invalid);
}
} else {
return Err(Error::Invalid);
}
if let Some(comment_id) = note_id.comment() {
let comment = ctx.store.comments.by_id(comment_id)?.req()?;
if comment.profile_id() != profile_id {
return Err(Error::Invalid);
}
let body = note
.content()
.and_then(|c| c.as_single_xsd_string())
.map(|s| s.to_owned());
return Ok(Ok(Box::new(UpdateComment {
update_apub_id: Some(update_id.to_owned()),
comment_id,
body,
})));
}
let submission_id = note_id.submission().req()?;
let submission = ctx.store.submissions.by_id(submission_id)?.req()?;
if submission.profile_id() != profile_id {
return Err(Error::Invalid);
}
let title = note
.summary()
.and_then(|s| s.as_single_xsd_string())
.map(|s| s.to_owned());
let description = note
.content()
.and_then(|c| c.as_single_xsd_string())
.map(|s| s.to_owned());
let published = note.published().req()?;
let mut missing_files = vec![];
let mut existing_files = vec![];
for attachment in note.attachment().req()?.iter() {
let image = if let Ok(Some(image)) = attachment.clone().extend::<Image, _>() {
image
} else {
continue;
};
let url = if let Some(url) = image.url().and_then(|u| u.as_single_id()) {
url
} else {
continue;
};
match ctx.apub.id_for_apub(url) {
Ok(Some(id)) => {
existing_files.push(id.file().req()?);
}
Ok(None) => {
missing_files.push(url.to_owned());
}
_ => (),
}
}
if !missing_files.is_empty() {
return Ok(Err(RecoverableError::MissingImages(missing_files)));
}
let mut removed_files = vec![];
for file in submission.files() {
if !existing_files.contains(file) {
removed_files.push(*file);
}
}
Ok(Ok(Box::new(UpdateSubmission {
submission_id,
title,
description,
published: Some(published.into()),
removed_files: Some(removed_files),
new_files: Some(existing_files),
})))
}
pub(crate) fn delete_note(
delete: &activitystreams::activity::Delete,
note: &activitystreams::object::Note,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let actor_id = delete.actor()?.as_single_id().req()?;
let attributed_to = note.attributed_to().req()?.as_single_id().req()?;
if actor_id != attributed_to {
return Err(Error::Invalid);
}
let note_object_id = note.id_unchecked().req()?;
let note_id = recover!(note_object_id, ctx.apub.id_for_apub(note_object_id)?);
let profile_id = recover!(attributed_to, ctx.apub.id_for_apub(attributed_to)?);
let profile_id = profile_id.profile().req()?;
if let Some(actor_profile) = ctx.store.profiles.by_id(profile_id)? {
if actor_profile.is_suspended() {
return Err(Error::Invalid);
}
} else {
return Err(Error::Invalid);
}
if let Some(comment_id) = note_id.comment() {
let comment = ctx.store.comments.by_id(comment_id)?.req()?;
if comment.profile_id() != profile_id {
return Err(Error::Invalid);
}
return Ok(Ok(Box::new(DeleteComment { comment_id })));
}
let submission_id = note_id.submission().req()?;
let submission = ctx.store.submissions.by_id(submission_id)?.req()?;
if submission.profile_id() != profile_id {
return Err(Error::Invalid);
}
Ok(Ok(Box::new(DeleteSubmission { submission_id })))
}