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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Testing helpers that let services assert authorization-coverage
//! invariants in their own CI. Intended to be consumed from test and
//! integration-test code in downstream services.
//!
//! The main entry point is [`assert_every_route_has_policy`] — hand it
//! your list of registered HTTP routes and a small set of fixture
//! subjects; it asserts that every (route, action, resource) triple
//! resolves to at least one `Decision::Allow` somewhere across the
//! fixtures. If a route denies for every fixture, that's almost always
//! because no policy was wired for it — a production-breaking
//! misconfiguration the helper surfaces in CI.
use crateAction;
use crateDecision;
use crateAuthorizer;
use crateResourceRef;
use crateSubject;
/// Describes a single HTTP route the service exposes and the
/// (action, resource) the authz layer should evaluate for it.
/// A subject fixture used during the coverage sweep.
/// A route that failed the coverage sweep — i.e. returned `Decision::Deny`
/// for every fixture subject. Downstream CI should treat this as a
/// likely missing-policy configuration bug.
/// Asserts every route in `routes` resolves to `Decision::Allow` for at
/// least one of the `fixtures`. Routes denied for all fixtures are
/// returned as [`UnmappedRoute`]s.
///
/// The check is inherently conservative — a route denied for every
/// fixture MIGHT have a genuine policy that requires a subject shape
/// you didn't model in the fixtures. To reduce false positives, pass
/// fixtures covering every role/tenant pair you expect in production.
///
/// # Errors
///
/// Returns a non-empty `Vec<UnmappedRoute>` when at least one route
/// failed. The vector is in the same order as `routes`.
///
/// # Examples
///
/// ```
/// use secure_authz::action::Action;
/// use secure_authz::resource::ResourceRef;
/// use secure_authz::subject::Subject;
/// use secure_authz::testkit::MockAuthorizer;
/// use secure_authz::testing::{
/// assert_every_route_has_policy, PolicyFixture, RouteDescriptor,
/// };
///
/// let authz = MockAuthorizer::allow();
/// let routes = vec![RouteDescriptor {
/// method: "GET".to_owned(),
/// path: "/items".to_owned(),
/// action: Action::Read,
/// resource: ResourceRef::new("item"),
/// }];
/// let fixtures = vec![PolicyFixture {
/// subject: Subject {
/// actor_id: "fixture".to_owned(),
/// tenant_id: None,
/// roles: smallvec::smallvec!["reader".to_owned()],
/// attributes: Default::default(),
/// },
/// }];
/// assert!(assert_every_route_has_policy(&authz, &routes, &fixtures).is_ok());
/// ```
Sized>