asonix
642f0bb578
Currently federation requests + accepts + rejects + undos all work Federation blocking works also Profiles can federate their text and images, provided federation was enabled before profile creation. Looking into backfilling existing profiles is TODO
888 lines
28 KiB
Rust
888 lines
28 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: &Url, actor: &Url) -> Result<bool, Error> {
|
|
if key_owner == actor {
|
|
return Ok(true);
|
|
}
|
|
|
|
if let Some(server_id) = self.apub.id_for_apub(key_owner)?.and_then(|id| id.server()) {
|
|
if let Some(profile_id) = self.apub.id_for_apub(actor)?.and_then(|id| id.profile()) {
|
|
let profile = self
|
|
.store
|
|
.profiles
|
|
.by_id(profile_id)?
|
|
.req("profile by id")?;
|
|
if let OwnerSource::Remote(remote_id) = profile.owner_source() {
|
|
if *remote_id == server_id {
|
|
return Ok(true);
|
|
}
|
|
} else {
|
|
let local_server = self.store.servers.get_self()?.req("self server id")?;
|
|
|
|
if server_id == local_server {
|
|
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)]
|
|
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_owner: Option<Url>,
|
|
stack: Vec<AnyBase>,
|
|
) -> Result<(), Error> {
|
|
let ctx = Context::from_state(self).await;
|
|
|
|
actix_web::web::block(move || apub::ingest(any_base, key_owner.as_ref(), &ctx, stack))
|
|
.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,
|
|
}
|
|
}
|
|
}
|