actix_web_grants/
middleware.rs

1use crate::permissions::AttachPermissions;
2use crate::permissions::PermissionsExtractor;
3use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
4use actix_web::Error;
5use std::future::{self, Future, Ready};
6use std::marker::PhantomData;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::task::{Context, Poll};
10
11/// Built-in middleware for extracting user permission.
12///
13///
14/// # Examples
15/// ```
16/// use actix_web::dev::ServiceRequest;
17/// use actix_web::{get, App, Error, HttpResponse, HttpServer, Responder};
18///
19/// use actix_web_grants::permissions::{AuthDetails, PermissionsCheck};
20/// use actix_web_grants::{proc_macro::has_permissions, GrantsMiddleware};
21///
22/// fn main() {
23///     HttpServer::new(|| {
24///         let auth = GrantsMiddleware::with_extractor(extract);
25///         App::new()
26///             .wrap(auth)
27///             .service(you_service)
28///     });
29/// }
30///
31/// // You can use both &ServiceRequest and &mut ServiceRequest
32/// // Futhermore, you can use you own type instead of `String` (e.g. Enum).
33/// async fn extract(_req: &ServiceRequest) -> Result<Vec<String>, Error> {
34///    // Here is a place for your code to get user permissions/grants/permissions from a request
35///    // For example from a token or database
36///
37///    // Stub example
38///    Ok(vec!["ROLE_ADMIN".to_string()])
39/// }
40///
41/// // `has_permissions` is one of options to validate permissions.
42/// // `proc-macro` crate has additional features, like ABAC security and custom types. See examples and `proc-macro` crate docs.
43/// #[get("/admin")]
44/// #[has_permissions("ROLE_ADMIN")]
45/// async fn you_service() -> impl Responder {
46///     HttpResponse::Ok().finish()
47/// }
48/// ```
49pub struct GrantsMiddleware<E, Req, Type>
50where
51    for<'a> E: PermissionsExtractor<'a, Req, Type>,
52    Type: PartialEq + Clone + 'static,
53{
54    extractor: Rc<E>,
55    phantom_req: PhantomData<Req>,
56    phantom_type: PhantomData<Type>,
57}
58
59impl<E, Req, Type> GrantsMiddleware<E, Req, Type>
60where
61    for<'a> E: PermissionsExtractor<'a, Req, Type>,
62    Type: PartialEq + Clone + 'static,
63{
64    /// Create middleware by [`PermissionsExtractor`].
65    ///
66    /// You can use a built-in implementation for `async fn` with a suitable signature (see example below).
67    /// Or you can define your own implementation of trait.
68    ///
69    /// # Example of function with implementation of [`PermissionsExtractor`]
70    /// ```
71    /// use actix_web::dev::ServiceRequest;
72    /// use actix_web::Error;
73    ///
74    /// async fn extract(_req: &ServiceRequest) -> Result<Vec<String>, Error> {
75    ///     // Here is a place for your code to get user permissions/grants/permissions from a request
76    ///      // For example from a token or database
77    ///     Ok(vec!["WRITE_ACCESS".to_string()])
78    /// }
79    ///
80    /// // Or with you own type:
81    /// #[derive(PartialEq, Clone)] // required bounds
82    /// enum Permission { WRITE, READ }
83    /// async fn extract_enum(_req: &ServiceRequest) -> Result<Vec<Permission>, Error> {
84    ///     // Here is a place for your code to get user permissions/grants/permissions from a request
85    ///      // For example from a token, database or external service
86    ///     Ok(vec![Permission::WRITE])
87    /// }
88    /// ```
89    ///
90    ///[`PermissionsExtractor`]: crate::permissions::PermissionsExtractor
91    pub fn with_extractor(extractor: E) -> GrantsMiddleware<E, Req, Type> {
92        GrantsMiddleware {
93            extractor: Rc::new(extractor),
94            phantom_req: PhantomData,
95            phantom_type: PhantomData,
96        }
97    }
98}
99
100impl<S, B, E, Req, Type> Transform<S, ServiceRequest> for GrantsMiddleware<E, Req, Type>
101where
102    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
103    for<'a> E: PermissionsExtractor<'a, Req, Type> + 'static,
104    Type: PartialEq + Clone + 'static,
105{
106    type Response = ServiceResponse<B>;
107    type Error = Error;
108    type Transform = GrantsService<S, E, Req, Type>;
109    type InitError = ();
110    type Future = Ready<Result<Self::Transform, Self::InitError>>;
111
112    fn new_transform(&self, service: S) -> Self::Future {
113        future::ready(Ok(GrantsService {
114            service: Rc::new(service),
115            extractor: self.extractor.clone(),
116            phantom_req: PhantomData,
117            phantom_type: PhantomData,
118        }))
119    }
120}
121
122pub struct GrantsService<S, E, Req, Type>
123where
124    for<'a> E: PermissionsExtractor<'a, Req, Type> + 'static,
125{
126    service: Rc<S>,
127    extractor: Rc<E>,
128    phantom_req: PhantomData<Req>,
129    phantom_type: PhantomData<Type>,
130}
131
132impl<S, B, E, Req, Type> Service<ServiceRequest> for GrantsService<S, E, Req, Type>
133where
134    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
135    for<'a> E: PermissionsExtractor<'a, Req, Type>,
136    Type: PartialEq + Clone + 'static,
137{
138    type Response = ServiceResponse<B>;
139    type Error = Error;
140    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Error>>>>;
141
142    fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
143        self.service.poll_ready(cx)
144    }
145
146    fn call(&self, mut req: ServiceRequest) -> Self::Future {
147        let service = Rc::clone(&self.service);
148        let extractor = Rc::clone(&self.extractor);
149
150        Box::pin(async move {
151            let permissions: Vec<Type> = extractor.extract(&mut req).await?;
152            req.attach(permissions);
153
154            service.call(req).await
155        })
156    }
157}