use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use crate::error::InternalError;
use crate::rest_api::auth::identity::Identity;
use super::{AuthorizationHandler, AuthorizationHandlerResult};
#[derive(Clone)]
pub struct AllowKeysAuthorizationHandler {
internal: Arc<Mutex<Internal>>,
}
impl AllowKeysAuthorizationHandler {
pub fn new(file_path: &str) -> Result<Self, InternalError> {
Ok(Self {
internal: Arc::new(Mutex::new(Internal::new(file_path)?)),
})
}
}
impl AuthorizationHandler for AllowKeysAuthorizationHandler {
fn has_permission(
&self,
identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
match identity {
Identity::Key(key)
if self
.internal
.lock()
.map_err(|_| {
InternalError::with_message(
"allow keys authorization handler internal lock poisoned".into(),
)
})?
.get_keys()
.contains(key) =>
{
Ok(AuthorizationHandlerResult::Allow)
}
_ => Ok(AuthorizationHandlerResult::Continue),
}
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
struct Internal {
file_path: String,
cached_keys: Vec<String>,
last_read: SystemTime,
}
impl Internal {
fn new(file_path: &str) -> Result<Self, InternalError> {
let mut internal = Self {
file_path: file_path.into(),
cached_keys: vec![],
last_read: SystemTime::UNIX_EPOCH,
};
if PathBuf::from(file_path).is_file() {
internal.read_keys()?;
} else {
internal.last_read = SystemTime::now();
}
Ok(internal)
}
fn get_keys(&mut self) -> &[String] {
let file_read_result = std::fs::metadata(&self.file_path)
.and_then(|metadata| metadata.modified())
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"failed to read allow keys file's last modification time".into(),
)
})
.and_then(|last_modified| {
if last_modified > self.last_read {
self.read_keys()
} else {
Ok(())
}
});
if let Err(err) = file_read_result {
warn!("Failed to read from allow keys file: {}", err);
self.cached_keys.clear();
}
&self.cached_keys
}
fn read_keys(&mut self) -> Result<(), InternalError> {
let file = File::open(&self.file_path).map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"failed to open allow keys file".into(),
)
})?;
let keys = BufReader::new(file)
.lines()
.enumerate()
.filter_map(|(idx, res)| {
match res {
Ok(line) => Some(line),
Err(err) => {
error!(
"Failed to read key from line {} of allow keys file: {}",
idx + 1, err
);
None
}
}
})
.collect();
self.cached_keys = keys;
self.last_read = SystemTime::now();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::remove_file;
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;
use tempdir::TempDir;
const KEY1: &str = "012345";
const KEY2: &str = "abcdef";
#[test]
fn auth_handler_unexpected_identity() {
let temp_dir =
TempDir::new("auth_handler_unexpected_identity").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
write_to_file(&[KEY1], &path);
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
#[cfg(any(feature = "biome-credentials", feature = "oauth"))]
assert!(matches!(
handler.has_permission(&Identity::User("user_id".into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
}
#[test]
fn auth_handler_unknown_key() {
let temp_dir = TempDir::new("auth_handler_unknown_key").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
write_to_file(&[KEY1], &path);
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
assert!(matches!(
handler.has_permission(&Identity::Key(KEY2.into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
}
#[test]
fn auth_handler_allow() {
let temp_dir = TempDir::new("auth_handler_allow").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
write_to_file(&[KEY1, KEY2], &path);
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
assert!(matches!(
handler.has_permission(&Identity::Key(KEY1.into()), "permission"),
Ok(AuthorizationHandlerResult::Allow),
));
assert!(matches!(
handler.has_permission(&Identity::Key(KEY2.into()), "permission"),
Ok(AuthorizationHandlerResult::Allow),
));
}
#[test]
fn reload_modified_file() {
let temp_dir = TempDir::new("reload_modified_file").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
write_to_file(&[], &path);
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
sleep(Duration::from_secs(1));
write_to_file(&[KEY1, KEY2], &path);
assert!(matches!(
handler.has_permission(&Identity::Key(KEY1.into()), "permission"),
Ok(AuthorizationHandlerResult::Allow),
));
assert!(matches!(
handler.has_permission(&Identity::Key(KEY2.into()), "permission"),
Ok(AuthorizationHandlerResult::Allow),
));
sleep(Duration::from_secs(1));
write_to_file(&[KEY1], &path);
assert!(matches!(
handler.has_permission(&Identity::Key(KEY2.into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
}
#[test]
fn file_removed() {
let temp_dir = TempDir::new("file_removed").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
write_to_file(&[KEY1], &path);
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
remove_file(&path).expect("Failed to remove file");
assert!(!PathBuf::from(&path).exists());
assert!(matches!(
handler.has_permission(&Identity::Key(KEY1.into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
}
fn write_to_file(keys: &[&str], file_path: &str) {
let mut file = File::create(file_path).expect("Failed to create allow keys file");
for key in keys {
writeln!(file, "{}", key).expect("Failed to write key to file");
}
}
#[test]
fn load_after_file_created() {
let temp_dir = TempDir::new("load_after_file_created").expect("Failed to create temp dir");
let path = temp_dir
.path()
.join("allow_keys")
.to_str()
.expect("Failed to get path")
.to_string();
let handler = AllowKeysAuthorizationHandler::new(&path).expect("Failed to create handler");
assert!(matches!(
handler.has_permission(&Identity::Key(KEY1.into()), "permission"),
Ok(AuthorizationHandlerResult::Continue),
));
sleep(Duration::from_secs(1));
write_to_file(&[KEY1], &path);
assert!(matches!(
handler.has_permission(&Identity::Key(KEY1.into()), "permission"),
Ok(AuthorizationHandlerResult::Allow),
));
}
}