use std::sync::Arc;
use kcode_k1_access::{
AccessCheck, AccessId, Authorizations, K1Access, ModelId, OwnerSubject, RequestPrincipal,
SubsystemId, Target, TxId, UserId, ViewerSubject,
};
use kcode_k1_groups::{ALL_MODELS, ALL_USERS, GroupId, GroupName, GroupRole, K1Groups};
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
struct Stack {
_ordering: Arc<K1TxnOrdering>,
_peering: Arc<K1Peering>,
groups: Arc<K1Groups>,
access: Arc<K1Access>,
}
impl Stack {
fn open(root: &std::path::Path) -> Result<Self, String> {
let ordering = Arc::new(at(
"open ordering",
K1TxnOrdering::open(&root.join("ordering")),
)?);
let peering = Arc::new(at(
"open peering",
K1Peering::open(&root.join("peering"), ordering.clone()),
)?);
let groups = Arc::new(at(
"open groups",
K1Groups::open(&root.join("groups"), ordering.clone(), peering.clone()),
)?);
let access = Arc::new(at(
"open access",
K1Access::open(
&root.join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
),
)?);
Ok(Self {
_ordering: ordering,
_peering: peering,
groups,
access,
})
}
}
fn at<T>(label: &str, result: Result<T, String>) -> Result<T, String> {
result.map_err(|error| format!("{label}: {error}"))
}
fn require(condition: bool, label: &str) -> Result<(), String> {
condition.then_some(()).ok_or_else(|| label.to_owned())
}
fn tx(byte: u8) -> TxId {
TxId::from_bytes([byte; 12])
}
fn user(byte: u8) -> UserId {
UserId::from_tx_id(tx(byte))
}
fn model(byte: u8) -> ModelId {
ModelId::from_bytes([byte; 32])
}
fn name(value: &str) -> Result<GroupName, String> {
GroupName::new(value.to_owned())
}
fn auth(
owners: Vec<OwnerSubject>,
viewers: Vec<ViewerSubject>,
label: &str,
) -> Result<Authorizations, String> {
at(label, Authorizations::new(owners, viewers))
}
fn check(
stack: &Stack,
principal: RequestPrincipal,
access_id: AccessId,
subsystem: SubsystemId,
label: &str,
) -> Result<AccessCheck, String> {
at(label, stack.access.check(principal, access_id, subsystem))
}
fn create_group(stack: &Stack, owner: UserId, label: &str) -> Result<GroupId, String> {
Ok(at(label, stack.groups.create(owner, name(label)?))?.group_id())
}
fn listed(
stack: &Stack,
principal: RequestPrincipal,
group: GroupId,
subsystem: SubsystemId,
expected: &[AccessId],
label: &str,
) -> Result<(), String> {
let actual = at(label, stack.access.list_group(principal, group, subsystem))?;
require(actual == expected, label)
}
pub fn verify() -> Result<(), String> {
let root = tempfile::tempdir().map_err(|error| format!("create root: {error}"))?;
let stack = Stack::open(root.path())?;
let owner = user(1);
let member = user(2);
let viewer = user(3);
let cross_user = user(4);
let late_user = user(5);
let group_model = model(11);
let direct_model = model(12);
let cross_model = model(14);
let subsystem = SubsystemId::from_str("audio-fragment")?;
let sentinel_target = Target::new(subsystem, vec![0, 1, 0, 1]);
let sentinel_access = at(
"create sentinel access",
stack.access.create(
sentinel_target.clone(),
auth(
vec![OwnerSubject::User(owner)],
vec![
ViewerSubject::Group(ALL_USERS),
ViewerSubject::Group(ALL_MODELS),
],
"sentinel authorizations",
)?,
),
)?
.access_id();
let sentinel = check(
&stack,
RequestPrincipal::new(user(21), model(21)),
sentinel_access,
subsystem,
"sentinel viewer check",
)?;
require(
sentinel.can_view()
&& !sentinel.can_manage()
&& sentinel.target() == Some(&sentinel_target),
"user/model sentinels did not grant dimensioned view-only access",
)?;
listed(
&stack,
RequestPrincipal::new(user(21), model(21)),
ALL_USERS,
subsystem,
&[sentinel_access],
"ordinary user lists all-users discovery",
)?;
let current_user = user(22);
let built_in_target = Target::new(subsystem, vec![0, 2, 0, 2]);
let built_in_access = at(
"create built-in-shape access",
stack.access.create(
built_in_target.clone(),
auth(
vec![OwnerSubject::User(current_user)],
vec![ViewerSubject::Group(ALL_MODELS)],
"built-in-shape authorizations",
)?,
),
)?
.access_id();
let built_in = check(
&stack,
RequestPrincipal::new(current_user, model(22)),
built_in_access,
subsystem,
"built-in-shape check",
)?;
require(
built_in.can_view() && built_in.can_manage() && built_in.target() == Some(&built_in_target),
"built-in-profile authorization shape did not grant view and management",
)?;
let owner_group = create_group(&stack, owner, "create owner group")?;
at(
"add owner-group user",
stack
.groups
.set_user_role(owner, owner_group, member, Some(GroupRole::User)),
)?;
at(
"add owner-group model",
stack
.groups
.set_model_membership(owner, owner_group, group_model, true),
)?;
let target = Target::new(subsystem, vec![0, 255, 7, 0]);
let access_id = at(
"create group-owned access",
stack.access.create(
target.clone(),
auth(
vec![OwnerSubject::Group(owner_group)],
vec![
ViewerSubject::User(viewer),
ViewerSubject::Model(direct_model),
],
"group-owned authorizations",
)?,
),
)?
.access_id();
let member_view = check(
&stack,
RequestPrincipal::new(member, group_model),
access_id,
subsystem,
"owner-group member check",
)?;
require(
member_view.can_view() && member_view.can_manage() && member_view.target() == Some(&target),
"owner-group human/model permissions are wrong",
)?;
let model_view = check(
&stack,
RequestPrincipal::new(viewer, group_model),
access_id,
subsystem,
"owner-group model check",
)?;
require(
model_view.can_view() && !model_view.can_manage(),
"model owner-group membership granted management or lost viewing",
)?;
at(
"revoke owner-group model",
stack
.groups
.set_model_membership(owner, owner_group, group_model, false),
)?;
let revoked_model = check(
&stack,
RequestPrincipal::new(member, group_model),
access_id,
subsystem,
"model revocation check",
)?;
require(
!revoked_model.can_view() && revoked_model.can_manage(),
"model revocation did not remove view while preserving user management",
)?;
at(
"restore owner-group model",
stack
.groups
.set_model_membership(owner, owner_group, group_model, true),
)?;
at(
"revoke owner-group human",
stack.groups.set_user_role(owner, owner_group, member, None),
)?;
let revoked_user = check(
&stack,
RequestPrincipal::new(member, group_model),
access_id,
subsystem,
"user revocation check",
)?;
require(
!revoked_user.can_view() && !revoked_user.can_manage(),
"human revocation did not remove authorization",
)?;
let user_group = create_group(&stack, owner, "create user viewer group")?;
at(
"add user viewer group member",
stack
.groups
.set_user_role(owner, user_group, cross_user, Some(GroupRole::User)),
)?;
let model_group = create_group(&stack, owner, "create model viewer group")?;
at(
"add model viewer group member",
stack
.groups
.set_model_membership(owner, model_group, cross_model, true),
)?;
at(
"install cross-group authorizations",
stack.access.set_authorizations(
RequestPrincipal::new(owner, model(13)),
access_id,
auth(
vec![OwnerSubject::User(owner)],
vec![
ViewerSubject::Group(user_group),
ViewerSubject::Group(model_group),
],
"cross-group authorizations",
)?,
),
)?;
let cross = check(
&stack,
RequestPrincipal::new(cross_user, cross_model),
access_id,
subsystem,
"different viewer groups check",
)?;
require(
cross.can_view() && !cross.can_manage(),
"user and model could not qualify through different groups",
)?;
let discovery_access = at(
"create group discovery access",
stack.access.create(
Target::new(subsystem, vec![2, 0, 2, 0]),
auth(
vec![OwnerSubject::Group(owner_group)],
vec![
ViewerSubject::Group(user_group),
ViewerSubject::Group(model_group),
],
"group discovery authorizations",
)?,
),
)?
.access_id();
listed(
&stack,
RequestPrincipal::new(owner, model(1)),
owner_group,
subsystem,
&[access_id, discovery_access],
"owner group receives creation discovery",
)?;
listed(
&stack,
RequestPrincipal::new(cross_user, model(4)),
user_group,
subsystem,
&[access_id, discovery_access],
"viewer group receives creation discovery",
)?;
require(
at(
"model-only user personal discovery",
stack
.access
.list_user(RequestPrincipal::new(late_user, cross_model), subsystem),
)?
.is_empty(),
"model membership created personal discovery",
)?;
let model_only = stack.access.list_group(
RequestPrincipal::new(late_user, cross_model),
model_group,
subsystem,
);
require(
model_only == Err("access denied".to_owned()),
"model membership created a human group discovery path",
)?;
at(
"add late viewer group member",
stack
.groups
.set_user_role(owner, user_group, late_user, Some(GroupRole::User)),
)?;
listed(
&stack,
RequestPrincipal::new(late_user, model(5)),
user_group,
subsystem,
&[access_id, discovery_access],
"late member lists historical group discovery",
)?;
at(
"remove late viewer group member",
stack
.groups
.set_user_role(owner, user_group, late_user, None),
)?;
let removed = stack.access.list_group(
RequestPrincipal::new(late_user, model(5)),
user_group,
subsystem,
);
require(
removed == Err("access denied".to_owned()),
"removed user retained group listing",
)?;
listed(
&stack,
RequestPrincipal::new(cross_user, model(4)),
user_group,
subsystem,
&[access_id, discovery_access],
"current member retains group discovery",
)?;
require(
at(
"removed user personal discovery",
stack
.access
.list_user(RequestPrincipal::new(late_user, model(5)), subsystem),
)?
.is_empty(),
"group discovery was copied into removed user discovery",
)?;
let replacement_group = create_group(&stack, owner, "create replacement viewer group")?;
at(
"add replacement viewer group member",
stack
.groups
.set_user_role(owner, replacement_group, late_user, Some(GroupRole::User)),
)?;
at(
"add replacement discovery group",
stack.access.set_authorizations(
RequestPrincipal::new(owner, model(13)),
discovery_access,
auth(
vec![OwnerSubject::Group(owner_group)],
vec![
ViewerSubject::Group(user_group),
ViewerSubject::Group(model_group),
ViewerSubject::Group(replacement_group),
],
"expanded group discovery authorizations",
)?,
),
)?;
listed(
&stack,
RequestPrincipal::new(late_user, model(5)),
replacement_group,
subsystem,
&[discovery_access],
"added authorization group receives discovery",
)?;
at(
"remove replacement discovery group",
stack.access.set_authorizations(
RequestPrincipal::new(owner, model(13)),
discovery_access,
auth(
vec![OwnerSubject::Group(owner_group)],
vec![
ViewerSubject::Group(user_group),
ViewerSubject::Group(model_group),
],
"reduced group discovery authorizations",
)?,
),
)?;
listed(
&stack,
RequestPrincipal::new(late_user, model(5)),
replacement_group,
subsystem,
&[discovery_access],
"removed authorization group retains stale discovery",
)
}
#[cfg(test)]
mod tests {
#[test]
fn groups_backed_access_conformance() {
super::verify().unwrap();
}
}