1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Roles and permissions — the authorization half of the auth starter kit.
//!
//! Modelled on `laravel-permission`, which is the package Laravel developers
//! actually reach for, and shaped so the same mental model transfers: roles are
//! named bundles of permissions, users hold roles, and a user can also be given
//! — or refused — a single permission on their own.
//!
//! ```ignore
//! // main.rs
//! App::new()
//! .plugin(Rbac::from_config(db.clone(), &config)?)
//! .routes(|r| {
//! r.group("/admin", |r| {
//! r.middleware(Authenticate::from_config(&config));
//! r.middleware(Can::permission("admin.access"));
//! r.get("", dashboard);
//! });
//! });
//! ```
//!
//! # The model
//!
//! Five tables. `roles` and `permissions` are lists of names; `role_permission`
//! attaches a permission to a role; `user_role` gives a user a role; and
//! `user_permission` gives — or refuses — a permission to one user directly,
//! which is what its `granted` boolean is for.
//!
//! Two ways to hold a permission, then: through a role, or in your own name.
//!
//! # Precedence
//!
//! The question this package exists to answer is "why can this user still do
//! X?", so the answer is one short list, applied in order:
//!
//! 1. **An explicit direct deny beats everything**, including a super role.
//! 2. A **super role** passes, with no permissions attached.
//! 3. A **grant** passes — from a role or made directly; the two rank equally,
//! so a direct grant beats the *absence* of a role grant.
//! 4. Otherwise, no. Silence is never permission.
//!
//! The one that surprises people is the first: a deny outranks `super-admin`.
//! It has to. A deny is the only entry an administrator writes in order to say
//! "no, not this one", and a rule that some role can quietly overrule is not a
//! rule. The trade is that a super role is not unstoppable — which is a
//! property worth having, not a hole.
//!
//! See [`Grants::allows`] for the implementation and the tests that pin it.
//!
//! # Wildcards
//!
//! A stored permission may end in `.*`, and `*` on its own matches everything.
//! So a role holding `users.*` satisfies a check for `users.create`. The
//! matching is a segment-wise prefix comparison and nothing more — no regular
//! expressions, no glob syntax, no `*` in the middle. An authorization rule
//! whose meaning cannot be worked out by reading it is worse than one that
//! cannot express every case. [`permission_matches`] has the details.
//!
//! # The super role
//!
//! A role named `super-admin` (configure it with `rbac.super_role`) passes
//! every check without holding a single permission. It is what makes the first
//! user of a fresh application able to do anything, and it should be treated
//! with suspicion after that.
//!
//! **The risk, plainly: a super role cannot be audited by listing its
//! permissions, because it has none.** Reading `role_permissions("super-admin")`
//! returns an empty list, and that empty list is not what the role can do — it
//! is the opposite. Nothing shows up in `permissions_for(user)` either. If your
//! compliance story is "here is what each role may do", a super role is a hole
//! in it, and the fix is to give the role real permissions (`*` is one) and
//! turn the escape hatch off with `.super_role("")`.
//!
//! # Caching
//!
//! An authorization check happens on every guarded request, so a check that
//! queries the database every time is the slowest thing in the application. A
//! user's resolved grants are cached in memory for 30 seconds
//! (`rbac.cache_ttl_ms`), and every method that changes what a user is allowed
//! invalidates it immediately — an administrator removing a role sees it take
//! effect on the next request, not in half a minute. The TTL is only the
//! backstop for a change made by *another* process; it is not the mechanism.
//!
//! The cache lives in the [`Permissions`] handle, and cloning one shares it.
//! Register it once, in application state.
pub use ;
pub use ;
pub use Rbac;
pub use ;
pub use ;
pub use ;
/// Everything an application usually wants: `use rustlavel_rbac::prelude::*;`