1use std::{path::Path, sync::Arc};
2
3use kcode_k1_access_driver::K1AccessDriver;
4use kcode_k1_groups::K1Groups;
5use kcode_k1_peering::K1Peering;
6use kcode_k1_txn_ordering::K1TxnOrdering;
7
8pub use kcode_k1_access_types::{
9 AccessCheck, AccessId, AccessRevision, Authorizations, GroupId, ModelId, OwnerSubject,
10 RequestPrincipal, Target, TxId, UserId, ViewerSubject,
11};
12pub use kcode_k1_transaction::SubsystemId;
13
14const ACCESS_DENIED: &str = "access denied";
15
16pub struct K1Access {
17 driver: K1AccessDriver,
18 groups: Arc<K1Groups>,
19}
20
21impl K1Access {
22 pub fn open(
23 root: &Path,
24 ordering: Arc<K1TxnOrdering>,
25 peering: Arc<K1Peering>,
26 groups: Arc<K1Groups>,
27 ) -> Result<Self, String> {
28 Ok(Self {
29 driver: K1AccessDriver::open(root, ordering, peering)?,
30 groups,
31 })
32 }
33
34 pub fn create(
35 &self,
36 target: Target,
37 authorizations: Authorizations,
38 ) -> Result<AccessRevision, String> {
39 self.driver.create(target, authorizations)
40 }
41
42 pub fn set_authorizations(
43 &self,
44 principal: RequestPrincipal,
45 access_id: AccessId,
46 authorizations: Authorizations,
47 ) -> Result<AccessRevision, String> {
48 let memberships = self
49 .groups
50 .memberships(principal.user(), principal.model())?;
51 let witness =
52 self.driver
53 .owner_witness(access_id, principal.user(), memberships.user_groups())?;
54 let witness = witness.ok_or_else(|| "principal is not an access owner".to_owned())?;
55 self.driver.replace(
56 access_id,
57 principal.user(),
58 memberships.revision(),
59 witness,
60 authorizations,
61 )
62 }
63
64 pub fn check(
65 &self,
66 principal: RequestPrincipal,
67 access_id: AccessId,
68 expected_subsystem: SubsystemId,
69 ) -> Result<AccessCheck, String> {
70 if self
71 .driver
72 .discovery_missing(access_id, expected_subsystem)?
73 {
74 self.driver.ensure_discovery(access_id)?;
75 }
76 let memberships = self
77 .groups
78 .memberships(principal.user(), principal.model())?;
79 self.driver.check(
80 principal,
81 access_id,
82 expected_subsystem,
83 memberships.user_groups(),
84 memberships.model_groups(),
85 memberships.revision(),
86 )
87 }
88
89 pub fn check_many(
90 &self,
91 user: UserId,
92 model: ModelId,
93 access_ids: &[AccessId],
94 expected_subsystem: SubsystemId,
95 ) -> Result<Vec<AccessCheck>, String> {
96 if access_ids.is_empty() {
97 return Ok(Vec::new());
98 }
99 let memberships = self.groups.memberships(user, model)?;
100 self.driver.check_many(
101 RequestPrincipal::new(user, model),
102 access_ids,
103 expected_subsystem,
104 memberships.user_groups(),
105 memberships.model_groups(),
106 memberships.revision(),
107 )
108 }
109
110 pub fn resolve_visible_targets(
111 &self,
112 user: UserId,
113 model: ModelId,
114 targets: &[Target],
115 expected_subsystem: SubsystemId,
116 ) -> Result<Vec<Option<AccessId>>, String> {
117 if targets.is_empty() {
118 return Ok(Vec::new());
119 }
120 let memberships = self.groups.memberships(user, model)?;
121 self.driver.resolve_visible_targets(
122 RequestPrincipal::new(user, model),
123 targets,
124 expected_subsystem,
125 memberships.user_groups(),
126 memberships.model_groups(),
127 )
128 }
129
130 pub fn list_user(
131 &self,
132 principal: RequestPrincipal,
133 expected_subsystem: SubsystemId,
134 ) -> Result<Vec<AccessId>, String> {
135 self.driver
136 .discovered_for_user(principal.user(), expected_subsystem)
137 }
138
139 pub fn list_group(
140 &self,
141 principal: RequestPrincipal,
142 group: GroupId,
143 expected_subsystem: SubsystemId,
144 ) -> Result<Vec<AccessId>, String> {
145 let groups = self.groups.groups_for_user(principal.user())?;
146 require_group(&groups, group)?;
147 self.driver.discovered_for_group(group, expected_subsystem)
148 }
149
150 pub fn list_user_group_targets(
151 &self,
152 principal: RequestPrincipal,
153 expected_subsystem: SubsystemId,
154 ) -> Result<Vec<(AccessId, Target)>, String> {
155 let user = principal.user();
156 let groups = self.groups.groups_for_user(user)?;
157 self.driver
158 .discovered_targets_for_user_and_groups(user, &groups, expected_subsystem)
159 }
160}
161
162fn require_group(groups: &[GroupId], group: GroupId) -> Result<(), String> {
163 if groups.contains(&group) {
164 Ok(())
165 } else {
166 Err(ACCESS_DENIED.to_owned())
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 type CheckMany = fn(
175 &K1Access,
176 UserId,
177 ModelId,
178 &[AccessId],
179 SubsystemId,
180 ) -> Result<Vec<AccessCheck>, String>;
181 type ResolveVisibleTargets = fn(
182 &K1Access,
183 UserId,
184 ModelId,
185 &[Target],
186 SubsystemId,
187 ) -> Result<Vec<Option<AccessId>>, String>;
188 type ListUserGroupTargets =
189 fn(&K1Access, RequestPrincipal, SubsystemId) -> Result<Vec<(AccessId, Target)>, String>;
190
191 fn group(byte: u8) -> GroupId {
192 GroupId::new(TxId::from_bytes([byte; 12]))
193 }
194
195 #[test]
196 fn requested_group_is_required() {
197 let requested = group(1);
198 assert_eq!(require_group(&[requested], requested), Ok(()));
199 assert_eq!(
200 require_group(&[group(2), group(3)], requested),
201 Err(ACCESS_DENIED.to_owned())
202 );
203 }
204
205 #[test]
206 fn batch_methods_have_expected_facade_contracts() {
207 let _: CheckMany = K1Access::check_many;
208 let _: ResolveVisibleTargets = K1Access::resolve_visible_targets;
209 }
210
211 #[test]
212 fn user_group_target_listing_has_expected_facade_contract() {
213 let _: ListUserGroupTargets = K1Access::list_user_group_targets;
214 }
215}