Skip to main content

pardarsh_core/
access.rs

1//! Visibility and attribution disclosure.
2//!
3//! These are primitives, not a policy: the application decides which
4//! audiences a viewer holds and who is privileged. Everything that leaves the
5//! system for a viewer (API responses, exports) should pass through
6//! [`view_record`] / [`view_history`] so that restricted records, events and
7//! sources, and withheld identities, are never exposed by accident.
8
9use std::collections::BTreeSet;
10
11use crate::event::{Change, Event, EventKind};
12use crate::model::{ActorRef, Attribution, Disclosure, Record, Visibility};
13
14/// Who is looking.
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct Viewer {
17    pub actor: Option<ActorRef>,
18    pub audiences: BTreeSet<String>,
19    /// Privileged viewers (e.g. moderators) see everything.
20    pub privileged: bool,
21}
22
23impl Viewer {
24    /// An anonymous member of the public.
25    pub fn public() -> Self {
26        Self::default()
27    }
28
29    /// Sees everything. Use for internal processing and full-fidelity export.
30    pub fn privileged() -> Self {
31        Self { actor: None, audiences: BTreeSet::new(), privileged: true }
32    }
33
34    pub fn actor(actor: ActorRef) -> Self {
35        Self { actor: Some(actor), ..Self::default() }
36    }
37
38    pub fn with_audience(mut self, a: impl Into<String>) -> Self {
39        self.audiences.insert(a.into());
40        self
41    }
42
43    pub fn can_see(&self, v: &Visibility) -> bool {
44        match v {
45            Visibility::Public => true,
46            Visibility::Restricted { audiences } => {
47                self.privileged || audiences.iter().any(|a| self.audiences.contains(a))
48            }
49        }
50    }
51
52    fn mask(&self, a: &Attribution) -> Attribution {
53        let own = self.actor.as_ref() == Some(&a.actor);
54        if a.disclosure == Disclosure::Withheld && !self.privileged && !own {
55            Attribution { actor: ActorRef::Anonymous, disclosure: Disclosure::Withheld }
56        } else {
57            a.clone()
58        }
59    }
60}
61
62/// The record as `viewer` may see it, or `None` if it is not visible.
63pub fn view_record(record: &Record, viewer: &Viewer) -> Option<Record> {
64    if !viewer.can_see(&record.visibility) {
65        return None;
66    }
67    let mut r = record.clone();
68    r.sources.retain(|s| viewer.can_see(&s.visibility));
69    for s in r.sources.iter_mut() {
70        if let Some(a) = &s.submitted_by {
71            s.submitted_by = Some(viewer.mask(a));
72        }
73    }
74    for a in r.actors.iter_mut() {
75        a.attribution = viewer.mask(&a.attribution);
76    }
77    for rel in r.relations.iter_mut() {
78        rel.asserted_by = viewer.mask(&rel.asserted_by);
79        rel.sources.retain(|s| viewer.can_see(&s.visibility));
80    }
81    Some(r)
82}
83
84/// The events of a history that `viewer` may see, with restricted content
85/// removed and withheld identities masked. Callers must first check that the
86/// record's current state is visible (see [`view_record`]).
87pub fn view_history(events: &[Event], viewer: &Viewer) -> Vec<Event> {
88    let mut out = Vec::new();
89    for e in events {
90        if !viewer.can_see(&e.visibility) {
91            continue;
92        }
93        let mut e = e.clone();
94        e.attribution = viewer.mask(&e.attribution);
95        match &mut e.kind {
96            EventKind::Created { record } => {
97                // Whether the record is visible at all is decided on its
98                // *current* state by the caller; here only filter contents.
99                let original = std::mem::take(&mut record.visibility);
100                if let Some(mut r) = view_record(record, viewer) {
101                    r.visibility = original;
102                    **record = r;
103                }
104            }
105            EventKind::Amended { changes } | EventKind::Corrected { changes, .. } => {
106                changes.retain(|c| match c {
107                    Change::AddSource(s) => viewer.can_see(&s.visibility),
108                    _ => true,
109                });
110                for c in changes.iter_mut() {
111                    if let Change::AddActor(a) = c {
112                        a.attribution = viewer.mask(&a.attribution);
113                    }
114                }
115            }
116            EventKind::RelationAdded { relation } => {
117                relation.asserted_by = viewer.mask(&relation.asserted_by);
118                relation.sources.retain(|s| viewer.can_see(&s.visibility));
119            }
120            _ => {}
121        }
122        out.push(e);
123    }
124    out
125}