use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use crate::commons::actor::{Actor, ActorDef};
use crate::commons::error::Error;
use crate::commons::KrillResult;
use crate::constants::{ACTOR_DEF_ANON, NO_RESOURCE};
use crate::daemon::auth::policy::AuthPolicy;
use crate::daemon::auth::providers::AdminTokenAuthProvider;
use crate::daemon::config::Config;
use crate::daemon::http::HttpResponse;
use crate::{commons::api::Token, daemon::auth::common::permissions::Permission};
pub trait AuthProvider: Send + Sync {
fn get_bearer_token(&self, request: &hyper::Request<hyper::Body>) -> Option<Token> {
if let Some(header) = request.headers().get("Authorization") {
if let Ok(header) = header.to_str() {
if header.len() > 6 {
let (bearer, token) = header.split_at(6);
let bearer = bearer.trim();
if "Bearer" == bearer {
return Some(Token::from(token.trim()));
}
}
}
}
None
}
fn authenticate(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<Option<ActorDef>>;
fn get_login_url(&self) -> KrillResult<HttpResponse>;
fn login(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<LoggedInUser>;
fn logout(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<HttpResponse>;
}
pub struct Authorizer {
primary_provider: Box<dyn AuthProvider>,
legacy_provider: Option<AdminTokenAuthProvider>,
policy: AuthPolicy,
private_attributes: Vec<String>,
}
impl Authorizer {
pub fn new<P>(config: Arc<Config>, provider: P) -> KrillResult<Self>
where
P: AuthProvider + Any,
{
let value_any = &provider as &dyn Any;
let is_admin_token_provider = value_any.downcast_ref::<AdminTokenAuthProvider>().is_some();
let legacy_provider = if is_admin_token_provider {
None
} else {
Some(AdminTokenAuthProvider::new(config.clone()))
};
#[cfg(feature = "multi-user")]
let private_attributes = config.auth_private_attributes.clone();
#[cfg(not(feature = "multi-user"))]
let private_attributes = vec!["role".to_string()];
Ok(Authorizer {
primary_provider: Box::new(provider),
legacy_provider,
policy: AuthPolicy::new(config)?,
private_attributes,
})
}
pub fn actor_from_request(&self, request: &hyper::Request<hyper::Body>) -> Actor {
trace!("Determining actor for request {:?}", &request);
let mut authenticate_res = match &self.legacy_provider {
Some(provider) => provider.authenticate(request),
None => Ok(None),
};
authenticate_res = match authenticate_res {
Ok(Some(res)) => Ok(Some(res)),
_ => self.primary_provider.authenticate(request),
};
let actor = match authenticate_res {
Ok(Some(actor_def)) => self.actor_from_def(actor_def),
Ok(None) => self.actor_from_def(ACTOR_DEF_ANON),
Err(err) => {
self.actor_from_def(ACTOR_DEF_ANON.with_auth_error(err))
}
};
trace!("Actor determination result: {:?}", &actor);
actor
}
pub fn actor_from_def(&self, def: ActorDef) -> Actor {
Actor::new(def, self.policy.clone())
}
pub fn get_login_url(&self) -> KrillResult<HttpResponse> {
self.primary_provider.get_login_url()
}
pub fn login(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<LoggedInUser> {
let user = self.primary_provider.login(request)?;
let actor_def = ActorDef::user(user.id.clone(), user.attributes.clone(), None);
let actor = self.actor_from_def(actor_def);
if !actor.is_allowed(Permission::LOGIN, NO_RESOURCE)? {
let reason = format!("Login denied for user '{}': User is not permitted to 'LOGIN'", user.id);
warn!("{}", reason);
return Err(Error::ApiInsufficientRights(reason));
}
let visible_attributes = user
.attributes
.clone()
.into_iter()
.filter(|(k, _)| !self.private_attributes.contains(k))
.collect::<HashMap<_, _>>();
let filtered_user = LoggedInUser {
token: user.token,
id: user.id,
attributes: visible_attributes,
};
if log_enabled!(log::Level::Trace) {
trace!("User logged in: {:?}", &filtered_user);
} else {
info!("User logged in: {}", &filtered_user.id);
}
Ok(filtered_user)
}
pub fn logout(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<HttpResponse> {
self.primary_provider.logout(request)
}
}
#[derive(Serialize, Debug)]
pub struct LoggedInUser {
pub token: Token,
pub id: String,
pub attributes: HashMap<String, String>,
}
#[derive(Clone, Debug)]
pub enum Auth {
Bearer(Token),
AuthorizationCode {
code: Token,
state: String,
nonce: String,
csrf_token_hash: String,
},
IdAndPasswordHash {
id: String,
password_hash: Token,
},
}
impl Auth {
pub fn bearer(token: Token) -> Self {
Auth::Bearer(token)
}
pub fn authorization_code(code: Token, state: String, nonce: String, csrf_token_hash: String) -> Self {
Auth::AuthorizationCode {
code,
state,
nonce,
csrf_token_hash,
}
}
pub fn id_and_password_hash(id: String, password_hash: Token) -> Self {
Auth::IdAndPasswordHash { id, password_hash }
}
}