1use std::{convert::Infallible, future::Future, marker::PhantomData, pin::Pin};
24
25use axum::{
26 Router,
27 extract::Request,
28 http::StatusCode,
29 response::{IntoResponse, Response},
30 routing::MethodRouter,
31};
32use tower::{Layer, Service};
33
34use crate::session::Session;
35
36pub trait Policy<U>: Send + Sync + 'static + Clone {
41 type Error: IntoResponse + Send;
43
44 fn evaluate(&self, user: &U) -> impl Future<Output = Result<bool, Self::Error>> + Send;
46}
47
48impl<U, F> Policy<U> for F
49where
50 F: Fn(&U) -> bool + Send + Sync + 'static + Clone,
51{
52 type Error = Infallible;
53
54 fn evaluate(&self, user: &U) -> impl Future<Output = Result<bool, Self::Error>> + Send {
55 let result = (self)(user);
56 async move { Ok(result) }
57 }
58}
59
60#[derive(Clone)]
62pub struct AllOf<P1, P2>(pub P1, pub P2);
63
64impl<U, P1, P2> Policy<U> for AllOf<P1, P2>
65where
66 U: Send + Sync + 'static,
67 P1: Policy<U>,
68 P2: Policy<U>,
69{
70 type Error = Response;
71
72 async fn evaluate(&self, user: &U) -> Result<bool, Self::Error> {
73 let a = self
74 .0
75 .evaluate(user)
76 .await
77 .map_err(IntoResponse::into_response)?;
78 if !a {
79 return Ok(false);
80 }
81 self.1
82 .evaluate(user)
83 .await
84 .map_err(IntoResponse::into_response)
85 }
86}
87
88#[derive(Clone)]
90pub struct AnyOf<P1, P2>(pub P1, pub P2);
91
92impl<U, P1, P2> Policy<U> for AnyOf<P1, P2>
93where
94 U: Send + Sync + 'static,
95 P1: Policy<U>,
96 P2: Policy<U>,
97{
98 type Error = Response;
99
100 async fn evaluate(&self, user: &U) -> Result<bool, Self::Error> {
101 let a = self
102 .0
103 .evaluate(user)
104 .await
105 .map_err(IntoResponse::into_response)?;
106 if a {
107 return Ok(true);
108 }
109 self.1
110 .evaluate(user)
111 .await
112 .map_err(IntoResponse::into_response)
113 }
114}
115
116#[derive(Clone)]
118pub struct Not<P>(pub P);
119
120impl<U, P> Policy<U> for Not<P>
121where
122 U: Send + Sync + 'static,
123 P: Policy<U>,
124{
125 type Error = P::Error;
126
127 async fn evaluate(&self, user: &U) -> Result<bool, Self::Error> {
128 Ok(!self.0.evaluate(user).await?)
129 }
130}
131
132pub trait PolicyExt<U>: Policy<U> + Sized {
136 fn and<P: Policy<U>>(self, other: P) -> AllOf<Self, P> {
138 AllOf(self, other)
139 }
140
141 fn or<P: Policy<U>>(self, other: P) -> AnyOf<Self, P> {
143 AnyOf(self, other)
144 }
145
146 fn not(self) -> Not<Self> {
148 Not(self)
149 }
150}
151
152impl<U, P: Policy<U>> PolicyExt<U> for P {}
153
154#[derive(Clone, Copy)]
156pub struct Allow;
157
158impl<U: Send + Sync + 'static> Policy<U> for Allow {
159 type Error = Infallible;
160
161 async fn evaluate(&self, _user: &U) -> Result<bool, Self::Error> {
162 Ok(true)
163 }
164}
165
166#[derive(Clone, Copy)]
168pub struct Deny;
169
170impl<U: Send + Sync + 'static> Policy<U> for Deny {
171 type Error = Infallible;
172
173 async fn evaluate(&self, _user: &U) -> Result<bool, Self::Error> {
174 Ok(false)
175 }
176}
177
178#[cfg(feature = "rbac")]
182pub struct HasRole<R: crate::rbac::RBAC> {
183 role: R,
184}
185
186#[cfg(feature = "rbac")]
187impl<R: crate::rbac::RBAC> Clone for HasRole<R> {
188 fn clone(&self) -> Self {
189 HasRole { role: self.role }
190 }
191}
192
193#[cfg(feature = "rbac")]
194impl<R: crate::rbac::RBAC> HasRole<R> {
195 pub fn new(role: R) -> Self {
196 HasRole { role }
197 }
198}
199
200#[cfg(feature = "rbac")]
201impl<R: crate::rbac::RBAC> Policy<R::Resource> for HasRole<R> {
202 type Error = Infallible;
203
204 async fn evaluate(&self, user: &R::Resource) -> Result<bool, Self::Error> {
205 Ok(R::extract_roles(user).into_iter().any(|r| *r == self.role))
206 }
207}
208
209struct PolicyLayer<P, U> {
210 policy: P,
211 _marker: PhantomData<fn() -> U>,
212}
213
214impl<P: Clone, U> Clone for PolicyLayer<P, U> {
215 fn clone(&self) -> Self {
216 PolicyLayer {
217 policy: self.policy.clone(),
218 _marker: PhantomData,
219 }
220 }
221}
222
223impl<P, S, U> Layer<S> for PolicyLayer<P, U>
224where
225 P: Clone,
226{
227 type Service = PolicyService<P, S, U>;
228
229 fn layer(&self, inner: S) -> PolicyService<P, S, U> {
230 PolicyService {
231 policy: self.policy.clone(),
232 inner,
233 _marker: PhantomData,
234 }
235 }
236}
237
238pub struct PolicyService<P, S, U> {
240 policy: P,
241 inner: S,
242 _marker: PhantomData<fn() -> U>,
243}
244
245impl<P: Clone, S: Clone, U> Clone for PolicyService<P, S, U> {
246 fn clone(&self) -> Self {
247 PolicyService {
248 policy: self.policy.clone(),
249 inner: self.inner.clone(),
250 _marker: PhantomData,
251 }
252 }
253}
254
255type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
256
257impl<P, S, U> Service<Request> for PolicyService<P, S, U>
258where
259 P: Policy<U> + 'static,
260 U: Clone + Send + Sync + 'static,
261 S: Service<Request, Response = Response> + Clone + Send + 'static,
262 S::Error: Send,
263 S::Future: Send,
264{
265 type Response = Response;
266 type Error = S::Error;
267 type Future = BoxFuture<Result<Response, S::Error>>;
268
269 fn poll_ready(
270 &mut self,
271 cx: &mut std::task::Context<'_>,
272 ) -> std::task::Poll<Result<(), Self::Error>> {
273 self.inner.poll_ready(cx)
274 }
275
276 fn call(&mut self, mut req: Request) -> Self::Future {
277 let policy = self.policy.clone();
278 let mut inner = self.inner.clone();
279 Box::pin(async move {
280 crate::debug!("evaluating policy");
281
282 let Some(session) = Session::<U>::from_extensions(req.extensions_mut()) else {
283 crate::debug!("no session found, returning 401");
284 return Ok(StatusCode::UNAUTHORIZED.into_response());
285 };
286
287 match policy.evaluate(&session).await {
288 Ok(true) => {
289 session.insert_into(req.extensions_mut());
290 inner.call(req).await
291 }
292 Ok(false) => {
293 crate::debug!("policy denied access, returning 403");
294 session.insert_into(req.extensions_mut());
295 Ok(StatusCode::FORBIDDEN.into_response())
296 }
297 Err(e) => {
298 crate::debug!("policy returned error");
299 session.insert_into(req.extensions_mut());
300 Ok(e.into_response())
301 }
302 }
303 })
304 }
305}
306
307pub trait PolicyRouterExt {
309 fn with_policy<P, U>(self, policy: P) -> Self
311 where
312 P: Policy<U> + 'static,
313 U: Clone + Send + Sync + 'static;
314}
315
316impl<S: Clone + 'static> PolicyRouterExt for MethodRouter<S, Infallible> {
317 fn with_policy<P, U>(self, policy: P) -> Self
318 where
319 P: Policy<U> + 'static,
320 U: Clone + Send + Sync + 'static,
321 {
322 self.layer(PolicyLayer {
323 policy,
324 _marker: PhantomData,
325 })
326 }
327}
328
329impl<S: Clone + Send + Sync + 'static> PolicyRouterExt for Router<S> {
330 fn with_policy<P, U>(self, policy: P) -> Self
331 where
332 P: Policy<U> + 'static,
333 U: Clone + Send + Sync + 'static,
334 {
335 self.layer(PolicyLayer {
336 policy,
337 _marker: PhantomData,
338 })
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[derive(Clone)]
347 struct User {
348 admin: bool,
349 banned: bool,
350 }
351
352 fn is_admin(u: &User) -> bool {
353 u.admin
354 }
355
356 fn is_not_banned(u: &User) -> bool {
357 !u.banned
358 }
359
360 #[tokio::test]
361 async fn closure_policy_passes() {
362 let user = User {
363 admin: true,
364 banned: false,
365 };
366 assert!(is_admin.evaluate(&user).await.unwrap());
367 }
368
369 #[tokio::test]
370 async fn closure_policy_fails() {
371 let user = User {
372 admin: false,
373 banned: false,
374 };
375 assert!(!is_admin.evaluate(&user).await.unwrap());
376 }
377
378 #[tokio::test]
379 async fn allow_always_passes() {
380 let user = User {
381 admin: false,
382 banned: true,
383 };
384 assert!(Allow.evaluate(&user).await.unwrap());
385 }
386
387 #[tokio::test]
388 async fn deny_always_fails() {
389 let user = User {
390 admin: true,
391 banned: false,
392 };
393 assert!(!Deny.evaluate(&user).await.unwrap());
394 }
395
396 #[tokio::test]
397 async fn not_combinator() {
398 let user = User {
399 admin: true,
400 banned: false,
401 };
402 let policy = is_admin.not();
403 assert!(!policy.evaluate(&user).await.unwrap());
404 }
405
406 #[tokio::test]
407 async fn all_of_both_pass() {
408 let user = User {
409 admin: true,
410 banned: false,
411 };
412 let policy = is_admin.and(is_not_banned);
413 assert!(policy.evaluate(&user).await.unwrap());
414 }
415
416 #[tokio::test]
417 async fn all_of_one_fails() {
418 let user = User {
419 admin: true,
420 banned: true,
421 };
422 let policy = is_admin.and(is_not_banned);
423 assert!(!policy.evaluate(&user).await.unwrap());
424 }
425
426 #[tokio::test]
427 async fn any_of_one_passes() {
428 let user = User {
429 admin: true,
430 banned: true,
431 };
432 let policy = is_admin.or(is_not_banned);
433 assert!(policy.evaluate(&user).await.unwrap());
434 }
435
436 #[tokio::test]
437 async fn any_of_none_pass() {
438 let user = User {
439 admin: false,
440 banned: true,
441 };
442 let policy = is_admin.or(is_not_banned);
443 assert!(!policy.evaluate(&user).await.unwrap());
444 }
445
446 #[tokio::test]
447 async fn chained_combinators() {
448 let user = User {
449 admin: true,
450 banned: false,
451 };
452 let policy = is_admin.and(is_not_banned);
453 assert!(policy.evaluate(&user).await.unwrap());
454
455 let policy2 = (is_admin as fn(&User) -> bool).not().or(is_not_banned);
456 assert!(policy2.evaluate(&user).await.unwrap());
457 }
458}