use std::rc::Rc;
use actix_service::{Service, Transform};
use actix_web::body::EitherBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{Error, HttpMessage};
use futures_util::future::{ok, LocalBoxFuture, Ready};
use crate::http::security::config::{Authenticator, Authorizer};
#[cfg(feature = "audit")]
fn get_client_ip(req: &ServiceRequest) -> String {
req.connection_info()
.realip_remote_addr()
.unwrap_or("-")
.to_string()
}
pub struct SecurityTransform<Auth, Autho> {
authenticator: Option<fn() -> Auth>,
authorizer: Option<fn() -> Autho>,
}
impl<Auth, Autho> SecurityTransform<Auth, Autho> {
pub fn new() -> Self {
SecurityTransform {
authorizer: None,
authenticator: None,
}
}
pub fn config_authenticator(mut self, authenticator: fn() -> Auth) -> Self {
self.authenticator = Some(authenticator);
self
}
pub fn config_authorizer(mut self, authorizer: fn() -> Autho) -> Self {
self.authorizer = Some(authorizer);
self
}
}
impl<Auth, Autho> Default for SecurityTransform<Auth, Autho> {
fn default() -> Self {
Self::new()
}
}
impl<S, B, Auth, Autho> Transform<S, ServiceRequest> for SecurityTransform<Auth, Autho>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
Auth: Authenticator + 'static,
Autho: Authorizer<B> + 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Transform = SecurityService<Auth, Autho, S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
let authenticator = self.authenticator.map(|f| f());
let authorizer = self.authorizer.map(|f| f());
ok(SecurityService {
authenticator,
authorizer,
service: Rc::new(service),
})
}
}
pub struct SecurityService<Auth, Autho, S> {
authenticator: Option<Auth>,
authorizer: Option<Autho>,
service: Rc<S>,
}
impl<Auth, Autho, S, B> Service<ServiceRequest> for SecurityService<Auth, Autho, S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
Auth: Authenticator,
Autho: Authorizer<B>,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
actix_web::dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
let user = self
.authenticator
.as_ref()
.and_then(|auth| auth.get_user(&req));
#[cfg(feature = "audit")]
{
let path = req.path().to_string();
let method = req.method().to_string();
let ip = get_client_ip(&req);
if let Some(ref u) = user {
tracing::info!(
target: "actix_security::audit",
event_type = "AUTHENTICATION_SUCCESS",
user = %u.get_username(),
ip = %ip,
path = %path,
method = %method,
"User authenticated successfully"
);
} else if self.authenticator.is_some() {
tracing::debug!(
target: "actix_security::audit",
event_type = "AUTHENTICATION_ANONYMOUS",
ip = %ip,
path = %path,
method = %method,
"Anonymous request (no credentials provided)"
);
}
}
if let Some(ref u) = user {
req.extensions_mut().insert(u.clone());
}
if let Some(authorizer) = &self.authorizer {
let next = move |req: ServiceRequest| -> LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>> {
let fut = service.call(req);
Box::pin(fut)
};
authorizer.process(req, user.as_ref(), next)
} else {
let fut = service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res.map_into_left_body())
})
}
}
}