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>, missing_files: &mut Vec, ctx: &Context, ) -> Result, 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, ctx: &Context, ) -> Result, 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, ctx: &Context, ) -> Result, 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, ctx: &Context, ) -> Result, 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, ctx: &Context, ) -> Result, 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 }))) }