diff options
| author | tv <tv@krebsco.de> | 2026-11-07 01:59:38 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-07 01:59:38 +0100 |
| commit | 09b4f7daac52b5aa6cebaa9c147a9086b105aac9 (patch) | |
| tree | 2e18e044e9e1c6b690fe1cfd1f95517a4fcf503c /src/access/registry.rs | |
| parent | 4479bb89595958721a2dfef57dbcb89b255a7ad8 (diff) | |
access first usersauth
Diffstat (limited to 'src/access/registry.rs')
| -rw-r--r-- | src/access/registry.rs | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/src/access/registry.rs b/src/access/registry.rs index c4ad0e6..e863ce5 100644 --- a/src/access/registry.rs +++ b/src/access/registry.rs @@ -133,6 +133,53 @@ pub(crate) fn capability(by: &str, since: u64, path: &[String], rights: &[Right] }) } +/// The first users of a new swarm, as its genesis node records them (`first_users`): +/// each name, and whether it may do everything (protocol data and managing users +/// included), not only read and write the swarm's data. +pub(crate) type FirstUsers = Vec<(String, bool)>; + +/// The first users of a new swarm, from the genesis node's options: those of +/// `bootstrap_users`, each may do everything; unset, the users of its htpasswd file +/// (`htpasswd`) may read and write the swarm's data, and those of them, or of the proxy's +/// (`--user-header`), in `protocol_writers` everything. Refused if nobody could manage +/// the swarm: a registry nobody may change would lock everyone out for good. +pub(crate) fn first_users( + bootstrap_users: &[String], + htpasswd: &[String], + protocol_writers: &[String], +) -> anyhow::Result<FirstUsers> { + let mut users: BTreeMap<String, bool> = BTreeMap::new(); + if bootstrap_users.is_empty() { + for name in htpasswd.iter().filter(|name| valid_name(name)) { + users.insert(name.clone(), false); + } + for name in protocol_writers.iter().filter(|name| valid_name(name)) { + users.insert(name.clone(), true); + } + } else { + for name in bootstrap_users { + users.insert(name.clone(), true); + } + } + if !users.values().any(|admin| *admin) { + anyhow::bail!( + "genesis: nobody could manage the new swarm's users; name one with bootstrap-users, or put one of htpasswd's users in protocol-writers (docs/access.md)" + ); + } + Ok(users.into_iter().collect()) +} + +/// The capabilities of a first user (`first_users`): the swarm's data, and for one who +/// may do everything protocol data too. +pub(crate) fn first_caps(admin: bool) -> Vec<(Vec<String>, &'static [Right])> { + const BOTH: &[Right] = &[Right::Read, Right::Write]; + let mut caps = vec![(Vec::new(), BOTH)]; + if admin { + caps.push((meta::path(&[]), BOTH)); + } + caps +} + /// A random ID for a capability (8 bytes, hex): concurrent grants don't collide. pub(crate) fn new_id() -> String { let mut bytes = [0u8; 8]; @@ -214,6 +261,46 @@ mod tests { } #[test] + fn the_first_users() { + let names = |list: &[&str]| list.iter().map(|n| (*n).to_owned()).collect::<Vec<_>>(); + assert_eq!( + first_users(&[], &names(&["alice", "bob"]), &names(&["alice"])).unwrap(), + vec![("alice".to_owned(), true), ("bob".to_owned(), false)] + ); + // A protocol writer the proxy names, not in htpasswd. + assert_eq!( + first_users(&[], &names(&["bob"]), &names(&["carol"])).unwrap(), + vec![("bob".to_owned(), false), ("carol".to_owned(), true)] + ); + assert_eq!( + first_users(&names(&["root"]), &names(&["alice"]), &names(&["alice"])).unwrap(), + vec![("root".to_owned(), true)] + ); + assert!(first_users(&[], &names(&["alice"]), &[]).is_err()); + assert!(first_users(&[], &[], &[]).is_err()); + let admin = Caps::new( + first_caps(true) + .into_iter() + .map(|(path, rights)| { + serde_json::from_value(capability("x", 0, &path, rights)).unwrap() + }) + .collect(), + ); + assert!(admin.allows(Right::Write, &path("_continuum/users/x"))); + assert!(admin.allows(Right::Write, &path("accounts"))); + let user = Caps::new( + first_caps(false) + .into_iter() + .map(|(path, rights)| { + serde_json::from_value(capability("x", 0, &path, rights)).unwrap() + }) + .collect(), + ); + assert!(user.allows(Right::Write, &path("accounts"))); + assert!(!user.allows(Right::Read, &path("_continuum"))); + } + + #[test] fn concurrent_grants_both_hold() { let mut ours = Document::new(); let entry = entry("x", 0, &[]); |
