kcode_k1_access_persons/
lib.rs1use 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}