diff --git a/rust/noosphere-core/src/context/context.rs b/rust/noosphere-core/src/context/context.rs index c8f62e1f6..d649d4075 100644 --- a/rust/noosphere-core/src/context/context.rs +++ b/rust/noosphere-core/src/context/context.rs @@ -262,13 +262,13 @@ mod tests { helpers::{make_valid_link_record, simulated_sphere_context}, tracing::initialize_tracing, view::Sphere, + SphereSync, SyncError, }; use noosphere_storage::{MemoryStorage, SphereDb}; use ucan::{builder::UcanBuilder, crypto::KeyMaterial, store::UcanJwtStore}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test; - #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -436,6 +436,18 @@ mod tests { .set_petname_record("foo", records.get(1).unwrap()) .await .is_err()); + } + + async fn it_requires_write_access_to_sync() -> Result<()> { + initialize_tracing(None); + + let (mut sphere_context, _) = + simulated_sphere_context(SimulationAccess::Readonly, None).await?; + + assert!(matches!( + sphere_context.sync(crate::SyncRecovery::None).await, + Err(SyncError::InsufficientPermission) + )); Ok(()) } } diff --git a/rust/noosphere-core/src/context/sync/error.rs b/rust/noosphere-core/src/context/sync/error.rs index 5295155cc..0b20e518b 100644 --- a/rust/noosphere-core/src/context/sync/error.rs +++ b/rust/noosphere-core/src/context/sync/error.rs @@ -5,6 +5,9 @@ use thiserror::Error; /// gateway #[derive(Error, Debug)] pub enum SyncError { + /// The error was due to not having write access to the sphere + #[error("Insufficient permission to sync")] + InsufficientPermission, /// The error was a conflict; this is possibly recoverable #[error("There was a conflict during sync")] Conflict, diff --git a/rust/noosphere-core/src/context/sync/write.rs b/rust/noosphere-core/src/context/sync/write.rs index 1d075a3bd..9c040cf37 100644 --- a/rust/noosphere-core/src/context/sync/write.rs +++ b/rust/noosphere-core/src/context/sync/write.rs @@ -1,12 +1,15 @@ -use crate::data::{Link, MemoIpld}; +use crate::{ + authority::Authorization, + context::{ + internal::SphereContextInternal, GatewaySyncStrategy, HasMutableSphereContext, + HasSphereContext, SyncError, SyncExtent, SyncRecovery, + }, + data::{Link, MemoIpld}, +}; use anyhow::Result; use async_trait::async_trait; use noosphere_storage::Storage; -use crate::context::{HasMutableSphereContext, SyncError, SyncExtent, SyncRecovery}; - -use crate::context::GatewaySyncStrategy; - /// Implementors of [SphereSync] are able to sychronize with a Noosphere gateway #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -52,6 +55,17 @@ where ) -> Result, SyncError> { debug!("Attempting to sync..."); + // Check that the author has write access to sync. + // If a sphere was joined from another sphere, do not check, + // but allow sync to proceed, as the local sphere does not have + // local proof until after initial sync. If truly no write access is + // available, the gateway will reject this sync. + if !is_sphere_joined(self).await { + self.assert_write_access() + .await + .map_err(|_| SyncError::InsufficientPermission)?; + } + let sync_strategy = GatewaySyncStrategy::default(); let version = match recovery { @@ -94,3 +108,31 @@ where Ok(version) } } + +/// Given a `HasSphereContext`, return a boolean indicating +/// whether or not this sphere has been joined from another sphere +/// (e.g. possibly lacking local authorization until syncing with a gateway). +async fn is_sphere_joined(context: &C) -> bool +where + C: HasSphereContext, + S: Storage + 'static, +{ + let context = { + let context = context.sphere_context().await; + if context.is_err() { + return false; + } + context.unwrap() + }; + + let author = context.author(); + + let auth = { + let auth = author.require_authorization(); + if auth.is_err() { + return false; + } + auth.unwrap() + }; + matches!(auth, Authorization::Cid(_)) +}