diff --git a/knot2/crates/knot-acl/src/lib.rs b/knot2/crates/knot-acl/src/lib.rs
index 95b4dc954..178afdbc4 100644
--- a/knot2/crates/knot-acl/src/lib.rs
+++ b/knot2/crates/knot-acl/src/lib.rs
@@ -707,7 +707,10 @@ mod tests {
mod integration {
use super::*;
use knot_cob::{CobHome, CobStore, KnotAuthored};
- use knot_cobs::{CollaboratorsChange, Grant, MembersChange, Registration, RegistryChange};
+ use knot_cobs::{
+ BlocklistChange, CollaboratorsChange, Grant, MembersChange, Registration,
+ RegistryChange,
+ };
use knot_git::{Layout, Repo};
use knot_runtime::{K256Signer, SeededEntropy};
use knot_types::{KnotId, RepoName, RepoRkey, UnixSeconds};
@@ -858,6 +861,102 @@ mod tests {
);
}
+ #[tokio::test]
+ async fn a_private_repo_reads_as_missing_to_everyone_but_its_unblocked_members() {
+ let (_dir, meta_path, layout, signer) = world();
+ let at = UnixSeconds::new;
+
+ let meta = Repo::open(&meta_path).unwrap();
+ let store = CobStore::new(&meta);
+ let squid = repo("squid");
+ let limpet = repo("limpet");
+ let registry = store
+ .create(
+ &knot_home(),
+ &RegistryChange::Register(Registration {
+ visibility: knot_types::Visibility::Private,
+ ..registration("nel", "anemone", &squid, 1)
+ }),
+ &signer,
+ at(1),
+ )
+ .unwrap();
+ store
+ .update(
+ &knot_home(),
+ registry.object,
+ &RegistryChange::Register(registration("nel", "barnacle", &limpet, 1)),
+ &signer,
+ at(1),
+ )
+ .unwrap();
+ let git = layout.create(&squid).unwrap();
+ let collaborators = CobStore::new(&git);
+ let roster = collaborators
+ .create(
+ &CobHome::from(&squid),
+ &CollaboratorsChange::Add(grant("lyna", 1)),
+ &signer,
+ at(1),
+ )
+ .unwrap();
+ collaborators
+ .update(
+ &CobHome::from(&squid),
+ roster.object,
+ &CollaboratorsChange::Add(grant("teq", 1)),
+ &signer,
+ at(1),
+ )
+ .unwrap();
+ seed(
+ &store,
+ &knot_home(),
+ &BlocklistChange::Add(grant("teq", 1)),
+ &signer,
+ at(1),
+ );
+
+ let index = Index::new(&meta_path, layout.clone());
+ index.rebuild().unwrap();
+ index.ensure_collaborators(&squid).unwrap();
+ let reads = |repo: &RepoDid, viewer: Viewer| {
+ read_grant(&index, repo.clone(), &viewer).map(|grant| grant.is_some())
+ };
+
+ assert_eq!(reads(&squid, Viewer::of(acc("nel"))), Resolved::Ready(true));
+ assert_eq!(
+ reads(&squid, Viewer::of(acc("lyna"))),
+ Resolved::Ready(true)
+ );
+ for stranger in [Viewer::anonymous(), Viewer::of(acc("olaren"))] {
+ assert_eq!(
+ reads(&squid, stranger),
+ reads(&repo("nobody"), Viewer::anonymous()),
+ "a stranger can't tell a private repo from a missing one"
+ );
+ }
+ assert_eq!(
+ reads(&squid, Viewer::of(acc("teq"))),
+ Resolved::Ready(false),
+ "a blocked collaborator loses read too"
+ );
+ assert_eq!(
+ reads(&limpet, Viewer::anonymous()),
+ Resolved::Ready(true),
+ "public repos stay open to anyone"
+ );
+
+ let cold = Index::new(&meta_path, layout);
+ cold.rebuild().unwrap();
+ assert_eq!(
+ read_grant(&cold, squid.clone(), &Viewer::of(acc("lyna")))
+ .map(|grant| grant.is_some()),
+ Resolved::Ready(false),
+ "collaborators not loaded yet read as a stranger, never as warming"
+ );
+ }
+
#[tokio::test]
async fn a_repo_re_registered_under_a_second_owner_grants_push_only_to_the_later_owner() {
let (_dir, meta_path, layout, signer) = world();
diff --git a/knot2/crates/knot-acl/src/repos.rs b/knot2/crates/knot-acl/src/repos.rs
index 46f3ff776..cdf87f129 100644
--- a/knot2/crates/knot-acl/src/repos.rs
+++ b/knot2/crates/knot-acl/src/repos.rs
@@ -4,6 +4,7 @@ use knot_git::{GitError, Layout, Repo};
use knot_index::{Index, Resolved, SocialIndex, SocialView};
use knot_types::{
AccountDid, ClonePath, ContributionPermission, KnotId, ObjectFormat, OwnerDid, RepoDid,
+ Visibility,
};
use crate::{Acl, Viewer, can_push};
@@ -120,10 +121,26 @@ impl Social {
}
}
-pub fn read_grant(index: &Index, repo: RepoDid, _viewer: &Viewer) -> Resolved