use super::*;
use kcode_k1_access_types::{FilteredAuthorities, ModelId, UserId, ViewerSubject};
fn tx(value: u32) -> TxId {
let mut bytes = [0; 12];
bytes[..4].copy_from_slice(&value.to_le_bytes());
TxId::from_bytes(bytes)
}
fn access(value: u32) -> AccessId {
AccessId::new(tx(value))
}
fn user(value: u32) -> UserId {
UserId::from_tx_id(tx(value))
}
fn group(value: u32) -> GroupId {
GroupId::new(tx(value))
}
fn model(value: u8) -> ModelId {
ModelId::from_bytes([value; 32])
}
fn subsystem(value: &str) -> SubsystemId {
SubsystemId::from_str(value).unwrap()
}
fn target(value: u32, subsystem_value: &str) -> Target {
Target::new(subsystem(subsystem_value), value.to_le_bytes().to_vec())
}
fn context(user_value: u32, model_value: u8) -> AccessContext {
AccessContext::new(
user(user_value),
model(model_value),
FilteredAuthorities::empty(),
)
.unwrap()
}
fn filtered_context(user_value: u32, model_value: u8, authority: Authority) -> AccessContext {
AccessContext::new(
user(user_value),
model(model_value),
FilteredAuthorities::new(vec![authority]).unwrap(),
)
.unwrap()
}
fn policy(
authority: Authority,
editors: Vec<Authority>,
viewers: Vec<ViewerSubject>,
) -> AccessPolicy {
AccessPolicy::new(authority, editors, viewers).unwrap()
}
fn stored(
id: u32,
target_value: u32,
subsystem_value: &str,
revision: u32,
policy: AccessPolicy,
) -> StoredAccess {
StoredAccess::new(
access(id),
target(target_value, subsystem_value),
tx(revision),
policy,
)
}
fn check(
state: &AccessState,
context: &AccessContext,
id: u32,
user_groups: &[GroupId],
model_groups: &[GroupId],
) -> AccessCheck {
state.check(
context,
access(id),
subsystem("alpha"),
user_groups,
model_groups,
Some(tx(8)),
)
}
fn checks(state: &AccessState, ids: &[AccessId]) -> Vec<AccessCheck> {
state.check_many(
&context(1, 1),
ids,
subsystem("alpha"),
&[group(1)],
&[group(1)],
Some(tx(8)),
)
}
fn resolve(
state: &AccessState,
context: &AccessContext,
targets: &[Target],
) -> Vec<Option<AccessId>> {
state.resolve_visible_targets(
context,
targets,
subsystem("alpha"),
&[group(1)],
&[group(1)],
)
}
fn assert_check(check: &AccessCheck, can_view: bool, can_edit: bool, revision: Option<TxId>) {
assert_eq!((check.can_view(), check.can_edit()), (can_view, can_edit));
assert_eq!(check.target().is_some(), can_view);
assert_eq!(check.access_revision(), revision);
assert_eq!(check.groups_revision(), revision.map(|_| tx(8)));
}
fn assert_concealed(check: &AccessCheck) {
assert_check(check, false, false, None);
}
fn shared(state: &AccessState, first: u32, second: u32) -> bool {
Arc::ptr_eq(
&state.records[&access(first)].policy,
&state.records[&access(second)].policy,
)
}
#[test]
fn initialization_and_mutations_preserve_invariants() {
let original = policy(Authority::User(user(1)), vec![], vec![]);
for records in [
vec![
stored(1, 1, "alpha", 10, original.clone()),
stored(1, 2, "alpha", 11, original.clone()),
],
vec![
stored(1, 1, "alpha", 10, original.clone()),
stored(2, 1, "alpha", 11, original.clone()),
],
] {
assert!(AccessState::new(records).is_err());
}
let mut state = AccessState::new(vec![stored(1, 1, "alpha", 10, original.clone())]).unwrap();
assert!(
state
.create(stored(1, 2, "alpha", 11, original.clone()))
.is_err()
);
assert!(
state
.create(stored(2, 1, "alpha", 11, original.clone()))
.is_err()
);
assert!(state.replace(access(9), tx(12), original).is_err());
let changed_authority = policy(Authority::Group(group(1)), vec![], vec![]);
assert!(state.replace(access(1), tx(12), changed_authority).is_err());
let replacement = policy(
Authority::User(user(1)),
vec![Authority::User(user(2))],
vec![ViewerSubject::Model(model(1))],
);
state
.replace(access(1), tx(12), replacement.clone())
.unwrap();
let record = &state.records[&access(1)];
assert_eq!(record.target, target(1, "alpha"));
assert_eq!(record.revision, tx(12));
assert_eq!(record.policy.as_ref(), &replacement);
assert_check(
&check(&state, &context(1, 1), 1, &[], &[]),
true,
true,
Some(tx(12)),
);
}
#[test]
fn policy_matrix_concealment_and_witnesses_are_exact() {
let matrix_policy = policy(
Authority::Group(group(1)),
vec![Authority::User(user(2)), Authority::Group(group(2))],
vec![
ViewerSubject::User(user(3)),
ViewerSubject::Group(group(3)),
ViewerSubject::Model(model(3)),
],
);
let state = AccessState::new(vec![stored(1, 1, "alpha", 9, matrix_policy.clone())]).unwrap();
let cases = vec![
(context(2, 3), vec![], vec![], true, true),
(context(2, 4), vec![], vec![], false, true),
(context(4, 4), vec![group(1)], vec![group(1)], true, true),
(context(4, 4), vec![group(2)], vec![group(2)], true, true),
(context(3, 3), vec![], vec![], true, false),
(context(4, 4), vec![group(3)], vec![group(3)], true, false),
(context(3, 4), vec![], vec![], false, false),
(context(4, 3), vec![], vec![], false, false),
];
for (context, user_groups, model_groups, can_view, can_edit) in cases {
let direct = evaluate(&matrix_policy, &context, &user_groups, &model_groups);
assert_eq!((direct.can_view(), direct.can_edit()), (can_view, can_edit));
let actual = check(&state, &context, 1, &user_groups, &model_groups);
assert_check(
&actual,
can_view,
can_edit,
(can_view || can_edit).then_some(tx(9)),
);
}
let filtered = filtered_context(4, 3, Authority::Group(group(1)));
assert_concealed(&check(&state, &filtered, 1, &[group(1)], &[group(1)]));
assert_concealed(&state.check(
&context(2, 3),
access(1),
subsystem("other"),
&[],
&[],
Some(tx(8)),
));
assert_concealed(&check(&state, &context(2, 3), 99, &[], &[]));
for witness in [
Authority::Group(group(1)),
Authority::User(user(2)),
Authority::Group(group(2)),
] {
assert!(state.accepts_edit_witness(access(1), witness));
}
assert!(!state.accepts_edit_witness(access(1), Authority::User(user(3))));
assert!(!state.accepts_edit_witness(access(1), Authority::Group(group(3))));
assert!(!state.accepts_edit_witness(access(99), Authority::Group(group(1))));
}
#[test]
fn batches_and_reverse_resolution_preserve_positions() {
let visible = policy(Authority::Group(group(1)), vec![], vec![]);
let hidden = policy(Authority::Group(group(2)), vec![], vec![]);
let state = AccessState::new(vec![
stored(1, 1, "alpha", 11, visible.clone()),
stored(2, 2, "alpha", 12, hidden),
stored(3, 3, "beta", 13, visible),
])
.unwrap();
let ids = [access(1), access(99), access(1), access(2), access(3)];
let results = checks(&state, &ids);
assert_eq!(results.len(), ids.len());
assert_eq!(results[0], results[2]);
assert_check(&results[0], true, true, Some(tx(11)));
for result in [&results[1], &results[3], &results[4]] {
assert_concealed(result);
}
assert!(checks(&state, &[]).is_empty());
let targets = [
target(1, "alpha"),
target(99, "alpha"),
target(1, "alpha"),
target(2, "alpha"),
target(3, "beta"),
];
assert_eq!(
resolve(&state, &context(1, 1), &targets),
vec![Some(access(1)), None, Some(access(1)), None, None]
);
let filtered = filtered_context(1, 1, Authority::Group(group(1)));
assert_eq!(resolve(&state, &filtered, &targets[..1]), vec![None]);
assert!(resolve(&state, &context(1, 1), &[]).is_empty());
}
#[test]
fn policies_are_shared_reused_cleaned_and_scaled() {
let first = policy(Authority::User(user(1)), vec![], vec![]);
let second = policy(
Authority::User(user(1)),
vec![],
vec![ViewerSubject::Model(model(1))],
);
let mut state = AccessState::new(vec![
stored(1, 1, "alpha", 1, first.clone()),
stored(2, 2, "alpha", 2, first.clone()),
stored(3, 3, "alpha", 3, second.clone()),
])
.unwrap();
assert!(shared(&state, 1, 2));
assert!(!shared(&state, 1, 3));
state
.create(stored(4, 4, "alpha", 4, first.clone()))
.unwrap();
assert!(shared(&state, 1, 4));
for id in [1, 2, 4] {
state
.replace(access(id), tx(10 + id), second.clone())
.unwrap();
assert!(shared(&state, id, 3));
}
assert!(!state.policies.contains_key(&first));
assert_eq!(state.policies.len(), 1);
let one_policy = policy(Authority::User(user(1)), vec![], vec![]);
let records = (1..=10_000)
.map(|value| stored(value, value, "alpha", 1, one_policy.clone()))
.collect();
let large = AccessState::new(records).unwrap();
assert_eq!(large.records.len(), 10_000);
assert_eq!(large.policies.len(), 1);
assert!(shared(&large, 1, 10_000));
assert_eq!(Arc::strong_count(&large.records[&access(1)].policy), 10_000);
}