use std::{any::Any, collections::HashMap, fmt, str::FromStr, sync::Arc};
use rpki::ca::idexchange::{InvalidHandle, MyHandle};
use crate::{
commons::{
actor::{Actor, ActorDef},
api::Token,
error::Error,
KrillResult,
},
constants::{ACTOR_DEF_ANON, NO_RESOURCE},
daemon::{
auth::{common::permissions::Permission, policy::AuthPolicy, providers::AdminTokenAuthProvider},
config::Config,
http::HttpResponse,
},
};
#[cfg(feature = "multi-user")]
use crate::daemon::auth::providers::{ConfigFileAuthProvider, OpenIDConnectAuthProvider};
pub enum AuthProvider {
Token(AdminTokenAuthProvider),
#[cfg(feature = "multi-user")]
ConfigFile(ConfigFileAuthProvider),
#[cfg(feature = "multi-user")]
OpenIdConnect(OpenIDConnectAuthProvider),
}
impl From<AdminTokenAuthProvider> for AuthProvider {
fn from(provider: AdminTokenAuthProvider) -> Self {
AuthProvider::Token(provider)
}
}
#[cfg(feature = "multi-user")]
impl From<ConfigFileAuthProvider> for AuthProvider {
fn from(provider: ConfigFileAuthProvider) -> Self {
AuthProvider::ConfigFile(provider)
}
}
#[cfg(feature = "multi-user")]
impl From<OpenIDConnectAuthProvider> for AuthProvider {
fn from(provider: OpenIDConnectAuthProvider) -> Self {
AuthProvider::OpenIdConnect(provider)
}
}
impl AuthProvider {
pub async fn authenticate(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<Option<ActorDef>> {
match &self {
AuthProvider::Token(provider) => provider.authenticate(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => provider.authenticate(request),
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => provider.authenticate(request).await,
}
}
pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
match &self {
AuthProvider::Token(provider) => provider.get_login_url(),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => provider.get_login_url(),
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => provider.get_login_url().await,
}
}
pub async fn login(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<LoggedInUser> {
match &self {
AuthProvider::Token(provider) => provider.login(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => provider.login(request),
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => provider.login(request).await,
}
}
pub async fn logout(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<HttpResponse> {
match &self {
AuthProvider::Token(provider) => provider.logout(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => provider.logout(request),
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => provider.logout(request).await,
}
}
}
pub struct Authorizer {
primary_provider: AuthProvider,
legacy_provider: Option<AdminTokenAuthProvider>,
policy: AuthPolicy,
private_attributes: Vec<String>,
}
impl Authorizer {
pub fn new(config: Arc<Config>, primary_provider: AuthProvider) -> KrillResult<Self> {
let value_any = &primary_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,
legacy_provider,
policy: AuthPolicy::new(config)?,
private_attributes,
})
}
pub async 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).await,
};
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 async fn get_login_url(&self) -> KrillResult<HttpResponse> {
self.primary_provider.get_login_url().await
}
pub async fn login(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<LoggedInUser> {
let user = self.primary_provider.login(request).await?;
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 async fn logout(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<HttpResponse> {
self.primary_provider.logout(request).await
}
}
#[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 }
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct Handle(MyHandle);
impl fmt::Display for Handle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<&MyHandle> for Handle {
fn from(h: &MyHandle) -> Self {
Handle(h.clone())
}
}
impl FromStr for Handle {
type Err = InvalidHandle;
fn from_str(s: &str) -> Result<Self, Self::Err> {
MyHandle::from_str(s).map(Handle)
}
}
impl AsRef<MyHandle> for Handle {
fn as_ref(&self) -> &MyHandle {
&self.0
}
}
#[cfg(feature = "multi-user")]
impl oso::PolarClass for Handle {
fn get_polar_class() -> oso::Class {
Self::get_polar_class_builder()
.set_constructor(|name: String| Handle::from_str(&name).unwrap())
.set_equality_check(|left: &Handle, right: &Handle| left == right)
.add_attribute_getter("name", |instance| instance.to_string())
.build()
}
fn get_polar_class_builder() -> oso::ClassBuilder<Self> {
oso::Class::builder()
}
}