Skip to main content

enterprise_access/
enterprise_access.rs

1//! User-scoped access with tenant-administrator visibility.
2
3use blindplane_access::{
4    AccessGrant, AccessIssuer, AuditContext, AuditEvent, AuditEventKind, CapabilityKind,
5    CapabilityRule, Decision, Effect, GrantSpec, Permissions, PolicySpec, PrincipalKeypair,
6    PrincipalKind, RevocationSpec, RevocationState, RoleKeypair, TenantPolicy, open_audit_event,
7    seal_audit_event,
8};
9use blindplane_core::{Author, fastest_payload_suite};
10
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12    let issuer = AccessIssuer::generate("acme-admin")?;
13    let alice = PrincipalKeypair::generate("acme", "alice", PrincipalKind::User, 1)?;
14    let admin = PrincipalKeypair::generate("acme", "admin", PrincipalKind::Administrator, 1)?;
15
16    let alice_audit = RoleKeypair::generate("acme", "audit", "alice-audit", 1)?;
17    let tenant_audit = RoleKeypair::generate("acme", "audit", "tenant-audit", 1)?;
18
19    let alice_grant = AccessGrant::issue(
20        &issuer,
21        alice.principal(),
22        &alice_audit,
23        GrantSpec {
24            grant_id: "alice-audit-v1".into(),
25            scope: "audit".into(),
26            permissions: Permissions::READ | Permissions::WRITE,
27            authorization_epoch: 1,
28            not_before: 1,
29            not_after: u64::MAX,
30        },
31    )?;
32    let admin_grant = AccessGrant::issue(
33        &issuer,
34        admin.principal(),
35        &tenant_audit,
36        GrantSpec {
37            grant_id: "admin-audit-v1".into(),
38            scope: "audit".into(),
39            permissions: Permissions::READ | Permissions::OBSERVE,
40            authorization_epoch: 1,
41            not_before: 1,
42            not_after: u64::MAX,
43        },
44    )?;
45
46    let alice_state = RevocationState::issue(
47        &issuer,
48        "acme",
49        "alice",
50        "audit",
51        RevocationSpec {
52            revision: 1,
53            minimum_authorization_epoch: 1,
54            minimum_role_key_epoch: 1,
55            issued_at: 1,
56        },
57    )?;
58    let admin_state = RevocationState::issue(
59        &issuer,
60        "acme",
61        "admin",
62        "audit",
63        RevocationSpec {
64            revision: 1,
65            minimum_authorization_epoch: 1,
66            minimum_role_key_epoch: 1,
67            issued_at: 1,
68        },
69    )?;
70    let trusted = issuer.trusted();
71    let alice_role = alice_grant.open_role(
72        &alice,
73        &trusted,
74        100,
75        &alice_state.verify(&trusted, "acme", "alice", "audit")?,
76    )?;
77    let admin_role = admin_grant.open_role(
78        &admin,
79        &trusted,
80        100,
81        &admin_state.verify(&trusted, "acme", "admin", "audit")?,
82    )?;
83
84    let policy = TenantPolicy::issue(
85        &issuer,
86        alice.principal(),
87        PolicySpec {
88            revision: 1,
89            previous_hash: [0; 32],
90            authorization_epoch: 1,
91            issued_at: 1,
92            not_before: 1,
93            not_after: u64::MAX,
94            rules: vec![CapabilityRule::new(
95                CapabilityKind::Mcp,
96                "github",
97                Effect::Allow,
98            )?],
99        },
100    )?;
101    let verified = policy.verify(&trusted, "acme", "alice", 100, 1, 1)?;
102    assert_eq!(
103        verified.decision(CapabilityKind::Mcp, "github"),
104        Decision::Allow
105    );
106    assert_eq!(
107        verified.decision(CapabilityKind::Skill, "deploy"),
108        Decision::Deny
109    );
110
111    let author = Author::generate()?;
112    let event = AuditEvent {
113        tenant_id: "acme".into(),
114        subject_id: "alice".into(),
115        session_id: "session-42".into(),
116        sequence: 1,
117        timestamp: 100,
118        kind: AuditEventKind::Request,
119        media_type: "application/json".into(),
120        body: br#"{"tool":"github.search","query":"private"}"#.to_vec(),
121    };
122    let record = seal_audit_event(
123        &author,
124        AuditContext {
125            tenant_id: "acme".into(),
126            subject_id: "alice".into(),
127            stream_id: "session-42".into(),
128            epoch: 1,
129            sequence: 1,
130        },
131        &event,
132        &alice_role,
133        &admin_role,
134        fastest_payload_suite(),
135    )?;
136
137    assert_eq!(
138        open_audit_event(&record, &alice_role, author.public_key())?,
139        event
140    );
141    assert_eq!(
142        open_audit_event(&record, &admin_role, author.public_key())?,
143        event
144    );
145    println!("user and tenant administrator opened the encrypted audit event");
146    Ok(())
147}