use std::future::poll_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::cache::Cache;
use crate::decision::AccessDecision;
use crate::engine::Engine;
use crate::permission::Permission;
#[cfg(feature = "platform")]
use crate::platform::{
PlatformAccessRequest, PlatformAuthorizationSource, PlatformEngine, PlatformPrincipalId,
PlatformSubject,
};
use crate::request::{AuthSubject, TenantAccessRequest};
use crate::source::AuthorizationSource;
use crate::{PrincipalId, ScopePath, ScopedAccessRequest, TenantId};
use ::axum::body::Body;
use ::axum::http::{Request, StatusCode};
use ::axum::response::{IntoResponse, Response};
use ::tower::{Layer, Service};
#[derive(Debug, Clone)]
pub struct AuthContext {
pub subject: AuthSubject,
}
impl AuthContext {
pub fn new(tenant: TenantId, principal: PrincipalId) -> Self {
Self {
subject: AuthSubject::new(tenant, principal),
}
}
}
#[cfg(feature = "platform")]
#[derive(Debug, Clone)]
pub struct PlatformAuthContext {
pub subject: PlatformSubject,
}
#[cfg(feature = "platform")]
impl PlatformAuthContext {
pub fn new(principal: PlatformPrincipalId) -> Self {
Self {
subject: PlatformSubject::new(principal),
}
}
}
#[derive(Debug, Clone)]
pub struct TenantAuthorizeLayer<S, C> {
engine: Arc<Engine<S, C>>,
permission: Permission,
}
impl<S, C> TenantAuthorizeLayer<S, C> {
pub fn new(engine: Arc<Engine<S, C>>, permission: Permission) -> Self {
Self { engine, permission }
}
}
impl<S, C, Inner> Layer<Inner> for TenantAuthorizeLayer<S, C>
where
S: AuthorizationSource,
C: Cache,
{
type Service = TenantAuthorizeService<Inner, S, C>;
fn layer(&self, inner: Inner) -> Self::Service {
TenantAuthorizeService {
inner,
engine: self.engine.clone(),
permission: self.permission.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct TenantAuthorizeService<Inner, S, C> {
inner: Inner,
engine: Arc<Engine<S, C>>,
permission: Permission,
}
impl<Inner, S, C> Service<Request<Body>> for TenantAuthorizeService<Inner, S, C>
where
Inner: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
Inner::Future: Send + 'static,
S: AuthorizationSource + 'static,
C: Cache + 'static,
{
type Response = Response;
type Error = Inner::Error;
type Future = Pin<Box<dyn std::future::Future<Output = Result<Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let mut inner = self.inner.clone();
let engine = self.engine.clone();
let permission = self.permission.clone();
Box::pin(async move {
let subject = req
.extensions()
.get::<AuthContext>()
.map(|context| context.subject.clone())
.or_else(|| req.extensions().get::<AuthSubject>().cloned());
let Some(subject) = subject else {
return Ok((StatusCode::UNAUTHORIZED, "missing auth context").into_response());
};
match engine
.can_tenant(TenantAccessRequest {
subject,
permission,
})
.await
{
Ok(AccessDecision::Allow) => {
poll_fn(|cx| inner.poll_ready(cx)).await?;
inner.call(req).await
}
Ok(AccessDecision::Deny) => {
Ok((StatusCode::FORBIDDEN, "forbidden").into_response())
}
Err(_) => Ok((StatusCode::INTERNAL_SERVER_ERROR, "auth error").into_response()),
}
})
}
}
#[cfg(feature = "platform")]
#[derive(Debug, Clone)]
pub struct PlatformAuthorizeLayer<S> {
engine: Arc<PlatformEngine<S>>,
permission: Permission,
}
#[cfg(feature = "platform")]
impl<S> PlatformAuthorizeLayer<S> {
pub fn new(engine: Arc<PlatformEngine<S>>, permission: Permission) -> Self {
Self { engine, permission }
}
}
#[cfg(feature = "platform")]
impl<S, Inner> Layer<Inner> for PlatformAuthorizeLayer<S>
where
S: PlatformAuthorizationSource,
{
type Service = PlatformAuthorizeService<Inner, S>;
fn layer(&self, inner: Inner) -> Self::Service {
PlatformAuthorizeService {
inner,
engine: self.engine.clone(),
permission: self.permission.clone(),
}
}
}
#[cfg(feature = "platform")]
#[derive(Debug, Clone)]
pub struct PlatformAuthorizeService<Inner, S> {
inner: Inner,
engine: Arc<PlatformEngine<S>>,
permission: Permission,
}
#[cfg(feature = "platform")]
impl<Inner, S> Service<Request<Body>> for PlatformAuthorizeService<Inner, S>
where
Inner: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
Inner::Future: Send + 'static,
S: PlatformAuthorizationSource + 'static,
{
type Response = Response;
type Error = Inner::Error;
type Future = Pin<Box<dyn std::future::Future<Output = Result<Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let mut inner = self.inner.clone();
let engine = self.engine.clone();
let permission = self.permission.clone();
Box::pin(async move {
let subject = req
.extensions()
.get::<PlatformAuthContext>()
.map(|context| context.subject.clone())
.or_else(|| req.extensions().get::<PlatformSubject>().cloned());
let Some(subject) = subject else {
return Ok(
(StatusCode::UNAUTHORIZED, "missing platform auth context").into_response()
);
};
match engine
.can_platform(PlatformAccessRequest {
subject,
permission,
})
.await
{
Ok(AccessDecision::Allow) => {
poll_fn(|cx| inner.poll_ready(cx)).await?;
inner.call(req).await
}
Ok(AccessDecision::Deny) => {
Ok((StatusCode::FORBIDDEN, "forbidden").into_response())
}
Err(_) => Ok((StatusCode::INTERNAL_SERVER_ERROR, "auth error").into_response()),
}
})
}
}
pub async fn can_access_scope<S, C>(
engine: &Engine<S, C>,
subject: AuthSubject,
permission: Permission,
target: ScopePath,
) -> crate::Result<AccessDecision>
where
S: AuthorizationSource,
C: Cache,
{
engine
.can_access_scope(ScopedAccessRequest {
subject,
permission,
target,
})
.await
}
#[cfg(feature = "platform")]
pub async fn can_platform<S>(
engine: &PlatformEngine<S>,
subject: PlatformSubject,
permission: Permission,
) -> crate::Result<AccessDecision>
where
S: PlatformAuthorizationSource,
{
engine
.can_platform(PlatformAccessRequest {
subject,
permission,
})
.await
}
#[cfg(feature = "axum-jwt")]
pub mod jwt {
use std::fmt;
use std::future::poll_fn;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use jsonwebtoken::{DecodingKey, Validation, decode};
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::axum::AuthContext;
use crate::{PrincipalId, TenantId};
use ::axum::body::Body;
use ::axum::extract::FromRequestParts;
use ::axum::http::header::AUTHORIZATION;
use ::axum::http::request::Parts;
use ::axum::http::{HeaderMap, Request, StatusCode};
use ::axum::response::{IntoResponse, Response};
use ::tower::{Layer, Service};
#[derive(Debug, Error)]
pub enum AuthError {
#[error("missing authorization header")]
MissingAuthorization,
#[error("invalid authorization header")]
InvalidAuthorization,
#[error("invalid token")]
InvalidToken,
#[error("invalid claims: {0}")]
InvalidClaims(String),
#[error("invalid id: {0}")]
InvalidId(String),
}
#[derive(Debug)]
pub struct AuthRejection {
status: StatusCode,
message: String,
}
impl From<AuthError> for AuthRejection {
fn from(err: AuthError) -> Self {
Self {
status: StatusCode::UNAUTHORIZED,
message: err.to_string(),
}
}
}
impl IntoResponse for AuthRejection {
fn into_response(self) -> Response {
(self.status, self.message).into_response()
}
}
pub trait JwtClaims: DeserializeOwned + Send + Sync + Clone + 'static {
fn tenant_id(&self) -> &str;
fn principal_id(&self) -> &str;
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct DefaultClaims {
pub tenant_id: String,
pub principal_id: String,
pub sub: Option<String>,
pub exp: Option<usize>,
}
impl JwtClaims for DefaultClaims {
fn tenant_id(&self) -> &str {
&self.tenant_id
}
fn principal_id(&self) -> &str {
&self.principal_id
}
}
#[derive(Clone)]
pub struct JwtAuthState<C: JwtClaims> {
decoding_key: Arc<DecodingKey>,
validation: Validation,
_marker: PhantomData<fn() -> C>,
}
impl<C: JwtClaims> fmt::Debug for JwtAuthState<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JwtAuthState")
.field("decoding_key", &"<redacted>")
.field("validation", &self.validation)
.finish()
}
}
impl<C: JwtClaims> JwtAuthState<C> {
pub fn new(decoding_key: DecodingKey, validation: Validation) -> Self {
Self {
decoding_key: Arc::new(decoding_key),
validation,
_marker: PhantomData,
}
}
fn decode_from_headers(&self, headers: &HeaderMap) -> Result<JwtAuth<C>, AuthError> {
let token = bearer_token(headers)?;
let data = decode::<C>(&token, &self.decoding_key, &self.validation)
.map_err(|_| AuthError::InvalidToken)?;
JwtAuth::from_claims(data.claims)
}
}
pub trait JwtAuthProvider<C: JwtClaims> {
fn jwt_auth(&self) -> &JwtAuthState<C>;
}
#[derive(Debug, Clone)]
pub struct JwtAuth<C: JwtClaims> {
pub context: AuthContext,
pub claims: C,
}
impl<C: JwtClaims> JwtAuth<C> {
fn from_claims(claims: C) -> Result<Self, AuthError> {
let tenant = TenantId::parse(claims.tenant_id())
.map_err(|err| AuthError::InvalidId(err.to_string()))?;
let principal = PrincipalId::parse(claims.principal_id())
.map_err(|err| AuthError::InvalidId(err.to_string()))?;
Ok(Self {
context: AuthContext::new(tenant, principal),
claims,
})
}
}
impl<S, C> FromRequestParts<S> for JwtAuth<C>
where
S: Send + Sync + JwtAuthProvider<C>,
C: JwtClaims,
{
type Rejection = AuthRejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
if let Some(existing) = parts.extensions.get::<JwtAuth<C>>() {
return Ok(existing.clone());
}
let auth = state.jwt_auth().decode_from_headers(&parts.headers)?;
parts.extensions.insert(auth.context.subject.clone());
parts.extensions.insert(auth.clone());
parts.extensions.insert(auth.context.clone());
Ok(auth)
}
}
impl<S> FromRequestParts<S> for AuthContext
where
S: Send + Sync + JwtAuthProvider<DefaultClaims>,
{
type Rejection = AuthRejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let auth = JwtAuth::<DefaultClaims>::from_request_parts(parts, state).await?;
Ok(auth.context)
}
}
#[derive(Debug, Clone)]
pub struct JwtAuthLayer<C: JwtClaims> {
state: Arc<JwtAuthState<C>>,
}
impl<C: JwtClaims> JwtAuthLayer<C> {
pub fn new(state: JwtAuthState<C>) -> Self {
Self {
state: Arc::new(state),
}
}
}
impl<S, C> Layer<S> for JwtAuthLayer<C>
where
C: JwtClaims,
{
type Service = JwtAuthService<S, C>;
fn layer(&self, inner: S) -> Self::Service {
JwtAuthService {
inner,
state: self.state.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct JwtAuthService<S, C: JwtClaims> {
inner: S,
state: Arc<JwtAuthState<C>>,
}
impl<S, C> Service<Request<Body>> for JwtAuthService<S, C>
where
S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
S::Future: Send + 'static,
C: JwtClaims,
{
type Response = Response;
type Error = S::Error;
type Future =
Pin<Box<dyn std::future::Future<Output = Result<Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
let state = self.state.clone();
let mut inner = self.inner.clone();
Box::pin(async move {
match state.decode_from_headers(req.headers()) {
Ok(auth) => {
req.extensions_mut().insert(auth.context.subject.clone());
req.extensions_mut().insert(auth.context.clone());
req.extensions_mut().insert(auth);
poll_fn(|cx| inner.poll_ready(cx)).await?;
inner.call(req).await
}
Err(err) => Ok(AuthRejection::from(err).into_response()),
}
})
}
}
fn bearer_token(headers: &HeaderMap) -> Result<String, AuthError> {
let value = headers
.get(AUTHORIZATION)
.ok_or(AuthError::MissingAuthorization)?;
let value = value
.to_str()
.map_err(|_| AuthError::InvalidAuthorization)?;
let token = value
.strip_prefix("Bearer ")
.ok_or(AuthError::InvalidAuthorization)?;
if token.is_empty() {
return Err(AuthError::InvalidAuthorization);
}
Ok(token.to_string())
}
}
#[cfg(all(test, feature = "platform", feature = "memory-store"))]
mod tests {
use super::*;
use crate::platform::{
MemoryPlatformSource, PlatformEngineBuilder, PlatformGrantScope, PlatformPrincipalStatus,
PlatformRoleId,
};
use futures::executor::block_on;
use std::convert::Infallible;
use std::future::{Ready, ready};
#[derive(Clone)]
struct OkService;
impl Service<Request<Body>> for OkService {
type Response = Response;
type Error = Infallible;
type Future = Ready<Result<Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: Request<Body>) -> Self::Future {
ready(Ok(StatusCode::NO_CONTENT.into_response()))
}
}
fn platform_engine() -> (PlatformEngine<MemoryPlatformSource>, PlatformSubject) {
let source = MemoryPlatformSource::new();
let subject = PlatformAuthContext::new(
PlatformPrincipalId::parse("platform_admin").expect("principal"),
)
.subject;
let role = PlatformRoleId::parse("platform_admin").expect("role");
source.set_principal_status(subject.principal.clone(), PlatformPrincipalStatus::Active);
source.add_role_assignment(
subject.principal.clone(),
role.clone(),
PlatformGrantScope::platform(),
);
source.add_role_permission(
role,
Permission::parse("platform/role:update").expect("permission"),
);
(PlatformEngineBuilder::new(source).build(), subject)
}
#[test]
fn platform_authorize_layer_should_allow_platform_subject_extension() {
let (engine, subject) = platform_engine();
let layer = PlatformAuthorizeLayer::new(
Arc::new(engine),
Permission::parse("platform/role:update").expect("permission"),
);
let mut service = layer.layer(OkService);
let mut req = Request::new(Body::empty());
req.extensions_mut().insert(subject);
let response = block_on(service.call(req)).expect("response");
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[test]
fn platform_authorize_layer_should_accept_platform_auth_context() {
let (engine, subject) = platform_engine();
let layer = PlatformAuthorizeLayer::new(
Arc::new(engine),
Permission::parse("platform/role:update").expect("permission"),
);
let mut service = layer.layer(OkService);
let mut req = Request::new(Body::empty());
req.extensions_mut().insert(PlatformAuthContext { subject });
let response = block_on(service.call(req)).expect("response");
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[test]
fn platform_authorize_layer_should_reject_missing_context() {
let (engine, _) = platform_engine();
let layer = PlatformAuthorizeLayer::new(
Arc::new(engine),
Permission::parse("platform/role:update").expect("permission"),
);
let mut service = layer.layer(OkService);
let response = block_on(service.call(Request::new(Body::empty()))).expect("response");
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[test]
fn platform_authorize_layer_should_reject_denied_permission() {
let (engine, subject) = platform_engine();
let layer = PlatformAuthorizeLayer::new(
Arc::new(engine),
Permission::parse("platform/role:delete").expect("permission"),
);
let mut service = layer.layer(OkService);
let mut req = Request::new(Body::empty());
req.extensions_mut().insert(subject);
let response = block_on(service.call(req)).expect("response");
assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
}