hyaenidae/profiles/src/apub/actions/apub/person/mod.rs

292 lines
8.5 KiB
Rust

use crate::{
apub::{
actions::{
apub::require_federation, CreateProfile, CreateProfileApub, DeleteProfile,
UpdateProfile, UpdateProfileApub,
},
Endpoints, ExtendedPerson,
},
recover,
store::OwnerSource,
Action, Context, Error, KeyOwner, MissingImage, RecoverableError, Required,
};
use activitystreams::{base::AnyBase, object::Image, prelude::*, primitives::OneOrMany, public};
use uuid::Uuid;
pub mod block;
pub mod follow;
fn from_image(
image: Option<&OneOrMany<AnyBase>>,
missing_files: &mut Vec<MissingImage>,
ctx: &Context,
) -> Result<Option<Uuid>, Error> {
let image = if let Some(image) = image.and_then(|i| i.as_one()) {
image
} else {
return Ok(None);
};
let image = if let Some(image) = Image::from_any_base(image.clone())? {
image
} else {
return Ok(None);
};
let id = image.id_unchecked().req("Image ID")?.to_owned();
let url = image
.url()
.req("Image Url")?
.as_single_id()
.req("Single Image Url")?
.to_owned();
match ctx.apub.id_for_apub(&id)? {
Some(stored_id) => Ok(Some(stored_id.file().req("image url as file")?)),
None => {
missing_files.push(MissingImage {
apub_id: id,
image_url: url,
});
Ok(None)
}
}
}
pub(crate) fn person(
person: &ExtendedPerson,
key_owner: Option<KeyOwner>,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
log::debug!("Person");
let id = person.id_unchecked().req("person id")?;
require_federation(key_owner, id, ctx)?;
// Double create
let profile_id = ctx.apub.id_for_apub(id)?;
let mut missing_files = Vec::new();
let banner = from_image(person.image(), &mut missing_files, ctx)?;
let icon = from_image(person.icon(), &mut missing_files, ctx)?;
if !missing_files.is_empty() {
return Ok(Err(RecoverableError::MissingImages(missing_files)));
}
let inbox = person.inbox()?.clone();
let outbox = person.outbox()?.map(|o| o.to_owned());
let following = person.following()?.map(|f| f.to_owned());
let followers = person.followers()?.map(|f| f.to_owned());
let shared_inbox = person
.endpoints()?
.and_then(|e| e.shared_inbox.map(|s| s.to_owned()));
let handle = person
.preferred_username()
.req("preferred username")?
.to_owned();
let domain = id.domain().req("domain for id")?.to_owned();
let display_name = person
.name()
.and_then(|n| n.one())
.and_then(|s| s.as_xsd_string())
.map(|s| s.to_owned());
let description = person
.summary()
.and_then(|s| s.as_single_xsd_string())
.map(|s| s.to_owned());
let public_key = person.ext_one.public_key.public_key_pem.clone();
let public_key_id = person.ext_one.public_key.id.clone();
let published = person.published().req("published")?.into();
let updated = person.updated().map(|u| u.into());
let login_required = person
.to()
.map(|to| {
to.iter()
.find(|entry| entry.id() == Some(&public()))
.is_some()
})
.unwrap_or(true);
let server_id = ctx
.store
.servers
.by_domain(&domain)?
.req("server by domain")?;
let endpoints = Endpoints {
inbox,
outbox,
following,
followers,
shared_inbox,
public_key: public_key_id,
};
if let Some(profile_id) = profile_id.and_then(|pid| pid.profile()) {
Ok(Ok(Box::new(UpdateProfile {
apub: Some(UpdateProfileApub {
public_key,
endpoints,
}),
profile_id,
display_name,
display_name_source: None,
description,
description_source: None,
login_required: Some(login_required),
icon,
banner,
updated,
})))
} else {
Ok(Ok(Box::new(CreateProfile {
apub: Some(CreateProfileApub {
person_apub_id: id.to_owned(),
public_key,
endpoints,
}),
owner_source: OwnerSource::Remote(server_id),
handle,
domain,
display_name,
description,
login_required,
icon,
banner,
published,
updated,
})))
}
}
pub(crate) fn create_person(
create: &activitystreams::activity::Create,
person_obj: &ExtendedPerson,
key_owner: Option<KeyOwner>,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
log::debug!("Create Person");
let create_actor = create.actor()?.as_single_id().req("create actor id")?;
require_federation(key_owner, create_actor, ctx)?;
person(person_obj, None, ctx)
}
pub(crate) fn update_person(
update: &activitystreams::activity::Update,
person: &ExtendedPerson,
key_owner: Option<KeyOwner>,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
log::debug!("Update Person");
let update_actor = update.actor()?.as_single_id().req("update actor id")?;
require_federation(key_owner, update_actor, ctx)?;
let id = person.id_unchecked().req("person id")?;
if update_actor != id {
return Err(Error::Invalid);
}
let profile_id = recover!(id, ctx.apub.id_for_apub(id)?);
let profile_id = profile_id.profile().req("person id as profile id")?;
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);
}
let mut missing_files = Vec::new();
let banner = from_image(person.image(), &mut missing_files, ctx)?;
let icon = from_image(person.icon(), &mut missing_files, ctx)?;
if !missing_files.is_empty() {
return Ok(Err(RecoverableError::MissingImages(missing_files)));
}
let inbox = person.inbox()?.clone();
let outbox = person.outbox()?.map(|o| o.to_owned());
let following = person.following()?.map(|f| f.to_owned());
let followers = person.followers()?.map(|f| f.to_owned());
let shared_inbox = person
.endpoints()?
.and_then(|e| e.shared_inbox.map(|s| s.to_owned()));
let display_name = person
.name()
.and_then(|n| n.one())
.and_then(|s| s.as_xsd_string())
.map(|s| s.to_owned());
let description = person
.summary()
.and_then(|s| s.as_single_xsd_string())
.map(|s| s.to_owned());
let public_key_id = person.ext_one.public_key.id.clone();
let public_key = person.ext_one.public_key.public_key_pem.clone();
let updated = person.updated().map(|u| u.into());
let login_required = person
.to()
.map(|to| {
to.iter()
.find(|entry| entry.id() == Some(&public()))
.is_some()
})
.unwrap_or(true);
let endpoints = Endpoints {
inbox,
outbox,
following,
followers,
shared_inbox,
public_key: public_key_id,
};
Ok(Ok(Box::new(UpdateProfile {
apub: Some(UpdateProfileApub {
public_key,
endpoints,
}),
profile_id,
display_name,
display_name_source: None,
description,
description_source: None,
login_required: Some(login_required),
icon,
banner,
updated,
})))
}
pub(crate) fn delete_person(
delete: &activitystreams::activity::Delete,
person: &ExtendedPerson,
key_owner: Option<KeyOwner>,
ctx: &Context,
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
log::debug!("Delete Person");
let delete_actor = delete.actor()?.as_single_id().req("delete actor id")?;
require_federation(key_owner, delete_actor, ctx)?;
let id = person.id_unchecked().req("person id")?;
// TODO: Validate Server Actor
if delete_actor != id {
return Err(Error::Invalid);
}
let profile_id = recover!(id, ctx.apub.id_for_apub(id)?);
let profile_id = profile_id.profile().req("person id as profile id")?;
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);
}
Ok(Ok(Box::new(DeleteProfile { profile_id })))
}