use std::{collections::HashMap, sync::Arc};
use urlparse::{urlparse, GetQuery};
use crate::commons::KrillResult;
use crate::commons::{actor::ActorDef, api::Token};
use crate::daemon::auth::common::crypt;
use crate::daemon::auth::common::session::*;
use crate::daemon::auth::providers::config_file::config::ConfigUserDetails;
use crate::daemon::auth::{Auth, AuthProvider, LoggedInUser};
use crate::daemon::config::Config;
use crate::daemon::http::HttpResponse;
use crate::{commons::error::Error, daemon::auth::common::crypt::CryptState};
use crate::constants::{PW_HASH_LOG_N, PW_HASH_P, PW_HASH_R};
const LAGOSTA_LOGIN_ROUTE_PATH: &str = "/login?withId=true";
const LOGIN_SESSION_STATE_KEY_PATH: &str = "login_session_state.key";
struct UserDetails {
password_hash: Token,
salt: String,
attributes: HashMap<String, String>,
}
fn get_checked_config_user(id: &str, user: &ConfigUserDetails) -> KrillResult<UserDetails> {
let password_hash = user
.password_hash
.as_ref()
.ok_or_else(|| Error::ConfigError(format!("Password hash missing for user '{}'", id)))?
.to_string();
let salt = user
.salt
.as_ref()
.ok_or_else(|| Error::ConfigError(format!("Password salt missing for user '{}'", id)))?
.to_string();
Ok(UserDetails {
password_hash: Token::from(password_hash),
salt,
attributes: user.attributes.clone(),
})
}
pub struct ConfigFileAuthProvider {
users: HashMap<String, UserDetails>,
session_key: CryptState,
session_cache: Arc<LoginSessionCache>,
fake_password_hash: String,
fake_salt: String,
}
impl ConfigFileAuthProvider {
pub fn new(config: Arc<Config>, session_cache: Arc<LoginSessionCache>) -> KrillResult<Self> {
match &config.auth_users {
Some(auth_users) => {
let mut users = HashMap::new();
for (k, v) in auth_users.iter() {
users.insert(k.clone(), get_checked_config_user(k, v)?);
}
let session_key = Self::init_session_key(config.clone())?;
Ok(ConfigFileAuthProvider {
users,
session_key,
session_cache,
fake_password_hash: hex::encode("fake password hash"),
fake_salt: hex::encode("fake salt"),
})
}
None => Err(Error::ConfigError("Missing [auth_users] config section!".into())),
}
}
fn init_session_key(config: Arc<Config>) -> KrillResult<CryptState> {
let key_path = config.data_dir.join(LOGIN_SESSION_STATE_KEY_PATH);
info!("Initializing login session encryption key {}", &key_path.display());
crypt::crypt_init(key_path.as_path())
}
fn get_auth(&self, request: &hyper::Request<hyper::Body>) -> Option<Auth> {
if let Some(password_hash) = self.get_bearer_token(request) {
if let Some(query) = urlparse(request.uri().to_string()).get_parsed_query() {
if let Some(id) = query.get_first_from_str("id") {
return Some(Auth::IdAndPasswordHash { id, password_hash });
}
}
}
None
}
}
impl AuthProvider for ConfigFileAuthProvider {
fn authenticate(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<Option<ActorDef>> {
if log_enabled!(log::Level::Trace) {
trace!("Attempting to authenticate the request..");
}
let res = match self.get_bearer_token(request) {
Some(token) => {
let session = self.session_cache.decode(token, &self.session_key, true)?;
trace!("id={}, attributes={:?}", &session.id, &session.attributes);
Ok(Some(ActorDef::user(session.id, session.attributes, None)))
}
_ => Ok(None),
};
if log_enabled!(log::Level::Trace) {
trace!("Authentication result: {:?}", res);
}
res
}
fn get_login_url(&self) -> KrillResult<HttpResponse> {
Ok(HttpResponse::text_no_cache(LAGOSTA_LOGIN_ROUTE_PATH.into()))
}
fn login(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<LoggedInUser> {
if let Some(Auth::IdAndPasswordHash { id, password_hash }) = self.get_auth(request) {
use scrypt::scrypt;
let (user_password_hash, user_salt) = match self.users.get(&id) {
Some(user) => (user.password_hash.to_string(), user.salt.clone()),
None => (self.fake_password_hash.clone(), self.fake_salt.clone()),
};
let params = scrypt::Params::new(PW_HASH_LOG_N, PW_HASH_R, PW_HASH_P).unwrap();
let password_hash_bytes = hex::decode(password_hash.as_ref()).unwrap();
let strong_salt = hex::decode(&user_salt).unwrap();
let mut hashed_hash: [u8; 32] = [0; 32];
scrypt(
password_hash_bytes.as_slice(),
strong_salt.as_slice(),
¶ms,
&mut hashed_hash,
)
.unwrap();
if hex::encode(hashed_hash) == user_password_hash.as_ref() {
if let Some(user) = self.users.get(&id) {
let api_token =
self.session_cache
.encode(&id, &user.attributes, HashMap::new(), &self.session_key, None)?;
Ok(LoggedInUser {
token: api_token,
id: id.to_string(),
attributes: user.attributes.clone(),
})
} else {
trace!("Incorrect password for user {}", id);
Err(Error::ApiInvalidCredentials("Incorrect credentials".to_string()))
}
} else {
trace!("Unknown user {}", id);
Err(Error::ApiInvalidCredentials("Incorrect credentials".to_string()))
}
} else {
trace!("Missing pr incomplete credentials for login attempt");
Err(Error::ApiInvalidCredentials("Missing credentials".to_string()))
}
}
fn logout(&self, request: &hyper::Request<hyper::Body>) -> KrillResult<HttpResponse> {
match self.get_bearer_token(request) {
Some(token) => {
self.session_cache.remove(&token);
if let Ok(Some(actor)) = self.authenticate(request) {
info!("User logged out: {}", actor.name.as_str());
}
}
_ => {
warn!("Unexpectedly received a logout request without a session token.");
}
}
Ok(HttpResponse::text_no_cache("/".into()))
}
}