Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

fix: Ensure write access when syncing a sphere. Fixes #389 #551

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion rust/noosphere-core/src/context/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ mod tests {
authority::{generate_capability, generate_ed25519_key, Access, SphereAbility},
context::{
HasMutableSphereContext, HasSphereContext, SphereContentWrite, SpherePetnameWrite,
SphereSync, SyncError,
},
data::{ContentType, LinkRecord, LINK_RECORD_FACT_NAME},
helpers::{make_valid_link_record, simulated_sphere_context},
Expand All @@ -268,7 +269,6 @@ mod tests {
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);

Expand Down Expand Up @@ -438,4 +438,18 @@ mod tests {
.is_err());
Ok(())
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_requires_write_access_to_sync() -> Result<()> {
initialize_tracing(None);

let (mut sphere_context, _) = simulated_sphere_context(Access::ReadOnly, None).await?;

assert!(matches!(
sphere_context.sync().await,
Err(SyncError::InsufficientPermission)
));
Ok(())
}
}
3 changes: 3 additions & 0 deletions rust/noosphere-core/src/context/sync/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
52 changes: 47 additions & 5 deletions rust/noosphere-core/src/context/sync/write.rs
Original file line number Diff line number Diff line change
@@ -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))]
Expand Down Expand Up @@ -52,6 +55,17 @@ where
) -> Result<Link<MemoIpld>, 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 {
Expand Down Expand Up @@ -94,3 +108,31 @@ where
Ok(version)
}
}

/// Given a `HasSphereContext<S>`, 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<C, S>(context: &C) -> bool
where
C: HasSphereContext<S>,
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(_))
}