#[cfg(not(test))]
const ROOTASROLE: &str = "/etc/security/rootasrole.json";
#[cfg(test)]
const ROOTASROLE: &str = "target/rootasrole.json";
use std::{cell::RefCell, error::Error, ffi::OsStr, path::PathBuf, rc::Rc};
use serde::{Deserialize, Serialize};
use log::debug;
pub mod api;
pub mod database;
pub mod plugin;
pub mod util;
pub mod version;
use util::{
dac_override_effective, open_with_privileges, read_effective, toggle_lock_config,
write_json_config, ImmutableLock,
};
use database::{
migration::Migration,
structs::SConfig,
versionning::{Versioning, JSON_MIGRATIONS, SETTINGS_MIGRATIONS},
};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum StorageMethod {
JSON,
#[serde(other)]
Unknown,
}
pub enum Storage {
JSON(Rc<RefCell<SConfig>>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SettingsFile {
pub storage: Settings,
#[serde(flatten)]
pub config: Rc<RefCell<SConfig>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Settings {
pub method: StorageMethod,
#[serde(skip_serializing_if = "Option::is_none")]
pub settings: Option<RemoteStorageSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ldap: Option<LdapSettings>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RemoteStorageSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub immutable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<ConnectionAuth>,
#[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub table_prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Properties>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectionAuth {
pub user: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_ssl: Option<ClientSsl>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientSsl {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub ca_cert: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_cert: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_key: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Properties {
pub use_unicode: bool,
pub character_encoding: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LdapSettings {
pub enabled: bool,
pub host: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<ConnectionAuth>,
pub base_dn: String,
pub user_dn: String,
pub group_dn: String,
pub user_filter: String,
pub group_filter: String,
}
impl Default for SettingsFile {
fn default() -> Self {
Self {
storage: Settings::default(),
config: Rc::new(RefCell::new(SConfig::default())),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
method: StorageMethod::JSON,
settings: Some(RemoteStorageSettings::default()),
ldap: None,
}
}
}
impl Default for RemoteStorageSettings {
fn default() -> Self {
Self {
immutable: None,
path: Some(ROOTASROLE.into()),
host: None,
port: None,
auth: None,
database: None,
schema: None,
table_prefix: None,
properties: None,
}
}
}
pub fn save_settings(settings: Rc<RefCell<SettingsFile>>) -> Result<(), Box<dyn Error>> {
let default_remote: RemoteStorageSettings = RemoteStorageSettings::default();
let into = ROOTASROLE.into();
let binding = settings.as_ref().borrow();
let path = binding
.storage
.settings
.as_ref()
.unwrap_or(&default_remote)
.path
.as_ref()
.unwrap_or(&into);
if let Some(settings) = &settings.as_ref().borrow().storage.settings {
if settings.immutable.unwrap_or(true) {
debug!("Toggling immutable on for config file");
toggle_lock_config(path, ImmutableLock::Unset)?;
}
}
debug!("Writing config file");
let versionned: Versioning<Rc<RefCell<SettingsFile>>> = Versioning::new(settings.clone());
write_json_config(&versionned, ROOTASROLE)?;
if let Some(settings) = &settings.as_ref().borrow().storage.settings {
if settings.immutable.unwrap_or(true) {
debug!("Toggling immutable off for config file");
toggle_lock_config(path, ImmutableLock::Set)?;
}
}
debug!("Resetting dac privilege");
dac_override_effective(false)?;
Ok(())
}
pub fn get_settings<S>(path: &S) -> Result<Rc<RefCell<SettingsFile>>, Box<dyn Error>>
where
S: AsRef<OsStr> + ?Sized,
{
if !std::path::Path::new(path.as_ref()).exists() {
return Ok(rc_refcell!(SettingsFile::default()));
}
let file = open_with_privileges(path.as_ref())?;
let value: Versioning<SettingsFile> = serde_json::from_reader(file)
.inspect_err(|e| {
debug!("Error reading file: {}", e);
})
.unwrap_or_default();
read_effective(false).or(dac_override_effective(false))?;
debug!("{}", serde_json::to_string_pretty(&value)?);
let settingsfile = rc_refcell!(value.data);
if let Ok(true) = Migration::migrate(
&value.version,
&mut *settingsfile.as_ref().borrow_mut(),
SETTINGS_MIGRATIONS,
) {
if let Ok(true) = Migration::migrate(
&value.version,
&mut *settingsfile
.as_ref()
.borrow_mut()
.config
.as_ref()
.borrow_mut(),
JSON_MIGRATIONS,
) {
save_settings(settingsfile.clone())?;
} else {
debug!("No config migrations needed");
}
} else {
debug!("No settings migrations needed");
}
Ok(settingsfile)
}