asonix
83b447b780
- Fix image federation - Enable submission & comment create/update federation - Improve checks to ensure we don't federate when we don't need to
919 lines
29 KiB
Rust
919 lines
29 KiB
Rust
use activitystreams::base::AnyBase;
|
|
use actix_rt::Arbiter;
|
|
use actix_web::{client::Client, dev::Payload, HttpRequest};
|
|
use sled::Db;
|
|
use std::{fmt, sync::Arc};
|
|
use url::Url;
|
|
use uuid::Uuid;
|
|
|
|
#[macro_export]
|
|
macro_rules! recover {
|
|
($url:expr, $expr:expr) => {
|
|
match $expr {
|
|
Some(item) => item,
|
|
None => return Ok(Err(RecoverableError::MissingApub($url.to_owned()))),
|
|
}
|
|
};
|
|
}
|
|
|
|
pub mod apub;
|
|
pub mod pictrs;
|
|
pub mod store;
|
|
|
|
use apub::ApubIds;
|
|
use pictrs::ImageInfo;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Context {
|
|
store: store::Store,
|
|
apub: apub::Store,
|
|
pictrs: Arc<dyn pictrs::ImageInfo + Send + Sync>,
|
|
arbiter: Arbiter,
|
|
spawner: Arc<dyn Spawner + Send + Sync>,
|
|
}
|
|
|
|
impl Context {
|
|
// async because it requires execution when an Arbiter exists, avoids panic
|
|
async fn from_state(state: &State) -> Self {
|
|
Context {
|
|
store: state.store.clone(),
|
|
apub: state.apub.clone(),
|
|
pictrs: state.pictrs.info.clone(),
|
|
arbiter: Arbiter::current(),
|
|
spawner: state.spawner.clone(),
|
|
}
|
|
}
|
|
|
|
fn deliver(&self, completed: &dyn Outbound) {
|
|
if completed.ready() {
|
|
let fallible = || -> Result<(), Error> {
|
|
let behalf = completed.behalf(self)?;
|
|
let inboxes = completed.inboxes(self)?;
|
|
let any_base = completed.to_apub(self)?;
|
|
self.spawner.deliver(behalf, any_base, inboxes);
|
|
|
|
Ok(())
|
|
};
|
|
|
|
if let Err(e) = (fallible)() {
|
|
log::error!("Error spawning deliver task: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_local(&self, id: Uuid) -> Result<bool, Error> {
|
|
Ok(self
|
|
.store
|
|
.profiles
|
|
.is_local(id)?
|
|
.req("Profile in is_local")?)
|
|
}
|
|
|
|
fn is_self_server(&self, id: Uuid) -> Result<bool, Error> {
|
|
Ok(self.store.servers.is_self(id)?)
|
|
}
|
|
|
|
fn is_or_is_server_of(&self, key_owner: KeyOwner, actor: &Url) -> Result<bool, Error> {
|
|
let actor_id = self.apub.id_for_apub(actor)?.req("Apub for Actor")?;
|
|
|
|
match key_owner {
|
|
KeyOwner::Server(server_id) => {
|
|
if let Some(actor_profile_id) = actor_id.profile() {
|
|
let profile = self
|
|
.store
|
|
.profiles
|
|
.by_id(actor_profile_id)?
|
|
.req("profile by id")?;
|
|
if let OwnerSource::Remote(actor_server_id) = profile.owner_source() {
|
|
if *actor_server_id == server_id {
|
|
return Ok(true);
|
|
}
|
|
} else {
|
|
let self_id = self.store.servers.get_self()?.req("get self server")?;
|
|
|
|
if self_id == server_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
if let Some(actor_server_id) = actor_id.server() {
|
|
if server_id == actor_server_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
KeyOwner::Profile(profile_id) => {
|
|
if let Some(actor_profile_id) = actor_id.profile() {
|
|
if profile_id == actor_profile_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn check_block(&self, left: Uuid, right: Uuid) -> Result<(), Error> {
|
|
let forward = self.store.view.blocks.by_forward(left, right)?.is_some();
|
|
let backward = self.store.view.blocks.by_forward(right, left)?.is_some();
|
|
if forward || backward {
|
|
return Err(Error::Blocked);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_server_block(&self, left: Uuid, right: Uuid) -> Result<(), Error> {
|
|
let forward = self
|
|
.store
|
|
.view
|
|
.server_blocks
|
|
.by_forward(left, right)?
|
|
.is_some();
|
|
let backward = self
|
|
.store
|
|
.view
|
|
.server_blocks
|
|
.by_forward(right, left)?
|
|
.is_some();
|
|
if forward || backward {
|
|
return Err(Error::Blocked);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn spawn_blocking<F>(&self, f: F)
|
|
where
|
|
F: FnOnce() + Send + 'static,
|
|
{
|
|
self.arbiter.send(Box::pin(async move {
|
|
let _ = actix_web::web::block(move || {
|
|
(f)();
|
|
Ok(()) as Result<(), ()>
|
|
})
|
|
.await;
|
|
}));
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
#[error("Missing required item: {0}")]
|
|
Missing(String),
|
|
|
|
#[error("ActivityPub object is malformed")]
|
|
Invalid,
|
|
|
|
#[error("Panic in blocking task")]
|
|
Panic,
|
|
|
|
#[error("Object was deleted")]
|
|
Deleted,
|
|
|
|
#[error("Blocked")]
|
|
Blocked,
|
|
|
|
#[error("Error deleting file: {0}")]
|
|
DeleteFile(#[from] pictrs::DeleteError),
|
|
|
|
#[error("Error uploading file: {0}")]
|
|
UploadFile(#[from] pictrs::UploadError),
|
|
|
|
#[error("Failed to serialize or deseiralize data: {0}")]
|
|
Json(#[from] serde_json::Error),
|
|
|
|
#[error("Error in DB: {0}")]
|
|
Store(#[from] store::StoreError),
|
|
|
|
#[error("Error in DB: {0}")]
|
|
Apub(#[from] apub::StoreError),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub enum OnBehalfOf {
|
|
Profile(Uuid),
|
|
Server,
|
|
}
|
|
|
|
pub trait Spawner {
|
|
fn download_apub(&self, on_behalf_of: OnBehalfOf, url: Url, stack: Vec<AnyBase>);
|
|
fn download_images(&self, images: Vec<MissingImage>, stack: Vec<AnyBase>);
|
|
fn purge_file(&self, file_id: Uuid);
|
|
fn process(&self, any_base: AnyBase, stack: Vec<AnyBase>);
|
|
fn deliver(&self, on_behalf_of: OnBehalfOf, any_base: AnyBase, inboxes: Vec<Url>);
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub struct MissingImage {
|
|
apub_id: Url,
|
|
image_url: Url,
|
|
}
|
|
|
|
enum RecoverableError {
|
|
MissingApub(Url),
|
|
MissingImages(Vec<MissingImage>),
|
|
}
|
|
|
|
pub trait Action {
|
|
fn perform(&self, context: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error>;
|
|
}
|
|
|
|
pub trait Outbound {
|
|
fn id(&self) -> Option<Uuid>;
|
|
fn behalf(&self, context: &Context) -> Result<OnBehalfOf, Error>;
|
|
fn inboxes(&self, context: &Context) -> Result<Vec<Url>, Error>;
|
|
fn to_apub(&self, context: &Context) -> Result<AnyBase, Error>;
|
|
fn ready(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
trait Required<T> {
|
|
fn req(self, msg: &str) -> Result<T, Error>;
|
|
}
|
|
|
|
impl<T> Required<T> for Option<T> {
|
|
fn req(self, msg: &str) -> Result<T, Error> {
|
|
self.ok_or_else(|| Error::Missing(msg.to_owned()))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum KeyOwner {
|
|
Server(Uuid),
|
|
Profile(Uuid),
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct State {
|
|
pub store: store::Store,
|
|
pub apub: apub::Store,
|
|
pub pictrs: pictrs::State,
|
|
pub spawner: Arc<dyn Spawner + Send + Sync>,
|
|
_db: Db,
|
|
}
|
|
|
|
impl State {
|
|
pub fn build(
|
|
pictrs_upstream: Url,
|
|
image_info: impl ImageInfo + Send + Sync + 'static,
|
|
apub_info: impl ApubIds + Send + Sync + 'static,
|
|
spawner: impl Spawner + Send + Sync + 'static,
|
|
client: Client,
|
|
db: Db,
|
|
) -> Result<Self, sled::Error> {
|
|
Ok(State {
|
|
store: store::Store::build(&db)?,
|
|
apub: apub::Store::build(apub_info, &db)?,
|
|
pictrs: pictrs::State::new(pictrs_upstream, image_info, client),
|
|
spawner: Arc::new(spawner),
|
|
_db: db,
|
|
})
|
|
}
|
|
|
|
pub async fn create_server_actor(&self, domain: String) -> Result<(), Error> {
|
|
let ctx = Context::from_state(self).await;
|
|
|
|
actix_web::web::block(move || {
|
|
if ctx.store.servers.self_exists() {
|
|
return Ok(());
|
|
}
|
|
|
|
let action = apub::actions::CreateServer::from_domain(domain);
|
|
match action.perform(&ctx)? {
|
|
Some(outbound) => {
|
|
let id = outbound.id().expect("Server Actor should exist");
|
|
ctx.store.servers.set_self(id)?;
|
|
let context_clone = ctx.clone();
|
|
ctx.spawn_blocking(move || context_clone.deliver(&*outbound));
|
|
}
|
|
None => panic!("Failed to generate server actor"),
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn run(&self, action: impl Action + Send + 'static) -> Result<Option<Uuid>, Error> {
|
|
let ctx = Context::from_state(self).await;
|
|
|
|
let opt = actix_web::web::block(move || match action.perform(&ctx)? {
|
|
Some(outbound) => {
|
|
let id = outbound.id();
|
|
let context_clone = ctx.clone();
|
|
ctx.spawn_blocking(move || context_clone.deliver(&*outbound));
|
|
Ok(id)
|
|
}
|
|
None => Ok(None),
|
|
})
|
|
.await?;
|
|
|
|
Ok(opt)
|
|
}
|
|
|
|
pub async fn ingest(
|
|
&self,
|
|
any_base: AnyBase,
|
|
key_id: Option<Url>,
|
|
stack: Vec<AnyBase>,
|
|
) -> Result<(), Error> {
|
|
let ctx = Context::from_state(self).await;
|
|
|
|
actix_web::web::block(move || {
|
|
if let Some(key_id) = key_id {
|
|
if let Some(profile) = ctx.apub.profile_for_key(&key_id)? {
|
|
return apub::ingest(any_base, Some(KeyOwner::Profile(profile)), &ctx, stack);
|
|
}
|
|
if let Some(server) = ctx.apub.server_for_key(&key_id)? {
|
|
return apub::ingest(any_base, Some(KeyOwner::Server(server)), &ctx, stack);
|
|
}
|
|
} else {
|
|
return apub::ingest(any_base, None, &ctx, stack);
|
|
}
|
|
|
|
Err(Error::Invalid)
|
|
})
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn serve_object(
|
|
&self,
|
|
object_id: Url,
|
|
viewer_key_id: Option<Url>,
|
|
) -> Result<Option<AnyBase>, Error> {
|
|
let ctx = Context::from_state(self).await;
|
|
|
|
let opt = actix_web::web::block(move || {
|
|
if ctx.apub.deleted(&object_id)? {
|
|
return Ok(None);
|
|
}
|
|
|
|
if let Some(viewer_key_id) = viewer_key_id {
|
|
let profile_id_opt = ctx.apub.profile_for_key(&viewer_key_id)?;
|
|
let server_id_opt = ctx.apub.server_for_key(&viewer_key_id)?;
|
|
|
|
let viewer = match (profile_id_opt, server_id_opt) {
|
|
(Some(profile_id), None) => Viewer::Profile(profile_id),
|
|
(None, Some(server_id)) => Viewer::Server(server_id),
|
|
(_, _) => {
|
|
return Ok(None);
|
|
}
|
|
};
|
|
|
|
let stored_record = if let Some(id) = ctx.apub.id_for_apub(&object_id)? {
|
|
id
|
|
} else {
|
|
return Ok(None);
|
|
};
|
|
|
|
if viewer.can_view(stored_record, &ctx)? {
|
|
let object = ctx.apub.object(&object_id)?;
|
|
if object.is_none() {}
|
|
Ok(object)
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
} else {
|
|
let stored_record = if let Some(id) = ctx.apub.id_for_apub(&object_id)? {
|
|
id
|
|
} else {
|
|
return Ok(None);
|
|
};
|
|
|
|
if is_public(stored_record, &ctx)? {
|
|
let object = ctx.apub.object(&object_id)?;
|
|
if object.is_none() {}
|
|
Ok(object)
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
})
|
|
.await?;
|
|
|
|
Ok(opt)
|
|
}
|
|
|
|
pub async fn download_image(&self, missing_image: &MissingImage) -> Result<Uuid, Error> {
|
|
let images = self.pictrs.download_image(&missing_image.image_url).await?;
|
|
|
|
// safe because we already checked emptiness
|
|
let image = images.images().next().unwrap();
|
|
|
|
let file_source = store::FileSource::PictRs(store::PictRsFile::new(
|
|
image.key(),
|
|
image.token(),
|
|
image.width(),
|
|
image.height(),
|
|
image.mime(),
|
|
));
|
|
let file = self.store.files.create(&file_source)?;
|
|
|
|
use activitystreams::prelude::*;
|
|
let ctx = Context::from_state(self).await;
|
|
ctx.apub.file(&missing_image.apub_id, file.id())?;
|
|
let mut apub_image = activitystreams::object::Image::new();
|
|
apub_image
|
|
.set_id(missing_image.apub_id.clone())
|
|
.set_media_type(image.mime())
|
|
.set_url(missing_image.image_url.clone());
|
|
let apub_image = apub_image.into_any_base()?;
|
|
ctx.apub.store_object(&apub_image)?;
|
|
|
|
Ok(file.id())
|
|
}
|
|
|
|
pub async fn upload_image(&self, req: HttpRequest, body: Payload) -> Result<Vec<Uuid>, Error> {
|
|
let images = self.pictrs.proxy_upload(req, body).await?;
|
|
|
|
let mut files = vec![];
|
|
for image in images.images() {
|
|
let file_source = store::FileSource::PictRs(store::PictRsFile::new(
|
|
image.key(),
|
|
image.token(),
|
|
image.width(),
|
|
image.height(),
|
|
image.mime(),
|
|
));
|
|
let file = self.store.files.create(&file_source)?;
|
|
files.push(file.id());
|
|
}
|
|
|
|
Ok(files)
|
|
}
|
|
|
|
pub async fn delete_file(&self, file_id: Uuid) -> Result<(), Error> {
|
|
let file = self
|
|
.store
|
|
.files
|
|
.by_id(file_id)?
|
|
.req("File in delete_file")?;
|
|
|
|
let store::FileSource::PictRs(image) = file.source();
|
|
|
|
self.pictrs.delete_image(image.key(), image.token()).await?;
|
|
|
|
self.store.files.delete(file_id)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
use apub::StoredRecords;
|
|
use store::OwnerSource;
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum Viewer {
|
|
Server(Uuid),
|
|
Profile(Uuid),
|
|
}
|
|
|
|
fn item_server_for_report(report: &store::Report, ctx: &Context) -> Result<Uuid, Error> {
|
|
let profile_id = match report.kind() {
|
|
store::ReportKind::Profile => report.item(),
|
|
store::ReportKind::Submission => {
|
|
let submission = ctx
|
|
.store
|
|
.submissions
|
|
.by_id(report.item())?
|
|
.req("report item: Submission")?;
|
|
submission.profile_id()
|
|
}
|
|
store::ReportKind::Comment => {
|
|
let comment = ctx
|
|
.store
|
|
.comments
|
|
.by_id(report.item())?
|
|
.req("report item: Comment")?;
|
|
comment.profile_id()
|
|
}
|
|
store::ReportKind::Post => {
|
|
unimplemented!()
|
|
}
|
|
};
|
|
|
|
let profile = ctx.store.profiles.by_id(profile_id)?.req("profile by id")?;
|
|
match profile.owner_source() {
|
|
OwnerSource::Local(_) => ctx.store.servers.get_self()?.req("self server"),
|
|
OwnerSource::Remote(server_id) => Ok(*server_id),
|
|
}
|
|
}
|
|
|
|
fn is_public(record: StoredRecords, ctx: &Context) -> Result<bool, Error> {
|
|
match record {
|
|
StoredRecords::Server(server_id) => {
|
|
let self_id = ctx.store.servers.get_self()?.req("get self server")?;
|
|
if self_id == server_id {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
|
|
impl Viewer {
|
|
fn can_view(&self, record: StoredRecords, ctx: &Context) -> Result<bool, Error> {
|
|
if let Viewer::Server(server_id) = self {
|
|
if ctx.store.is_blocked(*server_id)? {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
let is_federated = self.is_federated(ctx)?;
|
|
|
|
let self_id = ctx.store.servers.get_self()?.req("get self server")?;
|
|
|
|
match (self, record) {
|
|
(Viewer::Server(_), StoredRecords::Server(server_id)) if self_id == server_id => {
|
|
Ok(true)
|
|
}
|
|
(Viewer::Profile(_), StoredRecords::Server(server_id))
|
|
if is_federated || self_id == server_id =>
|
|
{
|
|
Ok(true)
|
|
}
|
|
(Viewer::Server(viewer_id), StoredRecords::Report(report_id)) => {
|
|
let report = ctx.store.reports.by_id(report_id)?;
|
|
let server_id = item_server_for_report(&report, ctx)?;
|
|
|
|
if *viewer_id == server_id {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
(Viewer::Server(_), StoredRecords::Profile(_)) if is_federated => Ok(true),
|
|
(Viewer::Profile(viewer_id), StoredRecords::Profile(profile_id)) if is_federated => {
|
|
let is_blocked = ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, profile_id)?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(profile_id, *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_blocked {
|
|
Ok(false)
|
|
} else {
|
|
Ok(true)
|
|
}
|
|
}
|
|
(Viewer::Server(_), StoredRecords::Submission(_)) if is_federated => Ok(true),
|
|
(Viewer::Profile(viewer_id), StoredRecords::Submission(submission_id))
|
|
if is_federated =>
|
|
{
|
|
let submission = ctx
|
|
.store
|
|
.submissions
|
|
.by_id(submission_id)?
|
|
.req("submission by id")?;
|
|
let is_blocked = ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, submission.profile_id())?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_blocked {
|
|
return Ok(false);
|
|
}
|
|
|
|
if submission.is_followers_only() {
|
|
let is_following = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_following {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
} else {
|
|
Ok(true)
|
|
}
|
|
}
|
|
(Viewer::Server(_), StoredRecords::Comment(_)) if is_federated => Ok(true),
|
|
(Viewer::Profile(viewer_id), StoredRecords::Comment(comment_id)) if is_federated => {
|
|
let comment = ctx.store.comments.by_id(comment_id)?.req("comment by id")?;
|
|
let submission = ctx
|
|
.store
|
|
.submissions
|
|
.by_id(comment.submission_id())?
|
|
.req("submission by id")?;
|
|
let is_blocked = ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, comment.profile_id())?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(comment.profile_id(), *viewer_id)?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, submission.profile_id())?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_blocked {
|
|
return Ok(false);
|
|
}
|
|
|
|
if submission.is_followers_only() {
|
|
let is_following = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_following {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
} else {
|
|
Ok(true)
|
|
}
|
|
}
|
|
(Viewer::Server(_), StoredRecords::React(_)) if is_federated => Ok(true),
|
|
(Viewer::Profile(viewer_id), StoredRecords::React(react_id)) if is_federated => {
|
|
let react = ctx.store.reacts.by_id(react_id)?.req("react by id")?;
|
|
let submission = ctx
|
|
.store
|
|
.submissions
|
|
.by_id(react.submission_id())?
|
|
.req("submission by id")?;
|
|
let is_blocked = ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, react.profile_id())?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(react.profile_id(), *viewer_id)?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(*viewer_id, submission.profile_id())?
|
|
.is_some()
|
|
|| ctx
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_blocked {
|
|
return Ok(false);
|
|
}
|
|
|
|
if submission.is_followers_only() {
|
|
let is_following = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.by_forward(submission.profile_id(), *viewer_id)?
|
|
.is_some();
|
|
|
|
if is_following {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
} else {
|
|
Ok(true)
|
|
}
|
|
}
|
|
(Viewer::Server(_), StoredRecords::Block(_)) if is_federated => Ok(true),
|
|
(Viewer::Server(viewer_id), StoredRecords::Follow(follow_id)) if is_federated => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.left(follow_id)?
|
|
.req("left for follow id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.right(follow_id)?
|
|
.req("right for follow id")?;
|
|
|
|
let left = ctx.store.profiles.by_id(left)?.req("profile by id")?;
|
|
let right = ctx.store.profiles.by_id(right)?.req("profile by id")?;
|
|
|
|
if let OwnerSource::Remote(server_id) = left.owner_source() {
|
|
if server_id == viewer_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
if let OwnerSource::Remote(server_id) = right.owner_source() {
|
|
if server_id == viewer_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
(Viewer::Profile(viewer_id), StoredRecords::Follow(follow_id)) if is_federated => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.left(follow_id)?
|
|
.req("left for follow id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.follows
|
|
.right(follow_id)?
|
|
.req("right for follow id")?;
|
|
|
|
if *viewer_id == left || *viewer_id == right {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
(Viewer::Server(viewer_id), StoredRecords::FollowRequest(freq_id)) if is_federated => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.follow_requests
|
|
.left(freq_id)?
|
|
.req("left for follow id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.follow_requests
|
|
.right(freq_id)?
|
|
.req("right for follow id")?;
|
|
|
|
let left = ctx.store.profiles.by_id(left)?.req("profile by id")?;
|
|
let right = ctx.store.profiles.by_id(right)?.req("profile by id")?;
|
|
|
|
if let OwnerSource::Remote(server_id) = left.owner_source() {
|
|
if server_id == viewer_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
if let OwnerSource::Remote(server_id) = right.owner_source() {
|
|
if server_id == viewer_id {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
(Viewer::Profile(viewer_id), StoredRecords::FollowRequest(freq_id)) if is_federated => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.follow_requests
|
|
.left(freq_id)?
|
|
.req("left for follow id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.follow_requests
|
|
.right(freq_id)?
|
|
.req("right for follow id")?;
|
|
|
|
if *viewer_id == left || *viewer_id == right {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
(Viewer::Server(viewer_id), StoredRecords::ServerFollow(follow_id)) if is_federated => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.server_follows
|
|
.left(follow_id)?
|
|
.req("left for server follow id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.server_follows
|
|
.right(follow_id)?
|
|
.req("right for server follow id")?;
|
|
|
|
if *viewer_id == left || *viewer_id == right {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
(Viewer::Server(viewer_id), StoredRecords::ServerFollowRequest(freq_id)) => {
|
|
let left = ctx
|
|
.store
|
|
.view
|
|
.server_follow_requests
|
|
.left(freq_id)?
|
|
.req("left for server follow request id")?;
|
|
let right = ctx
|
|
.store
|
|
.view
|
|
.server_follow_requests
|
|
.right(freq_id)?
|
|
.req("right for server follow request id")?;
|
|
|
|
if *viewer_id == left || *viewer_id == right {
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
(viewer, object) => {
|
|
if is_federated {
|
|
log::debug!(
|
|
"federated viewer {:?} denied fetch for resource {:?}",
|
|
viewer,
|
|
object
|
|
);
|
|
} else {
|
|
log::debug!("viewer {:?} denied fetch for resource {:?}", viewer, object);
|
|
}
|
|
Ok(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_federated(&self, ctx: &Context) -> Result<bool, Error> {
|
|
match self {
|
|
Viewer::Server(id) => Ok(ctx.store.is_federated(*id)?),
|
|
Viewer::Profile(id) => {
|
|
let profile = ctx.store.profiles.by_id(*id)?.req("profile by id")?;
|
|
if let OwnerSource::Remote(server_id) = profile.owner_source() {
|
|
Ok(ctx.store.is_federated(*server_id)?)
|
|
} else {
|
|
Ok(true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for State {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("State")
|
|
.field("store", &self.store)
|
|
.field("apub", &self.apub)
|
|
.field("pictrs", &self.pictrs)
|
|
.field("spawner", &"Arc<dyn Spawner>")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl From<activitystreams::error::DomainError> for Error {
|
|
fn from(_: activitystreams::error::DomainError) -> Self {
|
|
Self::Invalid
|
|
}
|
|
}
|
|
|
|
impl From<actix_web::error::BlockingError<Error>> for Error {
|
|
fn from(e: actix_web::error::BlockingError<Error>) -> Self {
|
|
match e {
|
|
actix_web::error::BlockingError::Error(e) => e,
|
|
_ => Error::Panic,
|
|
}
|
|
}
|
|
}
|