Role-Based Access Control (RBAC)

Traditional RBAC with quantitative access decisions.

Source: examples/rbac.rs

Setup

use schubert::{AccessController, Capability, CapabilityKind, AccessDecision};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut acl = AccessController::new(2, 4)?;

Capabilities

#![allow(unused)]
fn main() {
    acl.register_capability(Capability::new(
        "read", "Read access", vec![1], CapabilityKind::ReadLike,
    ))?;
    acl.register_capability(Capability::new(
        "write", "Write access", vec![2], CapabilityKind::WriteLike,
    ))?;
    acl.register_capability(Capability::new(
        "admin", "Admin access", vec![2, 1], CapabilityKind::AdminLike,
    ))?;
}

Principals and Grants

#![allow(unused)]
fn main() {
    let alice = acl.create_principal("alice")?;
    acl.grant(&alice, "read")?;
    acl.grant(&alice, "write")?;

    let bob = acl.create_principal("bob")?;
    acl.grant(&bob, "read")?;
}

Access Checks

#![allow(unused)]
fn main() {
    // Alice can read and write
    match acl.check(&alice, &["read", "write"])? {
        AccessDecision::Granted { configurations } => {
            println!("Alice: granted with {configurations} configurations");
        }
        _ => unreachable!(),
    }

    // Bob cannot write
    match acl.check(&bob, &["read", "write"])? {
        AccessDecision::Granted { .. } => unreachable!(),
        _ => println!("Bob: cannot read and write"),
    }

    Ok(())
}
}

Key Takeaway

RBAC is the simplest pattern — define roles as capability sets, grant them to principals, and check. The quantitative nature of Schubert shines when roles overlap or conflict.