asonix
29bdf064e9
Clear profile data on suspend Clear comment body on delete Update Unfollow and Unblock operations to only delete apub IDs if present
200 lines
5.8 KiB
Rust
200 lines
5.8 KiB
Rust
use crate::{
|
|
apub::actions::{CreateProfile, CreateProfileApub, DeleteProfile, UpdateProfile},
|
|
apub::ExtendedPerson,
|
|
recover,
|
|
store::OwnerSource,
|
|
Action, Context, Error, RecoverableError, Required,
|
|
};
|
|
use activitystreams::{base::AnyBase, object::Image, prelude::*, primitives::OneOrMany, public};
|
|
use url::Url;
|
|
use uuid::Uuid;
|
|
|
|
fn from_image(
|
|
image: Option<&OneOrMany<AnyBase>>,
|
|
missing_files: &mut Vec<Url>,
|
|
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_opt = Image::from_any_base(image.clone())?;
|
|
let url_opt =
|
|
image_opt.and_then(|i| i.url().and_then(|u| u.as_single_id()).map(|u| u.to_owned()));
|
|
|
|
let url = if let Some(url) = url_opt {
|
|
url
|
|
} else {
|
|
return Ok(None);
|
|
};
|
|
|
|
match ctx.apub.id_for_apub(&url)? {
|
|
Some(uuid) => Ok(Some(uuid.file().req()?)),
|
|
None => {
|
|
missing_files.push(url);
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn person(
|
|
person: &ExtendedPerson,
|
|
ctx: &Context,
|
|
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
|
|
let id = person.id_unchecked().req()?;
|
|
|
|
// Double create
|
|
if ctx.apub.object(id)?.is_some() {
|
|
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 handle = person.preferred_username().req()?.to_owned();
|
|
let domain = id.domain().req()?.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()?.into();
|
|
|
|
let login_required = person
|
|
.to()
|
|
.map(|to| {
|
|
to.iter()
|
|
.find(|entry| entry.id() == Some(&public()))
|
|
.is_some()
|
|
})
|
|
.unwrap_or(true);
|
|
|
|
Ok(Ok(Box::new(CreateProfile {
|
|
apub: Some(CreateProfileApub {
|
|
person_apub_id: id.to_owned(),
|
|
public_key_id,
|
|
public_key,
|
|
}),
|
|
owner_source: OwnerSource::Remote(id.to_string()),
|
|
handle,
|
|
domain,
|
|
display_name,
|
|
description,
|
|
login_required,
|
|
icon,
|
|
banner,
|
|
published,
|
|
})))
|
|
}
|
|
|
|
pub(crate) fn create_person(
|
|
_create: &activitystreams::activity::Create,
|
|
person_obj: &ExtendedPerson,
|
|
ctx: &Context,
|
|
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
|
|
// TODO: Validate Server Actor
|
|
person(person_obj, ctx)
|
|
}
|
|
|
|
pub(crate) fn update_person(
|
|
update: &activitystreams::activity::Update,
|
|
person: &ExtendedPerson,
|
|
ctx: &Context,
|
|
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
|
|
let update_actor = update.actor()?.as_single_id().req()?;
|
|
let id = person.id_unchecked().req()?;
|
|
|
|
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()?;
|
|
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 profile_id = recover!(id, ctx.apub.id_for_apub(id)?);
|
|
let profile_id = profile_id.profile().req()?;
|
|
|
|
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 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 login_required = person
|
|
.to()
|
|
.map(|to| {
|
|
to.iter()
|
|
.find(|entry| entry.id() == Some(&public()))
|
|
.is_some()
|
|
})
|
|
.unwrap_or(true);
|
|
|
|
Ok(Ok(Box::new(UpdateProfile {
|
|
profile_id,
|
|
display_name,
|
|
description,
|
|
login_required: Some(login_required),
|
|
icon,
|
|
banner,
|
|
public_key_id: Some(public_key_id),
|
|
public_key: Some(public_key),
|
|
})))
|
|
}
|
|
|
|
pub(crate) fn delete_person(
|
|
delete: &activitystreams::activity::Delete,
|
|
person: &ExtendedPerson,
|
|
ctx: &Context,
|
|
) -> Result<Result<Box<dyn Action>, RecoverableError>, Error> {
|
|
let delete_actor = delete.actor()?.as_single_id().req()?;
|
|
let id = person.id_unchecked().req()?;
|
|
|
|
// 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()?;
|
|
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 })))
|
|
}
|