use blindplane_access::{
AccessError, AuditContext, AuditEvent, AuditEventKind, RoleKeypair, open_audit_event,
seal_audit_event,
};
use blindplane_core::{Author, fastest_payload_suite};
fn role(id: &str, seed: u8) -> RoleKeypair {
RoleKeypair::from_secret_bytes("acme", "audit", id, 1, [seed; 32]).unwrap()
}
fn event(subject_id: &str, session_id: &str, body: &[u8]) -> AuditEvent {
AuditEvent {
tenant_id: "acme".into(),
subject_id: subject_id.into(),
session_id: session_id.into(),
sequence: 1,
timestamp: 1_786_406_400,
kind: AuditEventKind::Request,
media_type: "application/json".into(),
body: body.to_vec(),
}
}
fn context(subject_id: &str, session_id: &str) -> AuditContext {
AuditContext {
tenant_id: "acme".into(),
subject_id: subject_id.into(),
stream_id: session_id.into(),
epoch: 1,
sequence: 1,
}
}
#[test]
fn each_user_opens_only_their_events_while_admin_opens_both() {
let author = Author::from_secret_bytes(&[29; 32]);
let alice_role = role("alice-audit", 31);
let bob_role = role("bob-audit", 41);
let admin_role = role("tenant-admin-audit", 51);
let alice_event = event(
"alice",
"session-alice",
br#"{"prompt":"secret alice request"}"#,
);
let bob_event = event("bob", "session-bob", br#"{"prompt":"secret bob request"}"#);
let alice_record = seal_audit_event(
&author,
context("alice", "session-alice"),
&alice_event,
&alice_role,
&admin_role,
fastest_payload_suite(),
)
.unwrap();
let bob_record = seal_audit_event(
&author,
context("bob", "session-bob"),
&bob_event,
&bob_role,
&admin_role,
fastest_payload_suite(),
)
.unwrap();
assert!(
!alice_record
.encode()
.windows(alice_event.body.len())
.any(|window| window == alice_event.body)
);
assert!(
!bob_record
.encode()
.windows(bob_event.body.len())
.any(|window| window == bob_event.body)
);
assert_eq!(
open_audit_event(&alice_record, &alice_role, author.public_key()).unwrap(),
alice_event
);
assert_eq!(
open_audit_event(&bob_record, &bob_role, author.public_key()).unwrap(),
bob_event
);
assert_eq!(
open_audit_event(&alice_record, &admin_role, author.public_key()).unwrap(),
alice_event
);
assert_eq!(
open_audit_event(&bob_record, &admin_role, author.public_key()).unwrap(),
bob_event
);
assert_eq!(
open_audit_event(&alice_record, &bob_role, author.public_key()),
Err(AccessError::CryptographicFailure)
);
}
#[test]
fn audit_context_must_match_the_encrypted_event() {
let author = Author::from_secret_bytes(&[29; 32]);
let alice_role = role("alice-audit", 31);
let admin_role = role("tenant-admin-audit", 51);
let alice_event = event("alice", "session-alice", b"request");
let mismatched = context("bob", "session-alice");
assert_eq!(
seal_audit_event(
&author,
mismatched,
&alice_event,
&alice_role,
&admin_role,
fastest_payload_suite(),
),
Err(AccessError::SubjectMismatch)
);
}
#[test]
fn audit_event_encoding_is_bounded_and_canonical() {
let event = event("alice", "session-alice", b"request");
let encoded = event.encode();
assert_eq!(AuditEvent::decode(&encoded).unwrap(), event);
let mut trailing = encoded;
trailing.push(0);
assert_eq!(
AuditEvent::decode(&trailing),
Err(AccessError::TrailingBytes)
);
}