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 list_user(
90 &self,
91 principal: RequestPrincipal,
92 expected_subsystem: SubsystemId,
93 ) -> Result<Vec<AccessId>, String> {
94 self.driver
95 .discovered_for_user(principal.user(), expected_subsystem)
96 }
97
98 pub fn list_group(
99 &self,
100 principal: RequestPrincipal,
101 group: GroupId,
102 expected_subsystem: SubsystemId,
103 ) -> Result<Vec<AccessId>, String> {
104 let groups = self.groups.groups_for_user(principal.user())?;
105 require_group(&groups, group)?;
106 self.driver.discovered_for_group(group, expected_subsystem)
107 }
108
109 pub fn list_user_group_targets(
110 &self,
111 principal: RequestPrincipal,
112 expected_subsystem: SubsystemId,
113 ) -> Result<Vec<(AccessId, Target)>, String> {
114 let user = principal.user();
115 let groups = self.groups.groups_for_user(user)?;
116 self.driver
117 .discovered_targets_for_user_and_groups(user, &groups, expected_subsystem)
118 }
119}
120
121fn require_group(groups: &[GroupId], group: GroupId) -> Result<(), String> {
122 if groups.contains(&group) {
123 Ok(())
124 } else {
125 Err(ACCESS_DENIED.to_owned())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 type ListUserGroupTargets =
134 fn(&K1Access, RequestPrincipal, SubsystemId) -> Result<Vec<(AccessId, Target)>, String>;
135
136 fn group(byte: u8) -> GroupId {
137 GroupId::new(TxId::from_bytes([byte; 12]))
138 }
139
140 #[test]
141 fn requested_group_is_required() {
142 let requested = group(1);
143 assert_eq!(require_group(&[requested], requested), Ok(()));
144 assert_eq!(
145 require_group(&[group(2), group(3)], requested),
146 Err(ACCESS_DENIED.to_owned())
147 );
148 }
149
150 #[test]
151 fn user_group_target_listing_has_expected_facade_contract() {
152 let _: ListUserGroupTargets = K1Access::list_user_group_targets;
153 }
154}