#![allow(dead_code)]
use std::sync::{Arc, Mutex, OnceLock};
use tracing::{Event, Subscriber};
use tracing_subscriber::{
Layer, Registry,
layer::{Context, SubscriberExt},
};
use typesec_core::{PolicyEngine, RequestContext, ResourceId, SubjectId, policy::PolicyResult};
pub fn check_engine<E: PolicyEngine + ?Sized>(
engine: &E,
subject: &str,
action: &str,
resource: &str,
) -> PolicyResult {
engine.check(
&SubjectId::from(subject),
action,
&ResourceId::from(resource),
)
}
pub fn check_engine_with_context<E: PolicyEngine + ?Sized>(
engine: &E,
subject: &str,
action: &str,
resource: &str,
context: &RequestContext,
) -> PolicyResult {
engine.check_with_context(
&SubjectId::from(subject),
action,
&ResourceId::from(resource),
context,
)
}
pub const RBAC_ANALYST: &str = r#"
roles:
- name: analyst
permissions: [read]
resources: ["reports/*"]
assignments:
- subject: "agent:analyst"
roles: [analyst]
"#;
pub const RBAC_WRITER_ONLY: &str = r#"
roles:
- name: writer
permissions: [write]
resources: ["data/*"]
assignments:
- subject: "agent:writer"
roles: [writer]
"#;
pub fn odrl_with_expiry(expiry_date: &str) -> String {
format!(
r#"
policies:
- uid: "policy:timed-read"
type: Set
rules:
- type: permission
assigner: "org:acme"
assignee: "agent:reader"
action: read
target: "reports/q1"
constraints:
- leftOperand: dateTime
operator: lt
rightOperand: "{expiry_date}"
"#
)
}
pub const ODRL_PURPOSE: &str = r#"
policies:
- uid: "policy:purpose-read"
type: Set
rules:
- type: permission
assigner: "org:acme"
assignee: "agent:analyst"
action: read
target: "reports/q1"
constraints:
- leftOperand: purpose
operator: eq
rightOperand: "analytics"
"#;
pub const RBAC_ALLOW_READ: &str = r#"
roles:
- name: reader
permissions: [read]
resources: ["shared/*"]
assignments:
- subject: "agent:combinator"
roles: [reader]
"#;
pub const ODRL_PROHIBIT_READ: &str = r#"
policies:
- uid: "policy:prohibit-read"
type: Set
rules:
- type: prohibition
assignee: "agent:combinator"
action: read
target: "shared/data"
"#;
pub const ODRL_PERMIT_READ: &str = r#"
policies:
- uid: "policy:odrl-permit"
type: Set
rules:
- type: permission
assigner: "org:acme"
assignee: "agent:odrl-only"
action: read
target: "private/data"
"#;
pub const ODRL_ALLOW_SHARED_READ: &str = r#"
policies:
- uid: "policy:shared-permit"
type: Set
rules:
- type: permission
assigner: "org:acme"
assignee: "agent:combinator"
action: read
target: "shared/data"
"#;
pub const ODRL_PROHIBIT_TRAINING_READ: &str = r#"
policies:
- uid: "policy:training-prohibit"
type: Set
rules:
- type: prohibition
assignee: "agent:combinator"
action: read
target: "shared/data"
constraints:
- leftOperand: purpose
operator: eq
rightOperand: "training"
"#;
pub const RBAC_NO_RULES: &str = r#"
roles: []
assignments: []
"#;
pub type EventRecord = String;
#[derive(Default, Clone)]
pub struct AuditCapture(Arc<Mutex<Vec<EventRecord>>>);
impl AuditCapture {
pub fn new() -> Self {
Self::default()
}
pub fn records(&self) -> Vec<EventRecord> {
self.0.lock().unwrap().clone()
}
pub fn has_field(&self, key: &str, value: &str) -> bool {
let target = format!("{key}={value}");
self.records().iter().any(|r| r.contains(&target))
}
}
pub struct CaptureLayer(pub AuditCapture);
impl<S: Subscriber> Layer<S> for CaptureLayer {
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
struct Visitor(Vec<(String, String)>);
impl tracing::field::Visit for Visitor {
fn record_debug(&mut self, f: &tracing::field::Field, v: &dyn std::fmt::Debug) {
self.0.push((f.name().to_owned(), format!("{v:?}")));
}
fn record_str(&mut self, f: &tracing::field::Field, v: &str) {
self.0.push((f.name().to_owned(), v.to_owned()));
}
}
let mut vis = Visitor(vec![]);
event.record(&mut vis);
let record = vis
.0
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(";");
self.0.0.lock().unwrap().push(record);
}
}
pub fn install_capture_subscriber() -> AuditCapture {
static CAPTURE: OnceLock<AuditCapture> = OnceLock::new();
let capture = CAPTURE
.get_or_init(|| {
let capture = AuditCapture::new();
let layer = CaptureLayer(capture.clone());
let subscriber = Registry::default().with(layer);
let _ = tracing::subscriber::set_global_default(subscriber);
tracing::callsite::rebuild_interest_cache();
capture
})
.clone();
capture.0.lock().unwrap().clear();
capture
}