292 lines
8.5 KiB
Rust
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 })))
|
|
}
|