1//! The one place that decides who may see what.2//!3//! The profile, ticket, and capability paths all route through [`can_view`]. Keeping it4//! single means the two can never disagree — a resource hidden from Bob's view of a5//! profile is also a resource he cannot obtain a capability for.67use devsite_proto::AccountId;8use serde::{Deserialize, Serialize};9101112pub enum Visibility {13 Public,14 Private,15 Shared,16}1718impl Visibility {19 pub fn as_str(&self) -> &'static str {20 match self {21 Visibility::Public => "public",22 Visibility::Private => "private",23 Visibility::Shared => "shared",24 }25 }2627 pub fn parse(text: &str) -> Option<Self> {28 match text {29 "public" => Some(Visibility::Public),30 "private" => Some(Visibility::Private),31 "shared" => Some(Visibility::Shared),32 _ => None,33 }34 }35}3637/// Whether `viewer` may see a resource owned by `owner`.38///39/// `shared_with` is the set of accounts the resource was explicitly shared with. Note that40/// an anonymous viewer (`None`) can only ever see public resources — there is no path41/// where a missing session widens access.42pub fn can_view(43 viewer: Option<AccountId>,44 owner: AccountId,45 visibility: Visibility,46 shared_with: &[AccountId],47) -> bool {48 match visibility {49 Visibility::Public => true,50 Visibility::Private => viewer == Some(owner),51 Visibility::Shared => {52 viewer == Some(owner) || viewer.is_some_and(|v| shared_with.contains(&v))53 }54 }55}565758mod tests {59 use super::*;6061 const ALICE: AccountId = AccountId::from_bytes([1; 16]);62 const BOB: AccountId = AccountId::from_bytes([2; 16]);63 const MALLORY: AccountId = AccountId::from_bytes([3; 16]);6465 66 fn public_resources_are_visible_to_everyone_including_strangers() {67 assert!(can_view(None, ALICE, Visibility::Public, &[]));68 assert!(can_view(Some(BOB), ALICE, Visibility::Public, &[]));69 }7071 72 fn private_resources_are_visible_only_to_their_owner() {73 assert!(can_view(Some(ALICE), ALICE, Visibility::Private, &[]));74 assert!(!can_view(Some(BOB), ALICE, Visibility::Private, &[]));75 assert!(!can_view(None, ALICE, Visibility::Private, &[]));76 }7778 79 fn shared_resources_reach_the_named_viewers_and_no_one_else() {80 let shared = [BOB];81 assert!(can_view(Some(ALICE), ALICE, Visibility::Shared, &shared));82 assert!(can_view(Some(BOB), ALICE, Visibility::Shared, &shared));83 assert!(!can_view(Some(MALLORY), ALICE, Visibility::Shared, &shared));84 assert!(!can_view(None, ALICE, Visibility::Shared, &shared));85 }8687 88 fn an_empty_share_list_is_not_a_wildcard() {89 // A "shared" resource with nobody named must behave like a private one rather90 // than falling open.91 assert!(!can_view(Some(BOB), ALICE, Visibility::Shared, &[]));92 assert!(can_view(Some(ALICE), ALICE, Visibility::Shared, &[]));93 }94}