read_gate.rs (15233B)
1 use crate::{ 2 GroupAuthority, GroupError, GroupEventClass, GroupId, GroupLimitsConfig, GroupProjection, 3 KIND_GROUP_ADMINS, KIND_GROUP_DELETE_GROUP, KIND_GROUP_METADATA, MemberStatus, 4 classify_group_event, event_view::GroupEventView, non_enumerating_group_error, 5 }; 6 use tangle_protocol::{EventId, PublicKeyHex}; 7 8 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 pub enum GroupReadDecision { 10 Visible, 11 Hidden, 12 } 13 14 #[derive(Debug, Clone, Copy)] 15 pub struct GroupReadGate<'a> { 16 projection: &'a GroupProjection, 17 authority: &'a GroupAuthority, 18 } 19 20 impl<'a> GroupReadGate<'a> { 21 pub fn new(projection: &'a GroupProjection, authority: &'a GroupAuthority) -> Self { 22 Self { 23 projection, 24 authority, 25 } 26 } 27 28 pub fn screen_event( 29 &self, 30 event: &(impl GroupEventView + ?Sized), 31 reader: Option<&PublicKeyHex>, 32 limits: GroupLimitsConfig, 33 ) -> Result<GroupReadDecision, GroupError> { 34 let event_id = event.id()?; 35 if self.projection.event_deletion(&event_id).is_some() { 36 return Ok(GroupReadDecision::Hidden); 37 } 38 match classify_group_event(event, limits)? { 39 GroupEventClass::NonGroup => Ok(GroupReadDecision::Visible), 40 GroupEventClass::Normal { group_id } => self.screen_normal_event(&group_id, reader), 41 GroupEventClass::Moderation { group_id, .. } => { 42 if event.kind_u32() == KIND_GROUP_DELETE_GROUP { 43 self.screen_delete_group_marker(&event_id, &group_id, reader) 44 } else { 45 self.screen_normal_event(&group_id, reader) 46 } 47 } 48 GroupEventClass::RelayGeneratedSnapshot { kind, group_id } => { 49 self.screen_snapshot_event(kind.as_u32(), &group_id, reader) 50 } 51 } 52 } 53 54 pub fn require_visible( 55 &self, 56 event: &(impl GroupEventView + ?Sized), 57 reader: Option<&PublicKeyHex>, 58 limits: GroupLimitsConfig, 59 ) -> Result<(), GroupError> { 60 match self.screen_event(event, reader, limits)? { 61 GroupReadDecision::Visible => Ok(()), 62 GroupReadDecision::Hidden => Err(non_enumerating_group_error()), 63 } 64 } 65 66 fn screen_snapshot_event( 67 &self, 68 kind: u32, 69 group_id: &GroupId, 70 reader: Option<&PublicKeyHex>, 71 ) -> Result<GroupReadDecision, GroupError> { 72 let Some(group) = self.projection.group(group_id) else { 73 return Ok(GroupReadDecision::Hidden); 74 }; 75 if self.projection.tombstone(group_id).is_some() { 76 return Ok(GroupReadDecision::Hidden); 77 } 78 if group.metadata().hidden() && !self.can_read_group(group_id, reader) { 79 return Ok(GroupReadDecision::Hidden); 80 } 81 if group.metadata().private() 82 && !is_public_when_private_snapshot(kind) 83 && !self.can_read_group(group_id, reader) 84 { 85 return Ok(GroupReadDecision::Hidden); 86 } 87 Ok(GroupReadDecision::Visible) 88 } 89 90 fn screen_delete_group_marker( 91 &self, 92 event_id: &EventId, 93 group_id: &GroupId, 94 reader: Option<&PublicKeyHex>, 95 ) -> Result<GroupReadDecision, GroupError> { 96 let Some(group) = self.projection.group(group_id) else { 97 return Ok(GroupReadDecision::Hidden); 98 }; 99 if self 100 .projection 101 .tombstone(group_id) 102 .is_none_or(|tombstone| tombstone.delete_event_id() != event_id) 103 { 104 return self.screen_normal_event(group_id, reader); 105 } 106 if (group.metadata().hidden() || group.metadata().private()) 107 && !self.can_read_group(group_id, reader) 108 { 109 return Ok(GroupReadDecision::Hidden); 110 } 111 Ok(GroupReadDecision::Visible) 112 } 113 114 fn screen_normal_event( 115 &self, 116 group_id: &GroupId, 117 reader: Option<&PublicKeyHex>, 118 ) -> Result<GroupReadDecision, GroupError> { 119 let Some(group) = self.projection.group(group_id) else { 120 return Ok(GroupReadDecision::Hidden); 121 }; 122 if self.projection.tombstone(group_id).is_some() { 123 return Ok(GroupReadDecision::Hidden); 124 } 125 if (group.metadata().hidden() || group.metadata().private()) 126 && !self.can_read_group(group_id, reader) 127 { 128 return Ok(GroupReadDecision::Hidden); 129 } 130 Ok(GroupReadDecision::Visible) 131 } 132 133 fn can_read_group(&self, group_id: &GroupId, reader: Option<&PublicKeyHex>) -> bool { 134 let Some(reader) = reader else { 135 return false; 136 }; 137 if self.authority.is_admin(reader) { 138 return true; 139 } 140 self.projection 141 .member(group_id, reader) 142 .is_some_and(|member| member.status() == MemberStatus::Member) 143 } 144 } 145 146 fn is_public_when_private_snapshot(kind: u32) -> bool { 147 matches!(kind, KIND_GROUP_METADATA | KIND_GROUP_ADMINS) 148 } 149 150 #[cfg(test)] 151 mod tests { 152 use super::{GroupReadDecision, GroupReadGate}; 153 use crate::{ 154 GroupAuthority, GroupEventDeletion, GroupId, GroupMetadata, GroupMetadataFlags, 155 GroupMetadataText, GroupProjection, GroupState, GroupTombstone, KIND_GROUP_ADMINS, 156 KIND_GROUP_DELETE_GROUP, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, MemberState, 157 MemberStatus, ProjectionOrderTuple, StoreOffset, SupportedKinds, 158 }; 159 use pocket_types::Event as PocketEvent; 160 use tangle_protocol::{ 161 Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, 162 event_to_value, 163 }; 164 165 #[test] 166 fn read_gate_allows_public_group_events_and_hides_unknown_groups() { 167 let owner = pubkey("1"); 168 let projection = projection_with_group("Farm", metadata(false, false, false, false), owner); 169 let authority = GroupAuthority::empty(); 170 let gate = GroupReadGate::new(&projection, &authority); 171 172 assert_eq!( 173 gate.screen_event(&event(1, vec![h("Farm")]), None, Default::default()) 174 .expect("public"), 175 GroupReadDecision::Visible 176 ); 177 assert_eq!( 178 gate.screen_event(&event(1, vec![h("Other")]), None, Default::default()) 179 .expect("unknown"), 180 GroupReadDecision::Hidden 181 ); 182 } 183 184 #[test] 185 fn read_gate_screens_pocket_events_through_event_view() { 186 let owner = pubkey("1"); 187 let projection = projection_with_group("Farm", metadata(false, false, false, false), owner); 188 let authority = GroupAuthority::empty(); 189 let gate = GroupReadGate::new(&projection, &authority); 190 let event = event(1, vec![h("Farm")]); 191 let mut buffer = vec![0; 4096]; 192 193 assert_eq!( 194 gate.screen_event(pocket_event(&event, &mut buffer), None, Default::default()) 195 .expect("pocket"), 196 GroupReadDecision::Visible 197 ); 198 } 199 200 #[test] 201 fn read_gate_hides_hidden_and_private_group_events_from_non_members() { 202 let owner = pubkey("1"); 203 let member = pubkey("2"); 204 let outsider = pubkey("3"); 205 let mut projection = 206 projection_with_group("Farm", metadata(true, false, true, false), owner.clone()); 207 put_member(&mut projection, "Farm", member.clone()); 208 let authority = GroupAuthority::new([owner.clone()], Vec::<PublicKeyHex>::new()); 209 let gate = GroupReadGate::new(&projection, &authority); 210 211 assert_eq!( 212 gate.screen_event( 213 &event(1, vec![h("Farm")]), 214 Some(&outsider), 215 Default::default() 216 ) 217 .expect("outsider"), 218 GroupReadDecision::Hidden 219 ); 220 assert_eq!( 221 gate.screen_event( 222 &event(1, vec![h("Farm")]), 223 Some(&member), 224 Default::default() 225 ) 226 .expect("member"), 227 GroupReadDecision::Visible 228 ); 229 assert_eq!( 230 gate.screen_event(&event(1, vec![h("Farm")]), Some(&owner), Default::default()) 231 .expect("owner"), 232 GroupReadDecision::Visible 233 ); 234 } 235 236 #[test] 237 fn read_gate_splits_private_and_hidden_snapshot_visibility() { 238 let owner = pubkey("1"); 239 let projection = 240 projection_with_group("Farm", metadata(true, false, false, false), owner.clone()); 241 let authority = GroupAuthority::new([owner.clone()], Vec::<PublicKeyHex>::new()); 242 let gate = GroupReadGate::new(&projection, &authority); 243 244 assert_eq!( 245 gate.screen_event( 246 &event(KIND_GROUP_METADATA, vec![d("Farm")]), 247 None, 248 Default::default() 249 ) 250 .expect("private metadata"), 251 GroupReadDecision::Visible 252 ); 253 assert_eq!( 254 gate.screen_event( 255 &event(KIND_GROUP_ADMINS, vec![d("Farm")]), 256 None, 257 Default::default() 258 ) 259 .expect("private admins"), 260 GroupReadDecision::Visible 261 ); 262 assert_eq!( 263 gate.screen_event( 264 &event(KIND_GROUP_MEMBERS, vec![d("Farm")]), 265 None, 266 Default::default() 267 ) 268 .expect("private members"), 269 GroupReadDecision::Hidden 270 ); 271 assert_eq!( 272 gate.screen_event( 273 &event(KIND_GROUP_MEMBERS, vec![d("Farm")]), 274 Some(&owner), 275 Default::default() 276 ) 277 .expect("owner members"), 278 GroupReadDecision::Visible 279 ); 280 281 let hidden_projection = 282 projection_with_group("Hidden", metadata(false, false, true, false), owner.clone()); 283 let hidden_gate = GroupReadGate::new(&hidden_projection, &authority); 284 assert_eq!( 285 hidden_gate 286 .screen_event( 287 &event(KIND_GROUP_METADATA, vec![d("Hidden")]), 288 None, 289 Default::default() 290 ) 291 .expect("hidden metadata"), 292 GroupReadDecision::Hidden 293 ); 294 } 295 296 #[test] 297 fn require_visible_uses_non_enumerating_error() { 298 let owner = pubkey("1"); 299 let projection = projection_with_group("Farm", metadata(true, false, true, false), owner); 300 let authority = GroupAuthority::empty(); 301 let gate = GroupReadGate::new(&projection, &authority); 302 303 assert_eq!( 304 gate.require_visible(&event(1, vec![h("Farm")]), None, Default::default()) 305 .expect_err("hidden") 306 .message(), 307 "group is unavailable" 308 ); 309 } 310 311 #[test] 312 fn read_gate_hides_deleted_target_events() { 313 let owner = pubkey("1"); 314 let mut projection = 315 projection_with_group("Farm", metadata(false, false, false, false), owner); 316 let target = event(1, vec![h("Farm")]); 317 projection.put_event_deletion(GroupEventDeletion::new( 318 GroupId::new("Farm").expect("group"), 319 target.id().clone(), 320 event_id("20"), 321 UnixTimestamp::new(20), 322 pubkey("2"), 323 tuple(20, "20", 2), 324 )); 325 let authority = GroupAuthority::empty(); 326 let gate = GroupReadGate::new(&projection, &authority); 327 328 assert_eq!( 329 gate.screen_event(&target, None, Default::default()) 330 .expect("deleted"), 331 GroupReadDecision::Hidden 332 ); 333 } 334 335 #[test] 336 fn read_gate_keeps_group_delete_marker_under_group_visibility_policy() { 337 let owner = pubkey("1"); 338 let mut projection = 339 projection_with_group("Farm", metadata(false, false, false, false), owner); 340 let marker = event(KIND_GROUP_DELETE_GROUP, vec![h("Farm")]); 341 projection.put_tombstone(GroupTombstone::new( 342 GroupId::new("Farm").expect("group"), 343 marker.id().clone(), 344 marker.unsigned().created_at(), 345 marker.unsigned().pubkey().clone(), 346 tuple(30, "01", 3), 347 )); 348 let authority = GroupAuthority::empty(); 349 let gate = GroupReadGate::new(&projection, &authority); 350 351 assert_eq!( 352 gate.screen_event(&marker, None, Default::default()) 353 .expect("marker"), 354 GroupReadDecision::Visible 355 ); 356 assert_eq!( 357 gate.screen_event(&event(1, vec![h("Farm")]), None, Default::default()) 358 .expect("normal"), 359 GroupReadDecision::Hidden 360 ); 361 } 362 363 fn projection_with_group( 364 group_id: &str, 365 metadata: GroupMetadata, 366 author: PublicKeyHex, 367 ) -> GroupProjection { 368 let mut projection = GroupProjection::new(); 369 projection.put_group(GroupState::new( 370 GroupId::new(group_id).expect("group"), 371 metadata, 372 author, 373 event_id("10"), 374 tuple(10, "10", 1), 375 )); 376 projection 377 } 378 379 fn put_member(projection: &mut GroupProjection, group_id: &str, pubkey: PublicKeyHex) { 380 projection.put_member( 381 GroupId::new(group_id).expect("group"), 382 MemberState::new( 383 pubkey, 384 MemberStatus::Member, 385 Default::default(), 386 event_id("20"), 387 tuple(20, "20", 2), 388 ), 389 ); 390 } 391 392 fn metadata(private: bool, restricted: bool, hidden: bool, closed: bool) -> GroupMetadata { 393 GroupMetadata::from_parts( 394 GroupMetadataText::empty(), 395 GroupMetadataFlags::new(private, restricted, hidden, closed), 396 SupportedKinds::UnspecifiedAll, 397 ) 398 } 399 400 fn event(kind_value: u32, tags: Vec<Tag>) -> Event { 401 Event::new( 402 event_id("01"), 403 UnsignedEvent::new( 404 pubkey("9"), 405 UnixTimestamp::new(1), 406 Kind::new(kind_value.into()).expect("kind"), 407 tags, 408 "", 409 ), 410 SignatureHex::new(&"2".repeat(128)).expect("sig"), 411 ) 412 } 413 414 fn pocket_event<'a>(event: &Event, buffer: &'a mut [u8]) -> &'a PocketEvent { 415 let raw = event_to_value(event).to_string(); 416 let (_, pocket) = PocketEvent::from_json(raw.as_bytes(), buffer).expect("pocket"); 417 pocket 418 } 419 420 fn h(group_id: &str) -> Tag { 421 Tag::from_parts("h", &[group_id]).expect("h") 422 } 423 424 fn d(group_id: &str) -> Tag { 425 Tag::from_parts("d", &[group_id]).expect("d") 426 } 427 428 fn pubkey(suffix: &str) -> PublicKeyHex { 429 PublicKeyHex::new(&suffix.repeat(64)).expect("pubkey") 430 } 431 432 fn tuple(created_at: u64, suffix: &str, offset: u64) -> ProjectionOrderTuple { 433 ProjectionOrderTuple::new( 434 UnixTimestamp::new(created_at), 435 event_id(suffix), 436 StoreOffset::new(offset), 437 ) 438 } 439 440 fn event_id(suffix: &str) -> EventId { 441 let mut value = "0".repeat(64 - suffix.len()); 442 value.push_str(suffix); 443 EventId::new(&value).expect("id") 444 } 445 }