hyaenidae/profiles/src/apub/actions/apub/mod.rs
asonix 83b447b780 Profiles: Federation fixes
- Fix image federation
- Enable submission & comment create/update federation
- Improve checks to ensure we don't federate when we don't need to
2021-01-17 15:01:45 -06:00

385 lines
15 KiB
Rust

use crate::{Action, Context, Error, KeyOwner, OnBehalfOf, RecoverableError, Required};
use activitystreams::{base::AnyBase, prelude::*};
use url::Url;
mod application;
mod flag;
mod like;
mod note;
mod person;
use application::{
application, create_application, delete_application,
follow::{
accept_follow_application, follow_application, reject_follow_application,
undo_accept_follow_application, undo_follow_application,
},
update_application,
};
use flag::flag;
use like::{like, undo_like};
use note::{
announce_delete_note, announce_note, announce_update_note, create_note, delete_note, note,
update_note,
};
use person::{
block::{block_person, undo_block_person},
create_person, delete_person,
follow::{
accept_follow_person, follow_person, reject_follow_person, undo_accept_follow_person,
undo_follow_person,
},
person, update_person,
};
fn log_unsupported(path: &[&str]) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
let activity = path.join(" ");
log::warn!("Attempted to ingest unsupported activity: {}", activity);
Err(Error::Invalid)
}
pub(crate) fn ingest(
any_base: AnyBase,
key_owner: Option<KeyOwner>,
ctx: &Context,
mut stack: Vec<AnyBase>,
) -> Result<(), Error> {
log::debug!("Ingest");
if any_base.kind_str().is_none() {
log::debug!("Spawining download for URL");
ctx.spawner.download_apub(
OnBehalfOf::Server,
any_base.id().req("id from anybase")?.to_owned(),
stack,
);
return Ok(());
}
match parse(any_base.clone(), key_owner, ctx)? {
Ok(action) => {
if let Some(outbound) = action.perform(ctx)? {
ctx.deliver(&*outbound);
}
if let Some(any_base) = stack.pop() {
ctx.spawner.process(any_base, stack);
}
}
Err(RecoverableError::MissingApub(url)) => {
log::debug!("{} needed before further processing", url);
stack.push(any_base);
ctx.spawner.download_apub(OnBehalfOf::Server, url, stack);
}
Err(RecoverableError::MissingImages(urls)) => {
stack.push(any_base);
ctx.spawner.download_images(urls, stack);
}
}
Ok(())
}
fn get_obj<T, Kind>(
any_base: AnyBase,
ctx: &Context,
f: impl FnOnce(T, &Context, AnyBase) -> Result<Result<Box<dyn Action>, RecoverableError>, Error>,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error>
where
T: ActorAndObjectRefExt + ExtendsExt<Kind>,
<T as activitystreams::base::Extends<Kind>>::Error: From<serde_json::Error>,
Error: From<<T as activitystreams::base::Extends<Kind>>::Error>,
for<'de> Kind: serde::Deserialize<'de>,
Kind: serde::Serialize,
{
wrap(any_base, ctx, |activity: T, ctx| {
let object = activity.object().as_one().req("object from activity")?;
let any_base = if object.kind_str().is_none() {
let id = object.id().req("id from object")?;
if ctx.apub.deleted(id)? {
log::warn!("Attempting to ingest deleted activity");
return Err(Error::Invalid);
}
match ctx.apub.object(id)? {
Some(any_base) => any_base,
None => return Ok(Err(RecoverableError::MissingApub(id.to_owned()))),
}
} else {
object.clone()
};
(f)(activity, ctx, any_base.clone())
})
}
fn wrap<T, Kind>(
any_base: AnyBase,
ctx: &Context,
f: impl FnOnce(T, &Context) -> Result<Result<Box<dyn Action>, RecoverableError>, Error>,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error>
where
T: ExtendsExt<Kind>,
<T as activitystreams::base::Extends<Kind>>::Error: From<serde_json::Error>,
Error: From<<T as activitystreams::base::Extends<Kind>>::Error>,
for<'de> Kind: serde::Deserialize<'de>,
Kind: serde::Serialize,
{
let activity: T = any_base.clone().extend()?.req("activity from any_base")?;
let res = (f)(activity, ctx);
if res.as_ref().map(|r| r.is_ok()).unwrap_or(false) {
ctx.apub.store_object(&any_base)?;
}
res
}
fn parse(
any_base: AnyBase,
key_owner: Option<KeyOwner>,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
match any_base.kind_str().req("kind")? {
"Accept" => get_obj(any_base, ctx, |accept, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Follow" => get_obj(any_base, ctx, |follow, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
accept_follow_application(
&accept,
&follow,
&application,
key_owner,
ctx,
)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
accept_follow_person(&accept, &follow, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Accept", "Follow", kind]),
}
}),
kind => log_unsupported(&["Accept", kind]),
}
}),
"Announce" => get_obj(any_base, ctx, |announce, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Note" => wrap(any_base, ctx, |note, ctx| {
announce_note(&announce, &note, key_owner, ctx)
}),
"Update" => get_obj(any_base, ctx, |update, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Note" => wrap(any_base, ctx, |note, ctx| {
announce_update_note(&announce, &update, &note, key_owner, ctx)
}),
kind => log_unsupported(&["Announce", "Update", kind]),
}
}),
"Delete" => get_obj(any_base, ctx, |delete, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Note" => wrap(any_base, ctx, |note, ctx| {
announce_delete_note(&announce, &delete, &note, key_owner, ctx)
}),
kind => log_unsupported(&["Announce", "Delete", kind]),
}
}),
kind => log_unsupported(&["Announce", kind]),
}
}),
"Application" => wrap(any_base, ctx, |application_obj, ctx| {
application(&application_obj, key_owner, ctx)
}),
"Block" => get_obj(any_base, ctx, |block, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Person" => wrap(any_base, ctx, |person, ctx| {
block_person(&block, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Block", kind]),
}
}),
"Create" => get_obj(any_base, ctx, |create, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
create_application(&create, &application, key_owner, ctx)
}),
"Note" => wrap(any_base, ctx, |note, ctx| {
create_note(&create, &note, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
create_person(&create, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Create", kind]),
}
}),
"Delete" => get_obj(any_base, ctx, |delete, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
delete_application(&delete, &application, key_owner, ctx)
}),
"Note" => wrap(any_base, ctx, |note, ctx| {
delete_note(&delete, &note, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
delete_person(&delete, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Delete", kind]),
}
}),
"Flag" => wrap(any_base, ctx, |flag_obj, ctx| {
flag(&flag_obj, key_owner, ctx)
}),
"Follow" => get_obj(any_base, ctx, |follow, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
follow_application(&follow, &application, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
follow_person(&follow, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Follow", kind]),
}
}),
"Like" => wrap(any_base, ctx, |like_obj, ctx| {
like(&like_obj, key_owner, ctx)
}),
"Note" => wrap(any_base, ctx, |note_obj, ctx| {
note(&note_obj, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person_obj, ctx| {
person(&person_obj, key_owner, ctx)
}),
"Reject" => get_obj(any_base, ctx, |reject, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Follow" => get_obj(any_base, ctx, |follow, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
reject_follow_application(
&reject,
&follow,
&application,
key_owner,
ctx,
)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
reject_follow_person(&reject, &follow, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Reject", "Follow", kind]),
}
}),
kind => log_unsupported(&["Reject", kind]),
}
}),
"Undo" => get_obj(any_base, ctx, |undo, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Accept" => get_obj(any_base, ctx, |accept, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Follow" => get_obj(any_base, ctx, |follow, ctx, any_base| match any_base
.kind_str()
.req("kind")?
{
"Application" => wrap(any_base, ctx, |application, ctx| {
undo_accept_follow_application(
&undo,
&accept,
&follow,
&application,
key_owner,
ctx,
)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
undo_accept_follow_person(
&undo, &accept, &follow, &person, key_owner, ctx,
)
}),
kind => log_unsupported(&["Undo", "Accept", "Follow", kind]),
}),
kind => log_unsupported(&["Undo", "Accept", kind]),
}
}),
"Block" => get_obj(any_base, ctx, |block, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Person" => wrap(any_base, ctx, |person, ctx| {
undo_block_person(&undo, &block, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Undo", "Block", kind]),
}
}),
"Follow" => get_obj(any_base, ctx, |follow, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
undo_follow_application(&undo, &follow, &application, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
undo_follow_person(&undo, &follow, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Undo", "Follow", kind]),
}
}),
"Like" => wrap(any_base, ctx, |like, ctx| {
undo_like(&undo, &like, key_owner, ctx)
}),
kind => log_unsupported(&["Undo", kind]),
}
}),
"Update" => get_obj(any_base, ctx, |update, ctx, any_base| {
match any_base.kind_str().req("kind")? {
"Application" => wrap(any_base, ctx, |application, ctx| {
update_application(&update, &application, key_owner, ctx)
}),
"Note" => wrap(any_base, ctx, |note, ctx| {
update_note(&update, &note, key_owner, ctx)
}),
"Person" => wrap(any_base, ctx, |person, ctx| {
update_person(&update, &person, key_owner, ctx)
}),
kind => log_unsupported(&["Update", kind]),
}
}),
kind => log_unsupported(&[kind]),
}
}
fn require_federation(
key_owner: Option<KeyOwner>,
actor: &Url,
ctx: &Context,
) -> Result<(), Error> {
if let Some(key_owner) = key_owner {
if !ctx.is_or_is_server_of(key_owner, actor)? {
log::debug!("Key Owner does not control Actor");
return Err(Error::Invalid);
}
}
let domain = actor.domain().req("domain from actor")?;
if !ctx.store.is_domain_federated(domain)? {
log::debug!("Domain is not federated");
return Err(Error::Invalid);
}
Ok(())
}
fn require_not_blocked(
key_owner: Option<KeyOwner>,
actor: &Url,
ctx: &Context,
) -> Result<(), Error> {
if let Some(key_owner) = key_owner {
if !ctx.is_or_is_server_of(key_owner, actor)? {
log::debug!("Key Owner does not control Actor");
return Err(Error::Invalid);
}
}
let domain = actor.domain().req("domain from actor")?;
if ctx.store.is_domain_blocked(domain)? {
log::debug!("Domain is blocked");
return Err(Error::Blocked);
}
Ok(())
}