Skip to main content

kcode_k1_access/
lib.rs

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}