asonix
29bdf064e9
Clear profile data on suspend Clear comment body on delete Update Unfollow and Unblock operations to only delete apub IDs if present
351 lines
11 KiB
Rust
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 })))
|
|
}
|