diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-27 15:23:59 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-27 15:23:59 +0000 |
| commit | 09025b8435f673779ce109e2fb72ce48a13bf28e (patch) | |
| tree | 65f1a988db805b3c58c08cb0cf782f3430aa4df4 /src/git/authorization.rs | |
| parent | 233feae6af4b291e4860a1ddf9df2ccf82e57c2f (diff) | |
fix(auth): accept state announcements from maintainers
Updated get_maintainers_recursive() to properly handle maintainers listed
in accepted repository announcements:
1. Separated 'visited' set (cycle prevention) from 'maintainers' set (result)
2. Maintainers listed in an announcement's 'maintainers' tag are now added
to the maintainer set immediately, even without their own announcement
3. Recursively traverse maintainer chains to handle multi-level delegation
Also fixed RecursiveMaintainerRepoAndState fixture to publish the
maintainer's announcement (which lists the recursive maintainer) before
publishing the recursive maintainer's announcement, establishing the
proper trust chain: Owner -> Maintainer -> RecursiveMaintainer
Test results: 7/7 push authorization tests passing
Diffstat (limited to 'src/git/authorization.rs')
| -rw-r--r-- | src/git/authorization.rs | 53 |
1 files changed, 36 insertions, 17 deletions
diff --git a/src/git/authorization.rs b/src/git/authorization.rs index 2fe81b0..e9f59c7 100644 --- a/src/git/authorization.rs +++ b/src/git/authorization.rs | |||
| @@ -17,7 +17,7 @@ | |||
| 17 | use anyhow::{anyhow, Result}; | 17 | use anyhow::{anyhow, Result}; |
| 18 | use nostr_sdk::{Event, Filter, Kind, PublicKey, SingleLetterTag, Timestamp, ToBech32, Alphabet}; | 18 | use nostr_sdk::{Event, Filter, Kind, PublicKey, SingleLetterTag, Timestamp, ToBech32, Alphabet}; |
| 19 | use std::collections::HashSet; | 19 | use std::collections::HashSet; |
| 20 | use tracing::{debug, warn}; | 20 | use tracing::debug; |
| 21 | 21 | ||
| 22 | use crate::nostr::events::{ | 22 | use crate::nostr::events::{ |
| 23 | RepositoryAnnouncement, RepositoryState, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, | 23 | RepositoryAnnouncement, RepositoryState, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, |
| @@ -128,40 +128,59 @@ impl AuthorizationContext { | |||
| 128 | /// - getMaintainers(alice) -> [alice, bob, charlie] | 128 | /// - getMaintainers(alice) -> [alice, bob, charlie] |
| 129 | /// - getMaintainers(bob) -> [bob, charlie] (bob doesn't have alice's trust) | 129 | /// - getMaintainers(bob) -> [bob, charlie] (bob doesn't have alice's trust) |
| 130 | pub fn get_maintainers(&self, pubkey: &str, identifier: &str) -> Vec<String> { | 130 | pub fn get_maintainers(&self, pubkey: &str, identifier: &str) -> Vec<String> { |
| 131 | let mut checked: HashSet<String> = HashSet::new(); | 131 | let mut visited: HashSet<String> = HashSet::new(); |
| 132 | self.get_maintainers_recursive(pubkey, identifier, &mut checked); | 132 | let mut maintainers: HashSet<String> = HashSet::new(); |
| 133 | self.get_maintainers_recursive(pubkey, identifier, &mut visited, &mut maintainers); | ||
| 133 | 134 | ||
| 134 | checked.into_iter().collect() | 135 | maintainers.into_iter().collect() |
| 135 | } | 136 | } |
| 136 | 137 | ||
| 137 | /// Recursive helper for get_maintainers | 138 | /// Recursive helper for get_maintainers |
| 139 | /// | ||
| 140 | /// The key insight is that a pubkey is a valid maintainer if: | ||
| 141 | /// 1. They have their own accepted announcement for this repo, OR | ||
| 142 | /// 2. They are listed in the "maintainers" tag of an accepted announcement | ||
| 143 | /// | ||
| 144 | /// This allows maintainers to publish state events without needing their own | ||
| 145 | /// announcement - they're authorized by being listed in the owner's announcement. | ||
| 146 | /// | ||
| 147 | /// We use separate sets: | ||
| 148 | /// - `visited`: Tracks which pubkeys we've already processed (cycle prevention) | ||
| 149 | /// - `maintainers`: The result set of valid maintainers | ||
| 138 | fn get_maintainers_recursive( | 150 | fn get_maintainers_recursive( |
| 139 | &self, | 151 | &self, |
| 140 | pubkey: &str, | 152 | pubkey: &str, |
| 141 | identifier: &str, | 153 | identifier: &str, |
| 142 | checked: &mut HashSet<String>, | 154 | visited: &mut HashSet<String>, |
| 155 | maintainers: &mut HashSet<String>, | ||
| 143 | ) { | 156 | ) { |
| 144 | // Skip if already processed | 157 | // Skip if already visited (prevents infinite loops) |
| 145 | if checked.contains(pubkey) { | 158 | if visited.contains(pubkey) { |
| 146 | return; | 159 | return; |
| 147 | } | 160 | } |
| 161 | visited.insert(pubkey.to_string()); | ||
| 148 | 162 | ||
| 149 | // Find the announcement event for this pubkey | 163 | // Find the announcement event for this pubkey |
| 150 | let announcement = self.find_announcement_by_pubkey(pubkey, identifier); | 164 | let announcement = self.find_announcement_by_pubkey(pubkey, identifier); |
| 151 | if announcement.is_none() { | ||
| 152 | return; | ||
| 153 | } | ||
| 154 | 165 | ||
| 155 | // Mark this pubkey as checked (they have a valid announcement) | 166 | if let Some(announcement) = announcement { |
| 156 | checked.insert(pubkey.to_string()); | 167 | // This pubkey has an announcement - they are a valid maintainer |
| 168 | maintainers.insert(pubkey.to_string()); | ||
| 157 | 169 | ||
| 158 | let announcement = announcement.unwrap(); | 170 | // Get maintainers listed in this announcement (maintainers tag) |
| 171 | // These are ALSO valid maintainers, even without their own announcement | ||
| 172 | for maintainer_pubkey in &announcement.maintainers { | ||
| 173 | // Add them to the maintainer set immediately - they're authorized | ||
| 174 | // by being listed in an accepted announcement | ||
| 175 | maintainers.insert(maintainer_pubkey.clone()); | ||
| 159 | 176 | ||
| 160 | // Get maintainers listed in this announcement (p tags) | 177 | // Recursively check if they have their own announcement |
| 161 | for maintainer_pubkey in &announcement.maintainers { | 178 | // to get any maintainers THEY list (recursive maintainer chain) |
| 162 | // Recursively process each listed maintainer | 179 | self.get_maintainers_recursive(maintainer_pubkey, identifier, visited, maintainers); |
| 163 | self.get_maintainers_recursive(maintainer_pubkey, identifier, checked); | 180 | } |
| 164 | } | 181 | } |
| 182 | // If no announcement found, they can still be valid if they were | ||
| 183 | // added to maintainers by their parent caller | ||
| 165 | } | 184 | } |
| 166 | 185 | ||
| 167 | /// Find a repository announcement event by pubkey and identifier | 186 | /// Find a repository announcement event by pubkey and identifier |