Skip to main content

kcode_k1_access_persons/
lib.rs

1use std::sync::Arc;
2
3use kcode_k1_access::{
4    AccessCheck, Authorizations, K1Access, SubsystemId, Target, TxId, ViewerSubject,
5};
6use kcode_k1_access_profiles::K1AccessProfiles;
7use kcode_k1_groups::{ALL_MODELS, ALL_USERS};
8use kcode_k1_persons::K1Persons;
9
10pub use kcode_k1_access::{AccessId, RequestPrincipal};
11pub use kcode_k1_access_profiles::ProfileSelection;
12pub use kcode_k1_persons::{PersonId, PersonView};
13
14const PERSON_SUBSYSTEM: &str = "k1-person";
15const NOT_VISIBLE: &str = "principal cannot access person";
16const NOT_UPDATER: &str = "principal must be able to view and manage person";
17const TARGET_UNAVAILABLE: &str = "person access target is unavailable";
18const INVALID_TARGET: &str = "person access target must contain exactly 12 person bytes";
19const PERSON_UNAVAILABLE: &str = "person is unavailable";
20const NONCANONICAL_TARGET: &str = "person access target is no longer canonical";
21const MISMATCHED_TARGET: &str = "person access target does not match supplied person";
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct SubmittedPerson {
25    pub access_id: AccessId,
26    pub person_id: PersonId,
27}
28
29pub struct K1AccessPersons {
30    access: Arc<K1Access>,
31    profiles: Arc<K1AccessProfiles>,
32    persons: Arc<K1Persons>,
33    subsystem: SubsystemId,
34}
35
36impl K1AccessPersons {
37    pub fn open(
38        access: Arc<K1Access>,
39        profiles: Arc<K1AccessProfiles>,
40        persons: Arc<K1Persons>,
41    ) -> Result<Self, String> {
42        Ok(Self {
43            access,
44            profiles,
45            persons,
46            subsystem: SubsystemId::from_str(PERSON_SUBSYSTEM)?,
47        })
48    }
49
50    pub fn create(
51        &self,
52        principal: RequestPrincipal,
53        profile: ProfileSelection,
54        name: String,
55    ) -> Result<SubmittedPerson, String> {
56        let resolved = self
57            .profiles
58            .resolve(principal, profile)?
59            .into_authorizations();
60        let mut viewers = resolved.viewers().to_vec();
61        viewers.extend([
62            ViewerSubject::Group(ALL_USERS),
63            ViewerSubject::Group(ALL_MODELS),
64        ]);
65        let authorizations = Authorizations::new(resolved.owners().to_vec(), viewers)?;
66        let person_id = self.persons.create(name)?;
67        let target = Target::new(self.subsystem, person_id.as_tx_id().as_bytes().to_vec());
68        let access_id = self.access.create(target, authorizations)?.access_id();
69        Ok(SubmittedPerson {
70            access_id,
71            person_id,
72        })
73    }
74
75    pub fn read(
76        &self,
77        principal: RequestPrincipal,
78        access_id: AccessId,
79    ) -> Result<PersonView, String> {
80        self.read_person(visible_person_id(self.access.check(
81            principal,
82            access_id,
83            self.subsystem,
84        )?)?)
85    }
86
87    pub fn read_person(&self, person_id: PersonId) -> Result<PersonView, String> {
88        self.persons
89            .read(person_id)?
90            .ok_or_else(|| PERSON_UNAVAILABLE.to_owned())
91    }
92
93    pub fn update(
94        &self,
95        principal: RequestPrincipal,
96        access_id: AccessId,
97        name: String,
98    ) -> Result<(), String> {
99        self.update_checked(principal, access_id, None, name)
100    }
101
102    pub fn update_person(
103        &self,
104        principal: RequestPrincipal,
105        person_id: PersonId,
106        access_id: AccessId,
107        name: String,
108    ) -> Result<(), String> {
109        self.update_checked(principal, access_id, Some(person_id), name)
110    }
111
112    fn update_checked(
113        &self,
114        principal: RequestPrincipal,
115        access_id: AccessId,
116        expected_person_id: Option<PersonId>,
117        name: String,
118    ) -> Result<(), String> {
119        let check = self.access.check(principal, access_id, self.subsystem)?;
120        if !check.can_view() || !check.can_manage() {
121            return Err(NOT_UPDATER.to_owned());
122        }
123        let person_id = target_person_id(&check)?;
124        if expected_person_id.is_some_and(|expected| expected != person_id) {
125            return Err(MISMATCHED_TARGET.to_owned());
126        }
127        self.update_canonical_person(person_id, name)
128    }
129
130    fn update_canonical_person(&self, person_id: PersonId, name: String) -> Result<(), String> {
131        let current = self.read_person(person_id)?;
132        if current.person_id != person_id {
133            return Err(NONCANONICAL_TARGET.to_owned());
134        }
135        self.persons.update(person_id, name)
136    }
137}
138
139fn visible_person_id(check: AccessCheck) -> Result<PersonId, String> {
140    if !check.can_view() {
141        return Err(NOT_VISIBLE.to_owned());
142    }
143    target_person_id(&check)
144}
145
146fn target_person_id(check: &AccessCheck) -> Result<PersonId, String> {
147    let target = check
148        .target()
149        .ok_or_else(|| TARGET_UNAVAILABLE.to_owned())?;
150    let bytes: [u8; 12] = target
151        .object_id()
152        .try_into()
153        .map_err(|_| INVALID_TARGET.to_owned())?;
154    Ok(PersonId::from_tx_id(TxId::from_bytes(bytes)))
155}