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}