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 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}