diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-15 16:37:08 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-17 16:30:22 -0600 |
| commit | 45df4d0d9b577fecee798d672695fe24ff57fb1b (patch) | |
| tree | 1b99bf645035b58e0d6db08c7a83521f41f7a75b /vendor/tower-http/src/auth | |
| parent | f94f79608393d4ab127db63cc41668445ef6b243 (diff) | |
feat: migrate from Cedar to SpiceDB authorization system
This is a major architectural change that replaces the Cedar policy-based
authorization system with SpiceDB's relation-based authorization.
Key changes:
- Migrate from Rust to Go implementation
- Replace Cedar policies with SpiceDB schema and relationships
- Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks
- Update build system and dependencies for Go ecosystem
- Maintain Envoy integration for external authorization
This change enables more flexible permission modeling through SpiceDB's
Google Zanzibar inspired relation-based system, supporting complex
hierarchical permissions that were difficult to express in Cedar.
Breaking change: Existing Cedar policies and Rust-based configuration
will no longer work and need to be migrated to SpiceDB schema.
Diffstat (limited to 'vendor/tower-http/src/auth')
| -rw-r--r-- | vendor/tower-http/src/auth/add_authorization.rs | 267 | ||||
| -rw-r--r-- | vendor/tower-http/src/auth/async_require_authorization.rs | 385 | ||||
| -rw-r--r-- | vendor/tower-http/src/auth/mod.rs | 13 | ||||
| -rw-r--r-- | vendor/tower-http/src/auth/require_authorization.rs | 404 |
4 files changed, 0 insertions, 1069 deletions
diff --git a/vendor/tower-http/src/auth/add_authorization.rs b/vendor/tower-http/src/auth/add_authorization.rs deleted file mode 100644 index 246c13b6..00000000 --- a/vendor/tower-http/src/auth/add_authorization.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! Add authorization to requests using the [`Authorization`] header. -//! -//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -//! -//! # Example -//! -//! ``` -//! use tower_http::validate_request::{ValidateRequestHeader, ValidateRequestHeaderLayer}; -//! use tower_http::auth::AddAuthorizationLayer; -//! use http::{Request, Response, StatusCode, header::AUTHORIZATION}; -//! use tower::{Service, ServiceExt, ServiceBuilder, service_fn, BoxError}; -//! use http_body_util::Full; -//! use bytes::Bytes; -//! # async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> { -//! # Ok(Response::new(Full::default())) -//! # } -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), BoxError> { -//! # let service_that_requires_auth = ValidateRequestHeader::basic( -//! # tower::service_fn(handle), -//! # "username", -//! # "password", -//! # ); -//! let mut client = ServiceBuilder::new() -//! // Use basic auth with the given username and password -//! .layer(AddAuthorizationLayer::basic("username", "password")) -//! .service(service_that_requires_auth); -//! -//! // Make a request, we don't have to add the `Authorization` header manually -//! let response = client -//! .ready() -//! .await? -//! .call(Request::new(Full::default())) -//! .await?; -//! -//! assert_eq!(StatusCode::OK, response.status()); -//! # Ok(()) -//! # } -//! ``` - -use base64::Engine as _; -use http::{HeaderValue, Request, Response}; -use std::{ - convert::TryFrom, - task::{Context, Poll}, -}; -use tower_layer::Layer; -use tower_service::Service; - -const BASE64: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD; - -/// Layer that applies [`AddAuthorization`] which adds authorization to all requests using the -/// [`Authorization`] header. -/// -/// See the [module docs](crate::auth::add_authorization) for an example. -/// -/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this -/// middleware. -/// -/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -/// [`SetRequestHeader`]: crate::set_header::SetRequestHeader -#[derive(Debug, Clone)] -pub struct AddAuthorizationLayer { - value: HeaderValue, -} - -impl AddAuthorizationLayer { - /// Authorize requests using a username and password pair. - /// - /// The `Authorization` header will be set to `Basic {credentials}` where `credentials` is - /// `base64_encode("{username}:{password}")`. - /// - /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS - /// with this method. However use of HTTPS/TLS is not enforced by this middleware. - pub fn basic(username: &str, password: &str) -> Self { - let encoded = BASE64.encode(format!("{}:{}", username, password)); - let value = HeaderValue::try_from(format!("Basic {}", encoded)).unwrap(); - Self { value } - } - - /// Authorize requests using a "bearer token". Commonly used for OAuth 2. - /// - /// The `Authorization` header will be set to `Bearer {token}`. - /// - /// # Panics - /// - /// Panics if the token is not a valid [`HeaderValue`]. - pub fn bearer(token: &str) -> Self { - let value = - HeaderValue::try_from(format!("Bearer {}", token)).expect("token is not valid header"); - Self { value } - } - - /// Mark the header as [sensitive]. - /// - /// This can for example be used to hide the header value from logs. - /// - /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive - #[allow(clippy::wrong_self_convention)] - pub fn as_sensitive(mut self, sensitive: bool) -> Self { - self.value.set_sensitive(sensitive); - self - } -} - -impl<S> Layer<S> for AddAuthorizationLayer { - type Service = AddAuthorization<S>; - - fn layer(&self, inner: S) -> Self::Service { - AddAuthorization { - inner, - value: self.value.clone(), - } - } -} - -/// Middleware that adds authorization all requests using the [`Authorization`] header. -/// -/// See the [module docs](crate::auth::add_authorization) for an example. -/// -/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this -/// middleware. -/// -/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -/// [`SetRequestHeader`]: crate::set_header::SetRequestHeader -#[derive(Debug, Clone)] -pub struct AddAuthorization<S> { - inner: S, - value: HeaderValue, -} - -impl<S> AddAuthorization<S> { - /// Authorize requests using a username and password pair. - /// - /// The `Authorization` header will be set to `Basic {credentials}` where `credentials` is - /// `base64_encode("{username}:{password}")`. - /// - /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS - /// with this method. However use of HTTPS/TLS is not enforced by this middleware. - pub fn basic(inner: S, username: &str, password: &str) -> Self { - AddAuthorizationLayer::basic(username, password).layer(inner) - } - - /// Authorize requests using a "bearer token". Commonly used for OAuth 2. - /// - /// The `Authorization` header will be set to `Bearer {token}`. - /// - /// # Panics - /// - /// Panics if the token is not a valid [`HeaderValue`]. - pub fn bearer(inner: S, token: &str) -> Self { - AddAuthorizationLayer::bearer(token).layer(inner) - } - - define_inner_service_accessors!(); - - /// Mark the header as [sensitive]. - /// - /// This can for example be used to hide the header value from logs. - /// - /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive - #[allow(clippy::wrong_self_convention)] - pub fn as_sensitive(mut self, sensitive: bool) -> Self { - self.value.set_sensitive(sensitive); - self - } -} - -impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for AddAuthorization<S> -where - S: Service<Request<ReqBody>, Response = Response<ResBody>>, -{ - 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<ReqBody>) -> Self::Future { - req.headers_mut() - .insert(http::header::AUTHORIZATION, self.value.clone()); - self.inner.call(req) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_helpers::Body; - use crate::validate_request::ValidateRequestHeaderLayer; - use http::{Response, StatusCode}; - use std::convert::Infallible; - use tower::{BoxError, Service, ServiceBuilder, ServiceExt}; - - #[tokio::test] - async fn basic() { - // service that requires auth for all requests - let svc = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::basic("foo", "bar")) - .service_fn(echo); - - // make a client that adds auth - let mut client = AddAuthorization::basic(svc, "foo", "bar"); - - let res = client - .ready() - .await - .unwrap() - .call(Request::new(Body::empty())) - .await - .unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn token() { - // service that requires auth for all requests - let svc = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foo")) - .service_fn(echo); - - // make a client that adds auth - let mut client = AddAuthorization::bearer(svc, "foo"); - - let res = client - .ready() - .await - .unwrap() - .call(Request::new(Body::empty())) - .await - .unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn making_header_sensitive() { - let svc = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foo")) - .service_fn(|request: Request<Body>| async move { - let auth = request.headers().get(http::header::AUTHORIZATION).unwrap(); - assert!(auth.is_sensitive()); - - Ok::<_, Infallible>(Response::new(Body::empty())) - }); - - let mut client = AddAuthorization::bearer(svc, "foo").as_sensitive(true); - - let res = client - .ready() - .await - .unwrap() - .call(Request::new(Body::empty())) - .await - .unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - async fn echo(req: Request<Body>) -> Result<Response<Body>, BoxError> { - Ok(Response::new(req.into_body())) - } -} diff --git a/vendor/tower-http/src/auth/async_require_authorization.rs b/vendor/tower-http/src/auth/async_require_authorization.rs deleted file mode 100644 index fda9abea..00000000 --- a/vendor/tower-http/src/auth/async_require_authorization.rs +++ /dev/null @@ -1,385 +0,0 @@ -//! Authorize requests using the [`Authorization`] header asynchronously. -//! -//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -//! -//! # Example -//! -//! ``` -//! use tower_http::auth::{AsyncRequireAuthorizationLayer, AsyncAuthorizeRequest}; -//! use http::{Request, Response, StatusCode, header::AUTHORIZATION}; -//! use tower::{Service, ServiceExt, ServiceBuilder, service_fn, BoxError}; -//! use futures_core::future::BoxFuture; -//! use bytes::Bytes; -//! use http_body_util::Full; -//! -//! #[derive(Clone, Copy)] -//! struct MyAuth; -//! -//! impl<B> AsyncAuthorizeRequest<B> for MyAuth -//! where -//! B: Send + Sync + 'static, -//! { -//! type RequestBody = B; -//! type ResponseBody = Full<Bytes>; -//! type Future = BoxFuture<'static, Result<Request<B>, Response<Self::ResponseBody>>>; -//! -//! fn authorize(&mut self, mut request: Request<B>) -> Self::Future { -//! Box::pin(async { -//! if let Some(user_id) = check_auth(&request).await { -//! // Set `user_id` as a request extension so it can be accessed by other -//! // services down the stack. -//! request.extensions_mut().insert(user_id); -//! -//! Ok(request) -//! } else { -//! let unauthorized_response = Response::builder() -//! .status(StatusCode::UNAUTHORIZED) -//! .body(Full::<Bytes>::default()) -//! .unwrap(); -//! -//! Err(unauthorized_response) -//! } -//! }) -//! } -//! } -//! -//! async fn check_auth<B>(request: &Request<B>) -> Option<UserId> { -//! // ... -//! # None -//! } -//! -//! #[derive(Debug, Clone)] -//! struct UserId(String); -//! -//! async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> { -//! // Access the `UserId` that was set in `on_authorized`. If `handle` gets called the -//! // request was authorized and `UserId` will be present. -//! let user_id = request -//! .extensions() -//! .get::<UserId>() -//! .expect("UserId will be there if request was authorized"); -//! -//! println!("request from {:?}", user_id); -//! -//! Ok(Response::new(Full::default())) -//! } -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), BoxError> { -//! let service = ServiceBuilder::new() -//! // Authorize requests using `MyAuth` -//! .layer(AsyncRequireAuthorizationLayer::new(MyAuth)) -//! .service_fn(handle); -//! # Ok(()) -//! # } -//! ``` -//! -//! Or using a closure: -//! -//! ``` -//! use tower_http::auth::{AsyncRequireAuthorizationLayer, AsyncAuthorizeRequest}; -//! use http::{Request, Response, StatusCode}; -//! use tower::{Service, ServiceExt, ServiceBuilder, BoxError}; -//! use futures_core::future::BoxFuture; -//! use http_body_util::Full; -//! use bytes::Bytes; -//! -//! async fn check_auth<B>(request: &Request<B>) -> Option<UserId> { -//! // ... -//! # None -//! } -//! -//! #[derive(Debug)] -//! struct UserId(String); -//! -//! async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> { -//! # todo!(); -//! // ... -//! } -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), BoxError> { -//! let service = ServiceBuilder::new() -//! .layer(AsyncRequireAuthorizationLayer::new(|request: Request<Full<Bytes>>| async move { -//! if let Some(user_id) = check_auth(&request).await { -//! Ok(request) -//! } else { -//! let unauthorized_response = Response::builder() -//! .status(StatusCode::UNAUTHORIZED) -//! .body(Full::<Bytes>::default()) -//! .unwrap(); -//! -//! Err(unauthorized_response) -//! } -//! })) -//! .service_fn(handle); -//! # Ok(()) -//! # } -//! ``` - -use http::{Request, Response}; -use pin_project_lite::pin_project; -use std::{ - future::Future, - mem, - pin::Pin, - task::{ready, Context, Poll}, -}; -use tower_layer::Layer; -use tower_service::Service; - -/// Layer that applies [`AsyncRequireAuthorization`] which authorizes all requests using the -/// [`Authorization`] header. -/// -/// See the [module docs](crate::auth::async_require_authorization) for an example. -/// -/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -#[derive(Debug, Clone)] -pub struct AsyncRequireAuthorizationLayer<T> { - auth: T, -} - -impl<T> AsyncRequireAuthorizationLayer<T> { - /// Authorize requests using a custom scheme. - pub fn new(auth: T) -> AsyncRequireAuthorizationLayer<T> { - Self { auth } - } -} - -impl<S, T> Layer<S> for AsyncRequireAuthorizationLayer<T> -where - T: Clone, -{ - type Service = AsyncRequireAuthorization<S, T>; - - fn layer(&self, inner: S) -> Self::Service { - AsyncRequireAuthorization::new(inner, self.auth.clone()) - } -} - -/// Middleware that authorizes all requests using the [`Authorization`] header. -/// -/// See the [module docs](crate::auth::async_require_authorization) for an example. -/// -/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -#[derive(Clone, Debug)] -pub struct AsyncRequireAuthorization<S, T> { - inner: S, - auth: T, -} - -impl<S, T> AsyncRequireAuthorization<S, T> { - define_inner_service_accessors!(); -} - -impl<S, T> AsyncRequireAuthorization<S, T> { - /// Authorize requests using a custom scheme. - /// - /// The `Authorization` header is required to have the value provided. - pub fn new(inner: S, auth: T) -> AsyncRequireAuthorization<S, T> { - Self { inner, auth } - } - - /// Returns a new [`Layer`] that wraps services with an [`AsyncRequireAuthorizationLayer`] - /// middleware. - /// - /// [`Layer`]: tower_layer::Layer - pub fn layer(auth: T) -> AsyncRequireAuthorizationLayer<T> { - AsyncRequireAuthorizationLayer::new(auth) - } -} - -impl<ReqBody, ResBody, S, Auth> Service<Request<ReqBody>> for AsyncRequireAuthorization<S, Auth> -where - Auth: AsyncAuthorizeRequest<ReqBody, ResponseBody = ResBody>, - S: Service<Request<Auth::RequestBody>, Response = Response<ResBody>> + Clone, -{ - type Response = Response<ResBody>; - type Error = S::Error; - type Future = ResponseFuture<Auth, S, ReqBody>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: Request<ReqBody>) -> Self::Future { - let mut inner = self.inner.clone(); - let authorize = self.auth.authorize(req); - // mem::swap due to https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - mem::swap(&mut self.inner, &mut inner); - - ResponseFuture { - state: State::Authorize { authorize }, - service: inner, - } - } -} - -pin_project! { - /// Response future for [`AsyncRequireAuthorization`]. - pub struct ResponseFuture<Auth, S, ReqBody> - where - Auth: AsyncAuthorizeRequest<ReqBody>, - S: Service<Request<Auth::RequestBody>>, - { - #[pin] - state: State<Auth::Future, S::Future>, - service: S, - } -} - -pin_project! { - #[project = StateProj] - enum State<A, SFut> { - Authorize { - #[pin] - authorize: A, - }, - Authorized { - #[pin] - fut: SFut, - }, - } -} - -impl<Auth, S, ReqBody, B> Future for ResponseFuture<Auth, S, ReqBody> -where - Auth: AsyncAuthorizeRequest<ReqBody, ResponseBody = B>, - S: Service<Request<Auth::RequestBody>, Response = Response<B>>, -{ - type Output = Result<Response<B>, S::Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { - let mut this = self.project(); - - loop { - match this.state.as_mut().project() { - StateProj::Authorize { authorize } => { - let auth = ready!(authorize.poll(cx)); - match auth { - Ok(req) => { - let fut = this.service.call(req); - this.state.set(State::Authorized { fut }) - } - Err(res) => { - return Poll::Ready(Ok(res)); - } - }; - } - StateProj::Authorized { fut } => { - return fut.poll(cx); - } - } - } - } -} - -/// Trait for authorizing requests. -pub trait AsyncAuthorizeRequest<B> { - /// The type of request body returned by `authorize`. - /// - /// Set this to `B` unless you need to change the request body type. - type RequestBody; - - /// The body type used for responses to unauthorized requests. - type ResponseBody; - - /// The Future type returned by `authorize` - type Future: Future<Output = Result<Request<Self::RequestBody>, Response<Self::ResponseBody>>>; - - /// Authorize the request. - /// - /// If the future resolves to `Ok(request)` then the request is allowed through, otherwise not. - fn authorize(&mut self, request: Request<B>) -> Self::Future; -} - -impl<B, F, Fut, ReqBody, ResBody> AsyncAuthorizeRequest<B> for F -where - F: FnMut(Request<B>) -> Fut, - Fut: Future<Output = Result<Request<ReqBody>, Response<ResBody>>>, -{ - type RequestBody = ReqBody; - type ResponseBody = ResBody; - type Future = Fut; - - fn authorize(&mut self, request: Request<B>) -> Self::Future { - self(request) - } -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; - use crate::test_helpers::Body; - use futures_core::future::BoxFuture; - use http::{header, StatusCode}; - use tower::{BoxError, ServiceBuilder, ServiceExt}; - - #[derive(Clone, Copy)] - struct MyAuth; - - impl<B> AsyncAuthorizeRequest<B> for MyAuth - where - B: Send + 'static, - { - type RequestBody = B; - type ResponseBody = Body; - type Future = BoxFuture<'static, Result<Request<B>, Response<Self::ResponseBody>>>; - - fn authorize(&mut self, request: Request<B>) -> Self::Future { - Box::pin(async move { - let authorized = request - .headers() - .get(header::AUTHORIZATION) - .and_then(|auth| auth.to_str().ok()?.strip_prefix("Bearer ")) - == Some("69420"); - - if authorized { - Ok(request) - } else { - Err(Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body(Body::empty()) - .unwrap()) - } - }) - } - } - - #[tokio::test] - async fn require_async_auth_works() { - let mut service = ServiceBuilder::new() - .layer(AsyncRequireAuthorizationLayer::new(MyAuth)) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "Bearer 69420") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn require_async_auth_401() { - let mut service = ServiceBuilder::new() - .layer(AsyncRequireAuthorizationLayer::new(MyAuth)) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "Bearer deez") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - async fn echo(req: Request<Body>) -> Result<Response<Body>, BoxError> { - Ok(Response::new(req.into_body())) - } -} diff --git a/vendor/tower-http/src/auth/mod.rs b/vendor/tower-http/src/auth/mod.rs deleted file mode 100644 index fc8c2308..00000000 --- a/vendor/tower-http/src/auth/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Authorization related middleware. - -pub mod add_authorization; -pub mod async_require_authorization; -pub mod require_authorization; - -#[doc(inline)] -pub use self::{ - add_authorization::{AddAuthorization, AddAuthorizationLayer}, - async_require_authorization::{ - AsyncAuthorizeRequest, AsyncRequireAuthorization, AsyncRequireAuthorizationLayer, - }, -}; diff --git a/vendor/tower-http/src/auth/require_authorization.rs b/vendor/tower-http/src/auth/require_authorization.rs deleted file mode 100644 index 7aa1a87f..00000000 --- a/vendor/tower-http/src/auth/require_authorization.rs +++ /dev/null @@ -1,404 +0,0 @@ -//! Authorize requests using [`ValidateRequest`]. -//! -//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -//! -//! # Example -//! -//! ``` -//! use tower_http::validate_request::{ValidateRequest, ValidateRequestHeader, ValidateRequestHeaderLayer}; -//! use http::{Request, Response, StatusCode, header::AUTHORIZATION}; -//! use tower::{Service, ServiceExt, ServiceBuilder, service_fn, BoxError}; -//! use bytes::Bytes; -//! use http_body_util::Full; -//! -//! async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> { -//! Ok(Response::new(Full::default())) -//! } -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), BoxError> { -//! let mut service = ServiceBuilder::new() -//! // Require the `Authorization` header to be `Bearer passwordlol` -//! .layer(ValidateRequestHeaderLayer::bearer("passwordlol")) -//! .service_fn(handle); -//! -//! // Requests with the correct token are allowed through -//! let request = Request::builder() -//! .header(AUTHORIZATION, "Bearer passwordlol") -//! .body(Full::default()) -//! .unwrap(); -//! -//! let response = service -//! .ready() -//! .await? -//! .call(request) -//! .await?; -//! -//! assert_eq!(StatusCode::OK, response.status()); -//! -//! // Requests with an invalid token get a `401 Unauthorized` response -//! let request = Request::builder() -//! .body(Full::default()) -//! .unwrap(); -//! -//! let response = service -//! .ready() -//! .await? -//! .call(request) -//! .await?; -//! -//! assert_eq!(StatusCode::UNAUTHORIZED, response.status()); -//! # Ok(()) -//! # } -//! ``` -//! -//! Custom validation can be made by implementing [`ValidateRequest`]. - -use crate::validate_request::{ValidateRequest, ValidateRequestHeader, ValidateRequestHeaderLayer}; -use base64::Engine as _; -use http::{ - header::{self, HeaderValue}, - Request, Response, StatusCode, -}; -use std::{fmt, marker::PhantomData}; - -const BASE64: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD; - -impl<S, ResBody> ValidateRequestHeader<S, Basic<ResBody>> { - /// Authorize requests using a username and password pair. - /// - /// The `Authorization` header is required to be `Basic {credentials}` where `credentials` is - /// `base64_encode("{username}:{password}")`. - /// - /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS - /// with this method. However use of HTTPS/TLS is not enforced by this middleware. - pub fn basic(inner: S, username: &str, value: &str) -> Self - where - ResBody: Default, - { - Self::custom(inner, Basic::new(username, value)) - } -} - -impl<ResBody> ValidateRequestHeaderLayer<Basic<ResBody>> { - /// Authorize requests using a username and password pair. - /// - /// The `Authorization` header is required to be `Basic {credentials}` where `credentials` is - /// `base64_encode("{username}:{password}")`. - /// - /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS - /// with this method. However use of HTTPS/TLS is not enforced by this middleware. - pub fn basic(username: &str, password: &str) -> Self - where - ResBody: Default, - { - Self::custom(Basic::new(username, password)) - } -} - -impl<S, ResBody> ValidateRequestHeader<S, Bearer<ResBody>> { - /// Authorize requests using a "bearer token". Commonly used for OAuth 2. - /// - /// The `Authorization` header is required to be `Bearer {token}`. - /// - /// # Panics - /// - /// Panics if the token is not a valid [`HeaderValue`]. - pub fn bearer(inner: S, token: &str) -> Self - where - ResBody: Default, - { - Self::custom(inner, Bearer::new(token)) - } -} - -impl<ResBody> ValidateRequestHeaderLayer<Bearer<ResBody>> { - /// Authorize requests using a "bearer token". Commonly used for OAuth 2. - /// - /// The `Authorization` header is required to be `Bearer {token}`. - /// - /// # Panics - /// - /// Panics if the token is not a valid [`HeaderValue`]. - pub fn bearer(token: &str) -> Self - where - ResBody: Default, - { - Self::custom(Bearer::new(token)) - } -} - -/// Type that performs "bearer token" authorization. -/// -/// See [`ValidateRequestHeader::bearer`] for more details. -pub struct Bearer<ResBody> { - header_value: HeaderValue, - _ty: PhantomData<fn() -> ResBody>, -} - -impl<ResBody> Bearer<ResBody> { - fn new(token: &str) -> Self - where - ResBody: Default, - { - Self { - header_value: format!("Bearer {}", token) - .parse() - .expect("token is not a valid header value"), - _ty: PhantomData, - } - } -} - -impl<ResBody> Clone for Bearer<ResBody> { - fn clone(&self) -> Self { - Self { - header_value: self.header_value.clone(), - _ty: PhantomData, - } - } -} - -impl<ResBody> fmt::Debug for Bearer<ResBody> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Bearer") - .field("header_value", &self.header_value) - .finish() - } -} - -impl<B, ResBody> ValidateRequest<B> for Bearer<ResBody> -where - ResBody: Default, -{ - type ResponseBody = ResBody; - - fn validate(&mut self, request: &mut Request<B>) -> Result<(), Response<Self::ResponseBody>> { - match request.headers().get(header::AUTHORIZATION) { - Some(actual) if actual == self.header_value => Ok(()), - _ => { - let mut res = Response::new(ResBody::default()); - *res.status_mut() = StatusCode::UNAUTHORIZED; - Err(res) - } - } - } -} - -/// Type that performs basic authorization. -/// -/// See [`ValidateRequestHeader::basic`] for more details. -pub struct Basic<ResBody> { - header_value: HeaderValue, - _ty: PhantomData<fn() -> ResBody>, -} - -impl<ResBody> Basic<ResBody> { - fn new(username: &str, password: &str) -> Self - where - ResBody: Default, - { - let encoded = BASE64.encode(format!("{}:{}", username, password)); - let header_value = format!("Basic {}", encoded).parse().unwrap(); - Self { - header_value, - _ty: PhantomData, - } - } -} - -impl<ResBody> Clone for Basic<ResBody> { - fn clone(&self) -> Self { - Self { - header_value: self.header_value.clone(), - _ty: PhantomData, - } - } -} - -impl<ResBody> fmt::Debug for Basic<ResBody> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Basic") - .field("header_value", &self.header_value) - .finish() - } -} - -impl<B, ResBody> ValidateRequest<B> for Basic<ResBody> -where - ResBody: Default, -{ - type ResponseBody = ResBody; - - fn validate(&mut self, request: &mut Request<B>) -> Result<(), Response<Self::ResponseBody>> { - match request.headers().get(header::AUTHORIZATION) { - Some(actual) if actual == self.header_value => Ok(()), - _ => { - let mut res = Response::new(ResBody::default()); - *res.status_mut() = StatusCode::UNAUTHORIZED; - res.headers_mut() - .insert(header::WWW_AUTHENTICATE, "Basic".parse().unwrap()); - Err(res) - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::validate_request::ValidateRequestHeaderLayer; - - #[allow(unused_imports)] - use super::*; - use crate::test_helpers::Body; - use http::header; - use tower::{BoxError, ServiceBuilder, ServiceExt}; - use tower_service::Service; - - #[tokio::test] - async fn valid_basic_token() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::basic("foo", "bar")) - .service_fn(echo); - - let request = Request::get("/") - .header( - header::AUTHORIZATION, - format!("Basic {}", BASE64.encode("foo:bar")), - ) - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn invalid_basic_token() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::basic("foo", "bar")) - .service_fn(echo); - - let request = Request::get("/") - .header( - header::AUTHORIZATION, - format!("Basic {}", BASE64.encode("wrong:credentials")), - ) - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - - let www_authenticate = res.headers().get(header::WWW_AUTHENTICATE).unwrap(); - assert_eq!(www_authenticate, "Basic"); - } - - #[tokio::test] - async fn valid_bearer_token() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foobar")) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "Bearer foobar") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn basic_auth_is_case_sensitive_in_prefix() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::basic("foo", "bar")) - .service_fn(echo); - - let request = Request::get("/") - .header( - header::AUTHORIZATION, - format!("basic {}", BASE64.encode("foo:bar")), - ) - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - #[tokio::test] - async fn basic_auth_is_case_sensitive_in_value() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::basic("foo", "bar")) - .service_fn(echo); - - let request = Request::get("/") - .header( - header::AUTHORIZATION, - format!("Basic {}", BASE64.encode("Foo:bar")), - ) - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - #[tokio::test] - async fn invalid_bearer_token() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foobar")) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "Bearer wat") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - #[tokio::test] - async fn bearer_token_is_case_sensitive_in_prefix() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foobar")) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "bearer foobar") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - #[tokio::test] - async fn bearer_token_is_case_sensitive_in_token() { - let mut service = ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer("foobar")) - .service_fn(echo); - - let request = Request::get("/") - .header(header::AUTHORIZATION, "Bearer Foobar") - .body(Body::empty()) - .unwrap(); - - let res = service.ready().await.unwrap().call(request).await.unwrap(); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - } - - async fn echo(req: Request<Body>) -> Result<Response<Body>, BoxError> { - Ok(Response::new(req.into_body())) - } -} |
