actix-web-grants 4.1.2

Authorization extension `actix-web` to protect your endpoints
Documentation
use crate::authorities::AttachAuthorities;
use crate::authorities::AuthoritiesExtractor;
use actix_web::body::EitherBody;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use std::future::{self, Future, Ready};
use std::hash::Hash;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};

/// Built-in middleware for extracting user authorities.
///
///
/// # Examples
/// ```
/// use actix_web::dev::ServiceRequest;
/// use actix_web::{get, App, Error, HttpResponse, HttpServer, Responder};
/// use std::collections::HashSet;
///
/// use actix_web_grants::authorities::{AuthDetails, AuthoritiesCheck};
/// use actix_web_grants::{protect, 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
/// // Furthermore, you can use you own type instead of `String` (e.g. Enum).
/// async fn extract(_req: &ServiceRequest) -> Result<HashSet<String>, Error> {
///    // Here is a place for your code to get user permissions/roles/authorities from a request
///    // For example from a token or database
///
///    // Stub example
///    Ok(HashSet::from(["ROLE_ADMIN".to_string()]))
/// }
///
/// // `proc-macro` crate has additional features, like ABAC security and custom types. See examples and `proc-macro` crate docs.
/// #[get("/admin")]
/// #[protect("ROLE_ADMIN")]
/// async fn you_service() -> impl Responder {
///     HttpResponse::Ok().finish()
/// }
/// ```
pub struct GrantsMiddleware<E, Req, Type>
where
    for<'a> E: AuthoritiesExtractor<'a, Req, Type>,
    Type: Eq + Hash + 'static,
{
    extractor: Rc<E>,
    phantom_req: PhantomData<Req>,
    phantom_type: PhantomData<Type>,
}

impl<E, Req, Type> GrantsMiddleware<E, Req, Type>
where
    for<'a> E: AuthoritiesExtractor<'a, Req, Type>,
    Type: Eq + Hash + 'static,
{
    /// Create middleware by [`AuthoritiesExtractor`].
    ///
    /// 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 [`AuthoritiesExtractor`]
    /// ```
    /// use actix_web::dev::ServiceRequest;
    /// use actix_web::Error;
    /// use std::collections::HashSet;
    ///
    /// async fn extract(_req: &ServiceRequest) -> Result<HashSet<String>, Error> {
    ///     // Here is a place for your code to get user permissions/roles/authorities from a request
    ///      // For example from a token or database
    ///     Ok(HashSet::from(["WRITE_ACCESS".to_string()]))
    /// }
    ///
    /// // Or with you own type:
    /// #[derive(Eq, PartialEq, Hash)] // required bounds
    /// enum Permission { WRITE, READ }
    /// async fn extract_enum(_req: &ServiceRequest) -> Result<HashSet<Permission>, Error> {
    ///     // Here is a place for your code to get user permissions/roles/authorities from a request
    ///      // For example from a token, database or external service
    ///     Ok(HashSet::from([Permission::WRITE]))
    /// }
    /// ```
    ///
    ///[`AuthoritiesExtractor`]: crate::authorities::AuthoritiesExtractor
    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: AuthoritiesExtractor<'a, Req, Type> + 'static,
    Type: Eq + Hash + 'static,
{
    type Response = ServiceResponse<EitherBody<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: AuthoritiesExtractor<'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: AuthoritiesExtractor<'a, Req, Type>,
    Type: Eq + Hash + 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = S::Error;
    type Future =
        Pin<Box<dyn Future<Output = Result<ServiceResponse<EitherBody<B>>, Self::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 {
            match extractor.extract(&mut req).await {
                Ok(authorities) => {
                    req.attach(authorities);
                    Ok(service.call(req).await?.map_into_left_body())
                }
                Err(err) => Ok(req.error_response(err).map_into_right_body()),
            }
        })
    }
}