diff options
Diffstat (limited to 'src/git')
| -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 |