-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduced handling of Authorization http header. For now "Bearer" to…
…ken authentication is being handled. For now tokens and usernames are defined in the config file of sidecar
- Loading branch information
Jakub Zajkowski
committed
Nov 20, 2023
1 parent
6db42f7
commit 2c613a1
Showing
9 changed files
with
613 additions
and
71 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,163 @@ | ||
use futures_util::future::BoxFuture; | ||
use http::{header::AUTHORIZATION, StatusCode}; | ||
use hyper::{Body, Request, Response}; | ||
use std::sync::Arc; | ||
use tower_http::auth::AsyncAuthorizeRequest; | ||
|
||
use super::{Authorizer, UserId, BEARER}; | ||
|
||
#[derive(Clone)] | ||
pub struct HeaderBasedAuthorizeRequest<T> | ||
where | ||
T: Authorizer + Send + Sync + 'static, | ||
{ | ||
api_token_authorizer: Arc<T>, | ||
} | ||
|
||
impl<T> HeaderBasedAuthorizeRequest<T> | ||
where | ||
T: Authorizer + Send + Sync + 'static, | ||
{ | ||
pub(crate) fn new(api_token_authorizer: T) -> Self { | ||
HeaderBasedAuthorizeRequest { | ||
api_token_authorizer: Arc::new(api_token_authorizer), | ||
} | ||
} | ||
} | ||
|
||
impl<B, T> AsyncAuthorizeRequest<B> for HeaderBasedAuthorizeRequest<T> | ||
where | ||
B: Send + Sync + 'static, | ||
T: Authorizer + Send + Sync + 'static, | ||
{ | ||
type RequestBody = B; | ||
type ResponseBody = Body; | ||
type Future = BoxFuture<'static, Result<Request<B>, Response<Self::ResponseBody>>>; | ||
|
||
fn authorize(&mut self, mut request: Request<B>) -> Self::Future { | ||
let authorizer = self.api_token_authorizer.clone(); | ||
Box::pin(async { | ||
if let Some(user_id) = check_auth(authorizer, &request).await { | ||
request.extensions_mut().insert(user_id); | ||
Ok(request) | ||
} else { | ||
Err(unauthorized_response()) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
async fn check_auth<T: Authorizer, B>( | ||
api_token_authorizer: Arc<T>, | ||
request: &Request<B>, | ||
) -> Option<UserId> { | ||
let maybe_token = request | ||
.headers() | ||
.get(AUTHORIZATION) | ||
.and_then(|header: &http::HeaderValue| header.to_str().ok()); | ||
if let Some(token) = maybe_token { | ||
let parts = token.split(' ').collect::<Vec<&str>>(); | ||
if parts.len() != 2 { | ||
return None; | ||
} | ||
if parts[0].to_lowercase() != BEARER { | ||
return None; | ||
} | ||
api_token_authorizer.authorize(parts[1].to_string()).await | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use async_trait::async_trait; | ||
|
||
struct MockAuthorizer { | ||
expected_token: Option<String>, | ||
user_id: Option<UserId>, | ||
} | ||
|
||
impl MockAuthorizer { | ||
fn new(expected_token: Option<String>, user_id: Option<UserId>) -> Self { | ||
MockAuthorizer { | ||
expected_token, | ||
user_id, | ||
} | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl Authorizer for MockAuthorizer { | ||
async fn authorize(&self, token: String) -> Option<UserId> { | ||
if let Some(expected_token) = self.expected_token.clone() { | ||
if expected_token != token { | ||
unreachable!("Expected token {} but got {}", expected_token, token); | ||
} | ||
} | ||
self.user_id.clone() | ||
} | ||
} | ||
|
||
#[tokio::test] | ||
async fn should_fail_authorization_if_no_token_given() { | ||
let result = prepare_and_authorize(None, None, None).await; | ||
assert!(result.is_err()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn should_fail_authorization_if_no_user_for_token() { | ||
let result = prepare_and_authorize(Some("xyz"), None, Some("Bearer xyz")).await; | ||
assert!(result.is_err()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn should_fail_authorization_if_bearer_prefix_missing() { | ||
let result = prepare_and_authorize(Some("xyz"), Some("user_1"), Some("xyz")).await; | ||
assert!(result.is_err()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn should_fail_authorization_if_correct_token_under_wrong_method() { | ||
let result = prepare_and_authorize(Some("abc"), Some("user_1"), Some("Basic abc")).await; | ||
assert!(result.is_err()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn should_pass() { | ||
let result = prepare_and_authorize(Some("abc"), Some("user_1"), Some("Bearer abc")).await; | ||
let user_id = result | ||
.unwrap() | ||
.extensions() | ||
.get::<UserId>() | ||
.unwrap() | ||
.clone(); | ||
assert_eq!(user_id, UserId("user_1".into())); | ||
} | ||
|
||
async fn prepare_and_authorize( | ||
expected_token: Option<&str>, | ||
user_id: Option<&str>, | ||
authorization_header: Option<&str>, | ||
) -> Result<Request<Body>, Response<Body>> { | ||
let authorizer = MockAuthorizer::new( | ||
expected_token.map(|token| token.to_string()), | ||
user_id.map(|id| UserId(id.to_string())), | ||
); | ||
let mut auth = HeaderBasedAuthorizeRequest::new(authorizer); | ||
let mut builder = Request::builder(); | ||
if let Some(header) = authorization_header { | ||
builder = builder.header("Authorization", header); | ||
} | ||
let request = builder.body(Body::empty()).unwrap(); | ||
auth.authorize(request).await | ||
} | ||
} | ||
|
||
fn unauthorized_response() -> Response<Body> { | ||
Response::builder() | ||
.status(StatusCode::UNAUTHORIZED) | ||
.body(b"Unauthorized".to_vec().into()) | ||
.unwrap() | ||
} |
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,59 @@ | ||
use crate::types::config::AuthConfig; | ||
use async_trait::async_trait; | ||
pub use layer::HeaderBasedAuthorizeRequest; | ||
use std::collections::HashMap; | ||
mod layer; | ||
|
||
const BEARER: &str = "bearer"; | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
pub struct UserId(String); | ||
|
||
#[async_trait] | ||
pub trait Authorizer { | ||
async fn authorize(&self, token: String) -> Option<UserId>; | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct BearerAuthorizer { | ||
api_key_to_username: HashMap<String, String>, | ||
} | ||
|
||
#[async_trait] | ||
impl Authorizer for BearerAuthorizer { | ||
async fn authorize(&self, token: String) -> Option<UserId> { | ||
self.api_key_to_username | ||
.get(&token) | ||
.map(|username| UserId(username.clone())) | ||
} | ||
} | ||
|
||
impl BearerAuthorizer { | ||
pub fn new(token_user_map: HashMap<String, String>) -> Self { | ||
BearerAuthorizer { | ||
api_key_to_username: token_user_map, | ||
} | ||
} | ||
} | ||
|
||
pub enum AuthorizationValidator { | ||
SimpleAuthorization(BearerAuthorizer), | ||
NoAuthorization, | ||
} | ||
|
||
pub fn build_authorization_validator(auth_config: &Option<AuthConfig>) -> AuthorizationValidator { | ||
if let Some(auth) = auth_config { | ||
if auth.enabled { | ||
let token_to_user = auth | ||
.users | ||
.iter() | ||
.map(|user| (user.api_key.clone(), user.username.clone())) | ||
.collect(); | ||
AuthorizationValidator::SimpleAuthorization(BearerAuthorizer::new(token_to_user)) | ||
} else { | ||
AuthorizationValidator::NoAuthorization | ||
} | ||
} else { | ||
AuthorizationValidator::NoAuthorization | ||
} | ||
} |
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
Oops, something went wrong.