restrepo 0.5.12

A collection of components for building restful webservices with actix-web
Documentation
//! Enables implementation of role based access control.
//! Since any concrete implementation will usually be fairly application specific,
//! The [RbacConfig], [RbacResourceSpec] and [RbacRoleSpec] types provide components for
//! resource organisation and role management. Actual access control can be set up using
//! the [Authorizer] trait. See its documentation for a sample implementation.
//! #### Rbac config example:
//! ```yaml
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/sample_rbac_config.json"))]
//! ```
//!
use async_trait::async_trait;

mod rbac;
pub use rbac::{RbacConfig, RbacResourceSpec, RbacRoleSpec};
use thiserror::Error;
use utoipa::IntoResponses;

/// The server could not authorize the request
#[derive(Debug, Error, IntoResponses)]
#[error("AccessForbiddenError: {}", _0)]
#[response(status = StatusCode::UNAUTHORIZED)]
pub struct AccessForbiddenError(pub String);

/// A trait for implementing request authorization.
///
/// #### Example
///
/// Assuming an `AuthContext` type holding `claims` exists and, for example,
/// a middleware calls the [authorize](Authorizer::authorize) method, passing an
/// [RbacConfig] holding resource definitions identified by unix-style `glob` patterns,
/// the implementation could look like this:
///
/// ```
/// # use async_trait::async_trait;
/// # use actix_web::http::Method;
/// # use restrepo::security::{AccessForbiddenError, Authorizer, RbacConfig};
/// # use glob::Pattern;
/// # struct Claims {roles: Vec<String>};
/// # struct AuthContext {claims: Claims, url: String, method: Method};
/// #[async_trait]
/// impl Authorizer<RbacConfig<String>, (), AccessForbiddenError> for AuthContext {
///     async fn authorize(
///         &self,
///         authz_config: &RbacConfig<String>,
///     ) -> Result<(), AccessForbiddenError> {
///         let access = authz_config
///             .roles()
///             .iter()
///             .filter_map(|(name, role)| {
///                 role.accessible_resources()
///                     .get(&self.method)
///                     .iter()
///                     .any(|idents| {
///                         idents
///                             .iter()
///                             .filter_map(|ident| Pattern::new(ident).ok())
///                             .any(|pattern| pattern.matches(&self.url))
///                     })
///                     .then_some(name)
///             })
///             .any(|name| self.claims.roles.contains(name));
///         match access {
///             true => return Ok(()),
///             false => {
///                 return Err(AccessForbiddenError(
///                     "Not authorized to access resource".to_string(),
///                 ))
///             }
///         }
///     }
/// }
///```
#[async_trait]
pub trait Authorizer<C: Send, R: Send, E: Into<AccessForbiddenError>> {
    async fn authorize(&self, authz_config: &C) -> Result<R, E>;
}