use std::{
path::Path,
sync::{Arc, Barrier},
thread,
};
use kcode_k1_access::{
AccessCheck, AccessContext, AccessId, AccessPolicy, Authority, FilteredAuthorities, K1Access,
ModelId, ProfileId, SubsystemId, Target, TxId, UserId, ViewerSubject,
};
use kcode_k1_groups::K1Groups;
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
use tempfile::TempDir;
struct Stack {
_ordering: Arc<K1TxnOrdering>,
peering: Arc<K1Peering>,
_groups: Arc<K1Groups>,
access: Arc<K1Access>,
}
impl Stack {
fn open(root: &Path) -> Result<Self, String> {
let ordering = Arc::new(
K1TxnOrdering::open(&root.join("ordering"))
.map_err(|error| format!("open ordering: {error}"))?,
);
let peering = Arc::new(
K1Peering::open(&root.join("peering"), ordering.clone())
.map_err(|error| format!("open peering: {error}"))?,
);
let groups = Arc::new(
K1Groups::open(&root.join("groups"), ordering.clone(), peering.clone())
.map_err(|error| format!("open groups: {error}"))?,
);
let access = Arc::new(
K1Access::open(
&root.join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
)
.map_err(|error| format!("open access: {error}"))?,
);
Ok(Self {
_ordering: ordering,
peering,
_groups: groups,
access,
})
}
}
fn temporary_root(label: &str) -> Result<TempDir, String> {
tempfile::Builder::new()
.prefix(&format!("k1-access-testkit-{label}-"))
.tempdir()
.map_err(|error| format!("{label}: create temporary root: {error}"))
}
fn require(condition: bool, label: &str) -> Result<(), String> {
if condition {
Ok(())
} else {
Err(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 profile(byte: u8) -> ProfileId {
ProfileId::new(tx(byte))
}
fn model(byte: u8) -> ModelId {
ModelId::from_bytes([byte; 32])
}
fn context(user: UserId, model: ModelId) -> Result<AccessContext, String> {
AccessContext::new(user, model, FilteredAuthorities::empty())
}
fn policy(
authority: UserId,
editor: UserId,
viewer: UserId,
viewer_model: ModelId,
) -> Result<AccessPolicy, String> {
AccessPolicy::new(
Authority::User(authority),
vec![Authority::User(editor)],
vec![
ViewerSubject::User(viewer),
ViewerSubject::Model(viewer_model),
],
)
}
fn assert_profile(
access: &K1Access,
access_id: AccessId,
expected: ProfileId,
label: &str,
) -> Result<(), String> {
require(access.profile_id(access_id)? == Some(expected), label)
}
fn assert_check(
access: &K1Access,
context: &AccessContext,
access_id: AccessId,
subsystem: SubsystemId,
target: &Target,
expected: (bool, bool),
label: &str,
) -> Result<AccessCheck, String> {
let check = access
.check(context, access_id, subsystem)
.map_err(|error| format!("{label}: {error}"))?;
let target_matches = if expected.0 {
check.target() == Some(target)
} else {
check.target().is_none()
};
require(
(check.can_view(), check.can_edit()) == expected && target_matches,
label,
)?;
Ok(check)
}
fn assert_hidden(check: &AccessCheck, label: &str) -> Result<(), String> {
require(
!check.can_view()
&& !check.can_edit()
&& check.target().is_none()
&& check.access_revision().is_none(),
label,
)
}
fn assert_list(
access: &K1Access,
context: &AccessContext,
subsystem: SubsystemId,
expected: &[AccessId],
label: &str,
) -> Result<(), String> {
let actual = access
.list_user(context, subsystem)
.map_err(|error| format!("{label}: {error}"))?;
require(actual == expected, label)
}
pub fn verify() -> Result<(), String> {
kcode_k1_access_groups_testkit::verify()
.map_err(|error| format!("Access Groups verifier: {error}"))?;
kcode_k1_access_store_testkit::verify_access_store()
.map_err(|error| format!("Access store verifier: {error}"))?;
kcode_k1_access_privacy_testkit::verify_access_privacy()
.map_err(|error| format!("Access privacy verifier: {error}"))?;
{
let root = temporary_root("lifecycle")?;
lifecycle(root.path()).map_err(|error| format!("teardown/reopen scenario: {error}"))?;
}
{
let root = temporary_root("concurrent")?;
concurrent(root.path()).map_err(|error| format!("concurrent scenario: {error}"))?;
}
{
let root = temporary_root("malformed")?;
malformed_replay(root.path()).map_err(|error| format!("malformed scenario: {error}"))?;
}
Ok(())
}
fn lifecycle(root: &Path) -> Result<(), String> {
let stack = Stack::open(root)?;
let authority = user(1);
let old_editor = user(2);
let old_viewer = user(3);
let new_editor = user(4);
let new_viewer = user(5);
let second_editor = user(6);
let second_viewer = user(7);
let old_model = model(11);
let new_model = model(12);
let second_model = model(13);
let first_profile = profile(14);
let second_profile = profile(15);
let subsystem = SubsystemId::from_str("audio-fragment")?;
let first_target = Target::new(subsystem, vec![1, 0, 1]);
let second_target = Target::new(subsystem, vec![2, 0, 2]);
let owner = context(authority, old_model)?;
let first_revision = stack.access.create(
&owner,
first_target.clone(),
first_profile,
policy(authority, old_editor, old_viewer, old_model)?,
)?;
let first_id = first_revision.access_id();
assert_profile(
&stack.access,
first_id,
first_profile,
"created first profile link",
)?;
let second_revision = stack.access.create(
&context(authority, second_model)?,
second_target.clone(),
second_profile,
policy(authority, second_editor, second_viewer, second_model)?,
)?;
let second_id = second_revision.access_id();
assert_profile(
&stack.access,
second_id,
second_profile,
"created second profile link",
)?;
let initial_editor = context(old_editor, old_model)?;
let initial_viewer = context(old_viewer, old_model)?;
for (context, label) in [
(&initial_editor, "initial editor discovery"),
(&initial_viewer, "initial viewer discovery"),
] {
assert_list(&stack.access, context, subsystem, &[first_id], label)?;
}
let replacement = stack.access.replace_policy(
&context(authority, new_model)?,
first_id,
vec![Authority::User(new_editor)],
vec![
ViewerSubject::User(new_viewer),
ViewerSubject::Model(new_model),
],
)?;
require(
replacement.access_id() == first_id && replacement.txid() != first_revision.txid(),
"replacement did not retain identity with a new revision",
)?;
assert_profile(
&stack.access,
first_id,
first_profile,
"replacement changed first profile link",
)?;
let owner_current = context(authority, new_model)?;
let old_editor_current = context(old_editor, new_model)?;
let old_viewer_current = context(old_viewer, new_model)?;
let new_editor_current = context(new_editor, new_model)?;
let new_viewer_current = context(new_viewer, new_model)?;
for (context, label) in [
(&old_editor_current, "removed editor discovery"),
(&old_viewer_current, "removed viewer discovery"),
(&new_editor_current, "added editor discovery"),
(&new_viewer_current, "added viewer discovery"),
] {
assert_list(&stack.access, context, subsystem, &[first_id], label)?;
}
drop(stack);
let stack = Stack::open(root)?;
assert_profile(
&stack.access,
first_id,
first_profile,
"reopened first profile link",
)?;
assert_profile(
&stack.access,
second_id,
second_profile,
"reopened second profile link",
)?;
for (context, expected, label) in [
(&owner_current, (true, true), "reopened authority rights"),
(&new_editor_current, (true, true), "reopened editor rights"),
(&new_viewer_current, (true, false), "reopened viewer rights"),
(
&old_editor_current,
(false, false),
"reopened removed editor",
),
(
&old_viewer_current,
(false, false),
"reopened removed viewer",
),
] {
let check = assert_check(
&stack.access,
context,
first_id,
subsystem,
&first_target,
expected,
label,
)?;
require(
check.access_revision() == expected.0.then_some(replacement.txid()),
label,
)?;
}
for (context, expected, label) in [
(
&owner_current,
&[first_id, second_id][..],
"reopened authority discovery order",
),
(
&old_editor_current,
&[first_id][..],
"reopened removed editor discovery",
),
(
&old_viewer_current,
&[first_id][..],
"reopened removed viewer discovery",
),
(
&new_editor_current,
&[first_id][..],
"reopened added editor discovery",
),
(
&new_viewer_current,
&[first_id][..],
"reopened added viewer discovery",
),
] {
assert_list(&stack.access, context, subsystem, expected, label)?;
}
let filtered = AccessContext::new(
new_editor,
new_model,
FilteredAuthorities::new(vec![Authority::User(authority)])?,
)?;
let hidden = assert_check(
&stack.access,
&filtered,
first_id,
subsystem,
&first_target,
(false, false),
"filtered scalar check",
)?;
assert_hidden(&hidden, "filtered scalar exposed authority data")?;
assert_list(
&stack.access,
&filtered,
subsystem,
&[],
"filtered list exposed authority data",
)?;
require(
stack
.access
.list_user_group_targets(&filtered, subsystem)?
.is_empty(),
"filtered combined metadata exposed authority data",
)?;
require(
stack.access.resolve_visible_targets(
&filtered,
&[first_target, second_target],
subsystem,
)? == [None, None],
"filtered reverse lookup exposed authority data",
)
}
fn concurrent(root: &Path) -> Result<(), String> {
let stack = Stack::open(root)?;
let authority = user(21);
let a_editor = user(22);
let a_viewer = user(23);
let b_old_editor = user(24);
let b_old_viewer = user(25);
let b_new_editor = user(26);
let b_new_viewer = user(27);
let a_model = model(31);
let b_old_model = model(32);
let b_new_model = model(33);
let a_profile = profile(34);
let b_profile = profile(35);
let subsystem = SubsystemId::from_str("audio-fragment")?;
let target_a = Target::new(subsystem, vec![10, 0, 10]);
let target_b = Target::new(subsystem, vec![20, 0, 20]);
let a_revision = stack.access.create(
&context(authority, a_model)?,
target_a.clone(),
a_profile,
policy(authority, a_editor, a_viewer, a_model)?,
)?;
let a_id = a_revision.access_id();
assert_profile(
&stack.access,
a_id,
a_profile,
"created concurrent A profile link",
)?;
let b_revision = stack.access.create(
&context(authority, b_old_model)?,
target_b.clone(),
b_profile,
policy(authority, b_old_editor, b_old_viewer, b_old_model)?,
)?;
let b_id = b_revision.access_id();
assert_profile(
&stack.access,
b_id,
b_profile,
"created concurrent B profile link",
)?;
let barrier = Arc::new(Barrier::new(3));
let check_access = stack.access.clone();
let check_barrier = barrier.clone();
let check_context = context(a_viewer, a_model)?;
let replace_access = stack.access.clone();
let replace_barrier = barrier.clone();
let replace_context = context(authority, b_new_model)?;
let (checked, replaced) = thread::scope(|scope| {
let checked = scope.spawn(move || {
check_barrier.wait();
check_access.check(&check_context, a_id, subsystem)
});
let replaced = scope.spawn(move || {
replace_barrier.wait();
replace_access.replace_policy(
&replace_context,
b_id,
vec![Authority::User(b_new_editor)],
vec![
ViewerSubject::User(b_new_viewer),
ViewerSubject::Model(b_new_model),
],
)
});
barrier.wait();
let checked = checked
.join()
.map_err(|_| "concurrent object A check panicked".to_owned())?
.map_err(|error| format!("concurrent object A check: {error}"))?;
let replaced = replaced
.join()
.map_err(|_| "concurrent object B replacement panicked".to_owned())?
.map_err(|error| format!("concurrent object B replacement: {error}"))?;
Ok::<_, String>((checked, replaced))
})?;
require(
checked.can_view()
&& !checked.can_edit()
&& checked.target() == Some(&target_a)
&& checked.access_revision() == Some(a_revision.txid()),
"concurrent object A check returned the wrong result",
)?;
require(
replaced.access_id() == b_id && replaced.txid() != b_revision.txid(),
"concurrent object B replacement was not genuine",
)?;
assert_profile(
&stack.access,
a_id,
a_profile,
"concurrent A profile link changed",
)?;
assert_profile(
&stack.access,
b_id,
b_profile,
"concurrent B profile link changed",
)?;
let b_new_editor_context = context(b_new_editor, b_new_model)?;
let b_new_viewer_context = context(b_new_viewer, b_new_model)?;
let b_old_editor_context = context(b_old_editor, b_new_model)?;
let b_old_viewer_context = context(b_old_viewer, b_new_model)?;
for (context, expected, label) in [
(
&b_new_editor_context,
(true, true),
"changed B editor rights",
),
(
&b_new_viewer_context,
(true, false),
"changed B viewer rights",
),
(
&b_old_editor_context,
(false, false),
"removed B editor rights",
),
(
&b_old_viewer_context,
(false, false),
"removed B viewer rights",
),
] {
let check = assert_check(
&stack.access,
context,
b_id,
subsystem,
&target_b,
expected,
label,
)?;
require(
check.access_revision() == expected.0.then_some(replaced.txid()),
label,
)?;
}
Ok(())
}
fn malformed_replay(root: &Path) -> Result<(), String> {
let stack = Stack::open(root)?;
let subsystem = SubsystemId::from_str("k1-access-subsystem")?;
let error = match stack.peering.submit_txn(subsystem, &[255]) {
Ok(_) => return Err("malformed callback unexpectedly succeeded".to_owned()),
Err(error) => error,
};
require(
error.to_ascii_lowercase().contains("committed"),
"malformed callback was not committed before failure",
)?;
require(
stack
.access
.check(
&context(user(61), model(61))?,
AccessId::new(tx(61)),
subsystem,
)
.is_err(),
"malformed callback left the live Access handle available",
)?;
drop(stack);
match Stack::open(root) {
Ok(_) => Err("fresh stack open replayed committed malformed input".to_owned()),
Err(_) => Ok(()),
}
}
#[cfg(test)]
mod tests {
#[test]
fn full_stack_conformance() {
super::verify().unwrap();
}
}