use std::{path::Path, sync::Arc};
use kcode_k1_access_driver::K1AccessDriver;
use kcode_k1_groups::K1Groups;
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
pub use kcode_k1_access_types::{
AccessCheck, AccessId, AccessRevision, Authorizations, GroupId, ModelId, OwnerSubject,
RequestPrincipal, Target, TxId, UserId, ViewerSubject,
};
pub use kcode_k1_transaction::SubsystemId;
const ACCESS_DENIED: &str = "access denied";
pub struct K1Access {
driver: K1AccessDriver,
groups: Arc<K1Groups>,
}
impl K1Access {
pub fn open(
root: &Path,
ordering: Arc<K1TxnOrdering>,
peering: Arc<K1Peering>,
groups: Arc<K1Groups>,
) -> Result<Self, String> {
Ok(Self {
driver: K1AccessDriver::open(root, ordering, peering)?,
groups,
})
}
pub fn create(
&self,
target: Target,
authorizations: Authorizations,
) -> Result<AccessRevision, String> {
self.driver.create(target, authorizations)
}
pub fn set_authorizations(
&self,
principal: RequestPrincipal,
access_id: AccessId,
authorizations: Authorizations,
) -> Result<AccessRevision, String> {
let memberships = self
.groups
.memberships(principal.user(), principal.model())?;
let witness =
self.driver
.owner_witness(access_id, principal.user(), memberships.user_groups())?;
let witness = witness.ok_or_else(|| "principal is not an access owner".to_owned())?;
self.driver.replace(
access_id,
principal.user(),
memberships.revision(),
witness,
authorizations,
)
}
pub fn check(
&self,
principal: RequestPrincipal,
access_id: AccessId,
expected_subsystem: SubsystemId,
) -> Result<AccessCheck, String> {
if self
.driver
.discovery_missing(access_id, expected_subsystem)?
{
self.driver.ensure_discovery(access_id)?;
}
let memberships = self
.groups
.memberships(principal.user(), principal.model())?;
self.driver.check(
principal,
access_id,
expected_subsystem,
memberships.user_groups(),
memberships.model_groups(),
memberships.revision(),
)
}
pub fn list_user(
&self,
principal: RequestPrincipal,
expected_subsystem: SubsystemId,
) -> Result<Vec<AccessId>, String> {
self.driver
.discovered_for_user(principal.user(), expected_subsystem)
}
pub fn list_group(
&self,
principal: RequestPrincipal,
group: GroupId,
expected_subsystem: SubsystemId,
) -> Result<Vec<AccessId>, String> {
let groups = self.groups.groups_for_user(principal.user())?;
require_group(&groups, group)?;
self.driver.discovered_for_group(group, expected_subsystem)
}
}
fn require_group(groups: &[GroupId], group: GroupId) -> Result<(), String> {
if groups.contains(&group) {
Ok(())
} else {
Err(ACCESS_DENIED.to_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn group(byte: u8) -> GroupId {
GroupId::new(TxId::from_bytes([byte; 12]))
}
#[test]
fn requested_group_is_required() {
let requested = group(1);
assert_eq!(require_group(&[requested], requested), Ok(()));
assert_eq!(
require_group(&[group(2), group(3)], requested),
Err(ACCESS_DENIED.to_owned())
);
}
}