-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
//! Authentication middleware | ||
//! | ||
//! Depending on the use case, there are vastly different requirements on how to | ||
//! implement authentication and authorization for the Delta Sharing server. | ||
//! | ||
//! Example use cases include: | ||
//! - Public access: No authentication is required, all data is public. | ||
//! - Share based access: Recipients can access data based on a shared secret. | ||
//! - OAuth: Recipients can access data based on an OAuth token. | ||
//! | ||
//! In order to support these different use cases, the Delta Sharing server is | ||
//! designed to be extensible. The authentication middleware is implemented as a | ||
//! [tower::Layer](https://docs.rs/tower/0.4.4/tower/trait.Layer.html) that | ||
//! wraps the application service. The authentication middleware is responsible | ||
//! for authenticating the client and setting the recipient identifier in the | ||
//! request extensions. the recipient identifier is used by the Delta Sharing | ||
//! server to determine the access control policy for the request. | ||
|
||
use std::fmt::Display; | ||
|
||
pub mod public; | ||
|
||
/// Recipient identifier. | ||
/// | ||
/// The recipient identifier is used to identify the client that is making the | ||
/// request. The recipient identifier is used by the Delta Sharing server to | ||
/// determine the access control policy for the request. | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub enum RecipientId { | ||
/// Anonymous recipient identifier. | ||
Unknown, | ||
/// Known recipient identifier. | ||
Known(String), | ||
} | ||
|
||
impl RecipientId { | ||
/// Create a new [`RecipientId`] for an anonymous recipient. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::unknown(); | ||
/// assert_eq!(recipient_id, RecipientId::Unknown); | ||
/// ``` | ||
pub fn unknown() -> Self { | ||
Self::Unknown | ||
} | ||
|
||
/// Create a new [`RecipientId`] for an anonymous recipient. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::anonymous(); | ||
/// assert_eq!(recipient_id, RecipientId::Unknown); | ||
/// ``` | ||
pub fn anonymous() -> Self { | ||
Self::Unknown | ||
} | ||
|
||
/// Create a new [`RecipientId`] for an authenticated recipient. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::known("foo"); | ||
/// assert_eq!(recipient_id, RecipientId::Known("foo".to_owned())); | ||
/// ``` | ||
pub fn known<S: Into<String>>(recipient_id: S) -> Self { | ||
Self::Known(recipient_id.into()) | ||
} | ||
|
||
/// Check if the recipient identifier is unknown. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::anonymous(); | ||
/// assert!(recipient_id.is_unknown()); | ||
/// ``` | ||
pub fn is_unknown(&self) -> bool { | ||
matches!(self, RecipientId::Unknown) | ||
} | ||
|
||
/// Check if the recipient identifier is anonymous. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::anonymous(); | ||
/// assert!(recipient_id.is_anonymous()); | ||
/// ``` | ||
pub fn is_anonymous(&self) -> bool { | ||
matches!(self, RecipientId::Unknown) | ||
} | ||
|
||
/// Check if the recipient identifier is known. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use delta_sharing::auth::RecipientId; | ||
/// | ||
/// let recipient_id = RecipientId::known("foo"); | ||
/// assert!(recipient_id.is_known()); | ||
/// ``` | ||
pub fn is_known(&self) -> bool { | ||
matches!(self, RecipientId::Known(_)) | ||
} | ||
} | ||
|
||
impl Default for RecipientId { | ||
fn default() -> Self { | ||
Self::Unknown | ||
} | ||
} | ||
|
||
impl AsRef<str> for RecipientId { | ||
fn as_ref(&self) -> &str { | ||
match self { | ||
RecipientId::Unknown => "ANONYMOUS", | ||
RecipientId::Known(id) => id.as_str(), | ||
} | ||
} | ||
} | ||
|
||
impl Display for RecipientId { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
self.as_ref().fmt(f) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn default_recipient_id() { | ||
assert_eq!(RecipientId::default(), RecipientId::Unknown); | ||
} | ||
|
||
#[test] | ||
fn display_recipient_id() { | ||
assert_eq!( | ||
RecipientId::anonymous().to_string(), | ||
String::from("ANONYMOUS") | ||
); | ||
assert_eq!( | ||
RecipientId::known("my_recipient_id").to_string(), | ||
String::from("my_recipient_id") | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
//! Authentication middleware for public access | ||
//! | ||
//! The public access authentication middleware does not perform any | ||
//! authentication and sets the [`RecipientId`] in the request extension to | ||
//! anonymous. | ||
//! | ||
//! # Example | ||
//! ```rust | ||
//! use axum::extract::Request; | ||
//! use axum::response::Response; | ||
//! use axum::routing::get; | ||
//! use axum::body::Body; | ||
//! use tower::{ServiceBuilder, ServiceExt, Service, BoxError}; | ||
//! | ||
//! use delta_sharing::auth::RecipientId; | ||
//! use delta_sharing::auth::public::PublicAccessAuthLayer; | ||
//! | ||
//! async fn handler(req: Request<Body>) -> Result<Response<Body>, BoxError> { | ||
//! let recipient_id = req.extensions().get::<RecipientId>().unwrap(); | ||
//! assert_eq!(recipient_id, &RecipientId::Unknown); | ||
//! Ok(Response::new(Body::empty())) | ||
//! } | ||
//! | ||
//! # #[tokio::main] | ||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
//! | ||
//! let mut service = ServiceBuilder::new() | ||
//! .layer(PublicAccessAuthLayer::new()) | ||
//! .service_fn(handler); | ||
//! | ||
//! let request = Request::get("/") | ||
//! .body(Body::empty()) | ||
//! .unwrap(); | ||
//! | ||
//! let res = service.ready().await.unwrap().call(request).await.unwrap(); | ||
//! | ||
//! # Ok(()) | ||
//! # } | ||
//! ``` | ||
use std::task::{Context, Poll}; | ||
|
||
use axum::extract::Request; | ||
use tower::{Layer, Service}; | ||
|
||
use crate::auth::RecipientId; | ||
|
||
/// Layer that applies the [`PublicAccessAuth`] middleware. | ||
#[derive(Debug, Clone)] | ||
pub struct PublicAccessAuthLayer; | ||
|
||
impl PublicAccessAuthLayer { | ||
/// Create a new [`PublicAccessAuthLayer`]. | ||
pub fn new() -> Self { | ||
Self | ||
} | ||
} | ||
|
||
impl<S> Layer<S> for PublicAccessAuthLayer { | ||
type Service = PublicAccessAuth<S>; | ||
|
||
fn layer(&self, inner: S) -> Self::Service { | ||
PublicAccessAuth { inner } | ||
} | ||
} | ||
|
||
/// Authentication middleware. | ||
/// | ||
/// Does not perform any authentication and sets the [`RecipientId`] in the | ||
/// request extension to anonymous. | ||
#[derive(Debug, Clone)] | ||
pub struct PublicAccessAuth<S> { | ||
inner: S, | ||
} | ||
|
||
impl<S> Service<Request> for PublicAccessAuth<S> | ||
where | ||
S: Service<Request> + Send + 'static, | ||
S::Future: Send + 'static, | ||
{ | ||
type Response = S::Response; | ||
type Error = S::Error; | ||
type Future = S::Future; | ||
|
||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | ||
self.inner.poll_ready(cx) | ||
} | ||
|
||
fn call(&mut self, mut req: Request) -> Self::Future { | ||
let id = RecipientId::unknown(); | ||
tracing::info!(recipient_id=?id, "authenticated"); | ||
|
||
req.extensions_mut().insert(id); | ||
self.inner.call(req) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
use axum::body::Body; | ||
use axum::response::Response; | ||
use http::{header, Request, StatusCode}; | ||
use tower::BoxError; | ||
use tower::ServiceBuilder; | ||
use tower::ServiceExt; | ||
|
||
#[tokio::test] | ||
async fn public_access_auth_with_bearer() { | ||
let mut service = ServiceBuilder::new() | ||
.layer(PublicAccessAuthLayer::new()) | ||
.service_fn(check_recipient); | ||
|
||
let request = Request::get("/") | ||
.header(header::AUTHORIZATION, "Bearer foo") | ||
.body(Body::empty()) | ||
.unwrap(); | ||
|
||
let res = service.ready().await.unwrap().call(request).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::OK); | ||
} | ||
|
||
#[tokio::test] | ||
async fn public_access_auth_without_bearer() { | ||
let mut service = ServiceBuilder::new() | ||
.layer(PublicAccessAuthLayer::new()) | ||
.service_fn(check_recipient); | ||
|
||
let request = Request::get("/").body(Body::empty()).unwrap(); | ||
let res = service.ready().await.unwrap().call(request).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::OK); | ||
} | ||
|
||
async fn check_recipient(req: Request<Body>) -> Result<Response<Body>, BoxError> { | ||
assert_eq!( | ||
req.extensions().get::<RecipientId>(), | ||
Some(&RecipientId::Unknown) | ||
); | ||
Ok(Response::new(req.into_body())) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,6 @@ pub mod logging; | |
mod macros; | ||
pub mod server; | ||
|
||
pub mod auth; | ||
|
||
pub const VERSION: &str = git_version::git_version!(); |