use std::sync::Arc;
use log::{info, log_enabled, trace};
use rpki::ca::idexchange::MyHandle;
use serde::Serialize;
use tokio::runtime;
use crate::api::admin::Token;
use crate::commons::KrillResult;
use crate::commons::actor::Actor;
use crate::commons::error::ApiAuthError;
use crate::config::{AuthType, Config};
#[cfg(unix)]
use crate::daemon::http::auth::providers::unix_user;
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
use super::{Permission, Role};
use super::providers::admin_token;
#[cfg(feature = "multi-user")]
use super::providers::{config_file, openid_connect};
enum AuthProvider {
Token(admin_token::AuthProvider),
#[cfg(feature = "multi-user")]
ConfigFile(config_file::AuthProvider),
#[cfg(feature = "multi-user")]
OpenIdConnect(openid_connect::AuthProvider),
}
impl From<admin_token::AuthProvider> for AuthProvider {
fn from(provider: admin_token::AuthProvider) -> Self {
AuthProvider::Token(provider)
}
}
#[cfg(feature = "multi-user")]
impl From<config_file::AuthProvider> for AuthProvider {
fn from(provider: config_file::AuthProvider) -> Self {
AuthProvider::ConfigFile(provider)
}
}
#[cfg(feature = "multi-user")]
impl From<openid_connect::AuthProvider> for AuthProvider {
fn from(provider: openid_connect::AuthProvider) -> Self {
AuthProvider::OpenIdConnect(provider)
}
}
impl AuthProvider {
pub async fn authenticate(
&self,
request: &HyperRequest,
) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
match &self {
AuthProvider::Token(provider) => provider.authenticate(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => {
provider.authenticate(request).await
}
#[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: &HyperRequest,
) -> KrillResult<LoggedInUser> {
match &self {
AuthProvider::Token(provider) => provider.login(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => {
provider.login(request).await
}
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => {
provider.login(request).await
}
}
}
pub async fn logout(
&self,
request: &HyperRequest,
) -> KrillResult<HttpResponse> {
match &self {
AuthProvider::Token(provider) => provider.logout(request),
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => {
provider.logout(request).await
}
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => {
provider.logout(request).await
}
}
}
pub async fn login_session_cache_size(&self) -> usize {
match self {
AuthProvider::Token(_) => 0,
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => {
provider.cache_size().await
}
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => {
provider.cache_size().await
}
}
}
#[allow(unused_variables)]
pub fn spawn_sweep(&self, runtime: &runtime::Handle) {
match self {
AuthProvider::Token(_) => { }
#[cfg(feature = "multi-user")]
AuthProvider::ConfigFile(provider) => {
provider.spawn_sweep(runtime)
}
#[cfg(feature = "multi-user")]
AuthProvider::OpenIdConnect(provider) => {
provider.spawn_sweep(runtime)
}
}
}
}
pub struct Authorizer {
primary_provider: AuthProvider,
legacy_provider: Option<admin_token::AuthProvider>,
#[cfg(unix)]
unix_socket_provider: unix_user::AuthProvider,
}
impl Authorizer {
pub fn new(
config: Arc<Config>,
) -> KrillResult<Self> {
let (primary_provider, legacy_provider) = match config.auth_type {
AuthType::AdminToken => {
(admin_token::AuthProvider::new(config.clone()).into(), None)
}
#[cfg(feature = "multi-user")]
AuthType::ConfigFile => {
(
config_file::AuthProvider::new(&config)?.into(),
Some(admin_token::AuthProvider::new(config.clone()))
)
}
#[cfg(feature = "multi-user")]
AuthType::OpenIDConnect => {
(
openid_connect::AuthProvider::new(config.clone())?.into(),
Some(admin_token::AuthProvider::new(config.clone()))
)
}
};
Ok(Authorizer {
primary_provider,
legacy_provider,
#[cfg(unix)]
unix_socket_provider: unix_user::AuthProvider::new(config.clone())?
})
}
pub async fn authenticate_request(
&self, request: &HyperRequest
) -> (AuthInfo, Option<Token>) {
trace!("Determining actor for request {:?}", &request);
let authenticate_res = match &self.legacy_provider {
Some(provider) => provider.authenticate(request),
None => Ok(None),
};
let authenticate_res = match authenticate_res {
Ok(Some(res)) => Ok(Some(res)),
_ => self.primary_provider.authenticate(request).await,
};
#[cfg(unix)]
let authenticate_res = match authenticate_res {
Ok(Some(res)) => Ok(Some(res)),
_ => self.unix_socket_provider.authenticate(request),
};
let res = match authenticate_res {
Ok(Some(res)) => res,
Ok(None) => (AuthInfo::anonymous(), None),
Err(err) => (AuthInfo::error(err), None),
};
trace!("AuthInfo determination result: {res:?}");
res
}
pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
self.primary_provider.get_login_url().await
}
pub async fn login(
&self, request: &HyperRequest
) -> KrillResult<LoggedInUser> {
let user = self.primary_provider.login(request).await?;
if log_enabled!(log::Level::Trace) {
trace!("User logged in: {:?}", &user);
} else {
info!("User logged in: {}, role: {}", user.id(), user.role());
}
Ok(user)
}
pub async fn logout(
&self,
request: &HyperRequest,
) -> KrillResult<HttpResponse> {
self.primary_provider.logout(request).await
}
pub async fn login_session_cache_size(&self) -> usize {
self.primary_provider.login_session_cache_size().await
}
pub fn spawn_sweep(&self, runtime: &runtime::Handle) {
self.primary_provider.spawn_sweep(runtime)
}
}
#[derive(Serialize, Debug)]
pub struct LoggedInUser {
token: Token,
id: Arc<str>,
attributes: LoggedInUserAttributes,
}
#[derive(Serialize, Debug)]
pub struct LoggedInUserAttributes {
role: Arc<str>,
}
impl LoggedInUser {
pub fn new(token: Token, id: Arc<str>, role: Arc<str>) -> Self {
LoggedInUser {
token,
id,
attributes: LoggedInUserAttributes { role }
}
}
pub fn token(&self) -> &Token {
&self.token
}
pub fn id(&self) -> &str {
&self.id
}
pub fn role(&self) -> &str {
self.attributes.role.as_ref()
}
pub fn attributes(&self) -> &impl Serialize {
&self.attributes
}
}
#[derive(Clone, Debug)]
pub struct AuthInfo {
actor: Actor,
permissions: Result<Arc<Role>, ApiAuthError>,
}
impl AuthInfo {
pub fn user(
user_id: impl Into<Arc<str>>,
role: Arc<Role>,
) -> Self {
Self {
actor: Actor::user(user_id),
permissions: Ok(role),
}
}
pub fn testbed() -> Self {
Self::user("testbed", Role::testbed().into())
}
fn anonymous() -> Self {
Self {
actor: Actor::anonymous(),
permissions: Ok(Role::anonymous().into()),
}
}
fn error(err: ApiAuthError) -> Self {
Self {
actor: Actor::anonymous(),
permissions: Err(err)
}
}
pub fn actor(&self) -> &Actor {
&self.actor
}
pub fn into_actor(self) -> Actor {
self.actor
}
pub fn has_permission(
&self,
permission: Permission,
resource: Option<&MyHandle>
) -> bool {
self.check_permission(permission, resource).is_ok()
}
pub fn check_permission(
&self,
permission: Permission,
resource: Option<&MyHandle>
) -> Result<(), ApiAuthError> {
if self.permissions.as_ref().map_err(Clone::clone)?
.is_allowed(permission, resource)
{
Ok(())
}
else {
Err(ApiAuthError::insufficient_rights(
&self.actor, permission, resource
))
}
}
}