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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
use crate::permissions::AttachPermissions;
use crate::permissions::PermissionsExtractor;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use std::future::{self, Future, Ready};
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/// Built-in middleware for extracting user permission.
///
///
/// # Examples
/// ```
/// use actix_web::dev::ServiceRequest;
/// use actix_web::{get, App, Error, HttpResponse, HttpServer, Responder};
///
/// use actix_web_grants::permissions::{AuthDetails, PermissionsCheck};
/// use actix_web_grants::{proc_macro::has_permissions, GrantsMiddleware};
///
/// fn main() {
/// HttpServer::new(|| {
/// let auth = GrantsMiddleware::with_extractor(extract);
/// App::new()
/// .wrap(auth)
/// .service(you_service)
/// });
/// }
///
/// // You can use both &ServiceRequest and &mut ServiceRequest
/// // Futhermore, you can use you own type instead of `String` (e.g. Enum).
/// async fn extract(_req: &ServiceRequest) -> Result<Vec<String>, Error> {
/// // Here is a place for your code to get user permissions/grants/permissions from a request
/// // For example from a token or database
///
/// // Stub example
/// Ok(vec!["ROLE_ADMIN".to_string()])
/// }
///
/// // `has_permissions` is one of options to validate permissions.
/// // `proc-macro` crate has additional features, like ABAC security and custom types. See examples and `proc-macro` crate docs.
/// #[get("/admin")]
/// #[has_permissions("ROLE_ADMIN")]
/// async fn you_service() -> impl Responder {
/// HttpResponse::Ok().finish()
/// }
/// ```
pub struct GrantsMiddleware<E, Req, Type>
where
for<'a> E: PermissionsExtractor<'a, Req, Type>,
Type: PartialEq + Clone + 'static,
{
extractor: Rc<E>,
phantom_req: PhantomData<Req>,
phantom_type: PhantomData<Type>,
}
impl<E, Req, Type> GrantsMiddleware<E, Req, Type>
where
for<'a> E: PermissionsExtractor<'a, Req, Type>,
Type: PartialEq + Clone + 'static,
{
/// Create middleware by [`PermissionsExtractor`].
///
/// You can use a built-in implementation for `async fn` with a suitable signature (see example below).
/// Or you can define your own implementation of trait.
///
/// # Example of function with implementation of [`PermissionsExtractor`]
/// ```
/// use actix_web::dev::ServiceRequest;
/// use actix_web::Error;
///
/// async fn extract(_req: &ServiceRequest) -> Result<Vec<String>, Error> {
/// // Here is a place for your code to get user permissions/grants/permissions from a request
/// // For example from a token or database
/// Ok(vec!["WRITE_ACCESS".to_string()])
/// }
///
/// // Or with you own type:
/// #[derive(PartialEq, Clone)] // required bounds
/// enum Permission { WRITE, READ }
/// async fn extract_enum(_req: &ServiceRequest) -> Result<Vec<Permission>, Error> {
/// // Here is a place for your code to get user permissions/grants/permissions from a request
/// // For example from a token, database or external service
/// Ok(vec![Permission::WRITE])
/// }
/// ```
///
///[`PermissionsExtractor`]: crate::permissions::PermissionsExtractor
pub fn with_extractor(extractor: E) -> GrantsMiddleware<E, Req, Type> {
GrantsMiddleware {
extractor: Rc::new(extractor),
phantom_req: PhantomData,
phantom_type: PhantomData,
}
}
}
impl<S, B, E, Req, Type> Transform<S, ServiceRequest> for GrantsMiddleware<E, Req, Type>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
for<'a> E: PermissionsExtractor<'a, Req, Type> + 'static,
Type: PartialEq + Clone + 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = GrantsService<S, E, Req, Type>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
future::ready(Ok(GrantsService {
service: Rc::new(service),
extractor: self.extractor.clone(),
phantom_req: PhantomData,
phantom_type: PhantomData,
}))
}
}
pub struct GrantsService<S, E, Req, Type>
where
for<'a> E: PermissionsExtractor<'a, Req, Type> + 'static,
{
service: Rc<S>,
extractor: Rc<E>,
phantom_req: PhantomData<Req>,
phantom_type: PhantomData<Type>,
}
impl<S, B, E, Req, Type> Service<ServiceRequest> for GrantsService<S, E, Req, Type>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
for<'a> E: PermissionsExtractor<'a, Req, Type>,
Type: PartialEq + Clone + 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Error>>>>;
fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
let extractor = Rc::clone(&self.extractor);
Box::pin(async move {
let permissions: Vec<Type> = extractor.extract(&mut req).await?;
req.attach(permissions);
service.call(req).await
})
}
}