tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

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 }