use super::credential_cache::CredentialCache;
use super::htpasswd::HtpasswdFile;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::sync::Arc;
use swagger::{
ApiError,
auth::{Authorization, Basic, Bearer},
};
pub type SharedHtpasswd = Arc<RwLock<Option<HtpasswdFile>>>;
pub type SharedCredentialCache = Arc<RwLock<Option<CredentialCache>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
#[derive(Clone)]
pub struct HtpasswdAuthenticator {
htpasswd: Option<HtpasswdFile>,
require_auth: bool,
}
impl HtpasswdAuthenticator {
pub fn new(htpasswd: Option<HtpasswdFile>, require_auth: bool) -> Self {
HtpasswdAuthenticator {
htpasswd,
require_auth,
}
}
fn create_authorization(username: String) -> Authorization {
Authorization {
subject: username,
scopes: swagger::auth::Scopes::Some(BTreeSet::new()),
issuer: None,
}
}
fn unauthorized_error() -> ApiError {
ApiError("Unauthorized: Invalid username or password".to_string())
}
fn auth_required_error() -> ApiError {
ApiError("Unauthorized: Authentication required".to_string())
}
}
impl AuthenticationApi for HtpasswdAuthenticator {
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
if self.require_auth {
Err(Self::auth_required_error())
} else {
Ok(Self::create_authorization("anonymous".to_string()))
}
}
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
if self.require_auth {
Err(Self::auth_required_error())
} else {
Ok(Self::create_authorization("anonymous".to_string()))
}
}
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
match &self.htpasswd {
Some(htpasswd) => {
let password = match &basic.password {
Some(pwd) => pwd,
None => {
log::warn!(
"Authentication failed for user '{}': no password provided",
basic.username
);
return Err(Self::unauthorized_error());
}
};
if htpasswd.verify(&basic.username, password) {
log::debug!("User '{}' authenticated successfully", basic.username);
Ok(Self::create_authorization(basic.username.clone()))
} else {
log::warn!("Authentication failed for user '{}'", basic.username);
Err(Self::unauthorized_error())
}
}
None => {
if self.require_auth {
log::warn!("Authentication required but no htpasswd file configured");
Err(Self::auth_required_error())
} else {
log::debug!("No authentication configured, allowing request");
Ok(Self::create_authorization("anonymous".to_string()))
}
}
}
}
}
use futures::future::FutureExt;
use hyper::Request;
use hyper::service::Service;
use std::marker::PhantomData;
use std::task::{Context as TaskContext, Poll};
use swagger::auth::{RcBound, Scopes, from_headers};
#[derive(Debug)]
pub struct MakeHtpasswdAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
inner: T,
htpasswd: SharedHtpasswd,
require_auth: bool,
credential_cache: SharedCredentialCache,
marker: PhantomData<RC>,
}
impl<T, RC> MakeHtpasswdAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
pub fn new(
inner: T,
htpasswd: SharedHtpasswd,
require_auth: bool,
credential_cache: SharedCredentialCache,
) -> Self {
MakeHtpasswdAuthenticator {
inner,
htpasswd,
require_auth,
credential_cache,
marker: PhantomData,
}
}
}
impl<Inner, RC, Target> Service<Target> for MakeHtpasswdAuthenticator<Inner, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
Inner: Service<Target>,
Inner::Future: Send + 'static,
{
type Error = Inner::Error;
type Response = HtpasswdAuthenticatorService<Inner::Response, RC>;
type Future = futures::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut TaskContext<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, target: Target) -> Self::Future {
let htpasswd = self.htpasswd.clone();
let require_auth = self.require_auth;
let credential_cache = self.credential_cache.clone();
Box::pin(self.inner.call(target).map(move |s| {
Ok(HtpasswdAuthenticatorService {
inner: s?,
htpasswd,
require_auth,
credential_cache,
marker: PhantomData,
})
}))
}
}
#[derive(Debug)]
pub struct HtpasswdAuthenticatorService<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
inner: T,
htpasswd: SharedHtpasswd,
require_auth: bool,
credential_cache: SharedCredentialCache,
marker: PhantomData<RC>,
}
impl<T, RC> HtpasswdAuthenticatorService<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
fn verify_with_cache(&self, htpasswd: &HtpasswdFile, username: &str, password: &str) -> bool {
let cache_guard = self.credential_cache.read();
if let Some(ref cache) = *cache_guard
&& cache.is_cached(username, password)
{
log::debug!("User '{}' authenticated from cache", username);
return true;
}
drop(cache_guard);
if htpasswd.verify(username, password) {
let cache_guard = self.credential_cache.read();
if let Some(ref cache) = *cache_guard {
cache.cache_success(username, password);
}
true
} else {
false
}
}
}
impl<T, RC> Clone for HtpasswdAuthenticatorService<T, RC>
where
T: Clone,
RC: RcBound,
RC::Result: Send + 'static,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
htpasswd: Arc::clone(&self.htpasswd),
require_auth: self.require_auth,
credential_cache: Arc::clone(&self.credential_cache),
marker: PhantomData,
}
}
}
impl<T, RC> AuthenticationApi for HtpasswdAuthenticatorService<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, swagger::ApiError> {
if self.require_auth {
Err(swagger::ApiError(
"Unauthorized: Authentication required".to_string(),
))
} else {
Ok(Authorization {
subject: "anonymous".to_string(),
scopes: Scopes::All,
issuer: None,
})
}
}
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, swagger::ApiError> {
if self.require_auth {
Err(swagger::ApiError(
"Unauthorized: Authentication required".to_string(),
))
} else {
Ok(Authorization {
subject: "anonymous".to_string(),
scopes: Scopes::All,
issuer: None,
})
}
}
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, swagger::ApiError> {
let htpasswd_guard = self.htpasswd.read();
match &*htpasswd_guard {
Some(htpasswd) => {
let password = match &basic.password {
Some(pwd) => pwd,
None => {
log::warn!(
"Authentication failed for user '{}': no password provided",
basic.username
);
return Err(swagger::ApiError(
"Unauthorized: Invalid username or password".to_string(),
));
}
};
if self.verify_with_cache(htpasswd, &basic.username, password) {
log::debug!("User '{}' authenticated successfully", basic.username);
Ok(Authorization {
subject: basic.username.clone(),
scopes: Scopes::All,
issuer: None,
})
} else {
log::warn!("Authentication failed for user '{}'", basic.username);
Err(swagger::ApiError(
"Unauthorized: Invalid username or password".to_string(),
))
}
}
None => {
if self.require_auth {
log::warn!("Authentication required but no htpasswd file configured");
Err(swagger::ApiError(
"Unauthorized: Authentication required".to_string(),
))
} else {
log::debug!("No authentication configured, allowing request");
Ok(Authorization {
subject: "anonymous".to_string(),
scopes: Scopes::All,
issuer: None,
})
}
}
}
}
}
impl<T, B, RC> Service<(Request<B>, RC)> for HtpasswdAuthenticatorService<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
T: Service<(Request<B>, RC::Result)>,
T::Response: From<hyper::Response<hyper::Body>>,
{
type Response = T::Response;
type Error = T::Error;
type Future =
futures::future::Either<futures::future::Ready<Result<T::Response, T::Error>>, T::Future>;
fn poll_ready(&mut self, cx: &mut TaskContext<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: (Request<B>, RC)) -> Self::Future {
let (request, context) = req;
let basic_auth: Option<Basic> = from_headers(request.headers());
let htpasswd_guard = self.htpasswd.read();
let authorization = match &*htpasswd_guard {
Some(htpasswd) => {
match basic_auth {
Some(basic) => {
let password = basic.password.as_deref().unwrap_or("");
if self.verify_with_cache(htpasswd, &basic.username, password) {
log::debug!("User '{}' authenticated successfully", basic.username);
Some(swagger::auth::Authorization {
subject: basic.username.clone(),
scopes: Scopes::All,
issuer: None,
})
} else {
log::warn!("Authentication failed for user '{}'", basic.username);
None
}
}
None => {
if self.require_auth {
log::warn!("Authentication required but no credentials provided");
None
} else {
log::debug!("No credentials provided, allowing anonymous access");
Some(swagger::auth::Authorization {
subject: "anonymous".to_string(),
scopes: Scopes::All,
issuer: None,
})
}
}
}
}
None => {
if self.require_auth {
log::warn!("Authentication required but no htpasswd file configured");
None
} else {
log::debug!("No authentication configured, allowing request");
Some(swagger::auth::Authorization {
subject: "anonymous".to_string(),
scopes: Scopes::All,
issuer: None,
})
}
}
};
drop(htpasswd_guard);
if self.require_auth && authorization.is_none() {
let response = hyper::Response::builder()
.status(hyper::StatusCode::UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"Torc\"")
.body(hyper::Body::from("Unauthorized"))
.unwrap();
return futures::future::Either::Left(futures::future::ready(Ok(response.into())));
}
let context = context.push(authorization);
futures::future::Either::Right(self.inner.call((request, context)))
}
}
use swagger::auth::AllowAllAuthenticator;
fn dummy_authorization() -> Authorization {
Authorization {
subject: "Dummy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), issuer: None,
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static,
{
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}