rootasrole_core/
lib.rs

1// Let's define a serde configuration struct to define the database type and connection string
2// example in json:
3// {
4//     "storage_method": "sqlite", // storage method is where roles and permissions are stored
5//     "storage_settings": {
6//       "path": "/path/to/sqlite.db"
7//       "host": "localhost",
8//       "port": 5432,
9//       "auth": {
10//         "user": "user",
11//         "password": "password",
12//         "client_ssl": {
13//           "ca_cert": "/path/to/ca_cert",
14//           "client_cert": "/path/to/client_cert",
15//           "client_key": "/path/to/client_key"
16//         }
17//       },
18//       // when using rdbms as storage method
19//       "database": "database",
20//       "schema": "schema",
21//       "table_prefix": "rar_",
22//       "properties": {
23//         "use_unicode": true,
24//         "character_encoding": "utf8"
25//       },
26//       // when using ldap as storage method
27//       "role_dn": "ou=roles",
28//     },
29//     "ldap": { // when using ldap for user and groups definition storage
30//       "enabled": false,
31//       "host": "localhost",
32//       "port": 389,
33//       "auth": {
34//         "user": "user",
35//         "password": "password"
36//         "client_ssl": {
37//           "ca_cert": "/path/to/ca_cert",
38//           "client_cert": "/path/to/client_cert",
39//           "client_key": "/path/to/client_key"
40//         }
41//       },
42//       "base_dn": "dc=example,dc=com",
43//       "user_dn": "ou=users",
44//       "group_dn": "ou=groups",
45//       "user_filter": "(&(objectClass=person)(sAMAccountName=%s))",
46//       "group_filter": "(&(objectClass=group)(member=%s))"
47//     }
48//   }
49
50#[cfg(not(test))]
51const ROOTASROLE: &str = "/etc/security/rootasrole.json";
52#[cfg(test)]
53const ROOTASROLE: &str = "target/rootasrole.json";
54
55use std::{cell::RefCell, error::Error, ffi::OsStr, path::PathBuf, rc::Rc};
56
57use bon::Builder;
58use log::debug;
59use serde::{Deserialize, Serialize};
60
61pub mod api;
62pub mod database;
63pub mod plugin;
64pub mod util;
65pub mod version;
66
67use util::{
68    dac_override_effective, open_with_privileges, read_effective, toggle_lock_config,
69    write_json_config, ImmutableLock,
70};
71
72use database::{
73    migration::Migration,
74    structs::SConfig,
75    versionning::{Versioning, JSON_MIGRATIONS, SETTINGS_MIGRATIONS},
76};
77
78#[derive(Serialize, Deserialize, Debug, Clone)]
79#[serde(rename_all = "lowercase")]
80pub enum StorageMethod {
81    JSON,
82    //    SQLite,
83    //    PostgreSQL,
84    //    MySQL,
85    //    LDAP,
86    #[serde(other)]
87    Unknown,
88}
89
90pub enum Storage {
91    JSON(Rc<RefCell<SConfig>>),
92}
93
94#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
95pub struct SettingsFile {
96    pub storage: Settings,
97    #[serde(flatten)]
98    pub config: Rc<RefCell<SConfig>>,
99}
100
101#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
102pub struct Settings {
103    #[builder(default = StorageMethod::JSON)]
104    pub method: StorageMethod,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub settings: Option<RemoteStorageSettings>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub ldap: Option<LdapSettings>,
109}
110
111#[derive(Serialize, Deserialize, Debug, Clone, Builder, Default)]
112pub struct RemoteStorageSettings {
113    #[serde(skip_serializing_if = "Option::is_none")]
114    #[builder(name = not_immutable,with = || false)]
115    pub immutable: Option<bool>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    #[builder(into)]
118    pub path: Option<PathBuf>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub host: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub port: Option<u16>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub auth: Option<ConnectionAuth>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub database: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub schema: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub table_prefix: Option<String>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub properties: Option<Properties>,
133}
134
135#[derive(Serialize, Deserialize, Debug, Clone)]
136pub struct ConnectionAuth {
137    pub user: String,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub password: Option<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub client_ssl: Option<ClientSsl>,
142}
143
144#[derive(Serialize, Deserialize, Debug, Clone)]
145pub struct ClientSsl {
146    pub enabled: bool,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub ca_cert: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub client_cert: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub client_key: Option<String>,
153}
154
155#[derive(Serialize, Deserialize, Debug, Clone)]
156pub struct Properties {
157    pub use_unicode: bool,
158    pub character_encoding: String,
159}
160
161#[derive(Serialize, Deserialize, Debug, Clone)]
162pub struct LdapSettings {
163    pub enabled: bool,
164    pub host: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub port: Option<u16>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub auth: Option<ConnectionAuth>,
169    pub base_dn: String,
170    pub user_dn: String,
171    pub group_dn: String,
172    pub user_filter: String,
173    pub group_filter: String,
174}
175
176impl Default for SettingsFile {
177    fn default() -> Self {
178        Self {
179            storage: Settings::default(),
180            config: Rc::new(RefCell::new(SConfig::default())),
181        }
182    }
183}
184
185// Default implementation for Settings
186impl Default for Settings {
187    fn default() -> Self {
188        Self {
189            method: StorageMethod::JSON,
190            settings: None,
191            ldap: None,
192        }
193    }
194}
195
196pub fn save_settings(settings: Rc<RefCell<SettingsFile>>) -> Result<(), Box<dyn Error>> {
197    let default_remote: RemoteStorageSettings = RemoteStorageSettings::default();
198    // remove immutable flag
199    let into = ROOTASROLE.into();
200    let binding = settings.as_ref().borrow();
201    let path = binding
202        .storage
203        .settings
204        .as_ref()
205        .unwrap_or(&default_remote)
206        .path
207        .as_ref()
208        .unwrap_or(&into);
209    if let Some(settings) = &settings.as_ref().borrow().storage.settings {
210        if settings.immutable.unwrap_or(true) {
211            debug!("Toggling immutable on for config file");
212            toggle_lock_config(path, ImmutableLock::Unset)?;
213        }
214    }
215    debug!("Writing config file");
216    let versionned: Versioning<Rc<RefCell<SettingsFile>>> = Versioning::new(settings.clone());
217    write_json_config(&versionned, ROOTASROLE)?;
218    if let Some(settings) = &settings.as_ref().borrow().storage.settings {
219        if settings.immutable.unwrap_or(true) {
220            debug!("Toggling immutable off for config file");
221            toggle_lock_config(path, ImmutableLock::Set)?;
222        }
223    }
224    debug!("Resetting dac privilege");
225    dac_override_effective(false)?;
226    Ok(())
227}
228
229pub fn get_settings<S>(path: &S) -> Result<Rc<RefCell<SettingsFile>>, Box<dyn Error>>
230where
231    S: AsRef<OsStr> + ?Sized,
232{
233    // if file does not exist, return default settings
234    if !std::path::Path::new(path.as_ref()).exists() {
235        return Ok(rc_refcell!(SettingsFile::default()));
236    }
237    // if user does not have read permission, try to enable privilege
238    let file = open_with_privileges(path.as_ref())?;
239    let value: Versioning<SettingsFile> = serde_json::from_reader(file)
240        .inspect_err(|e| {
241            debug!("Error reading file: {}", e);
242        })
243        .unwrap_or_default();
244    read_effective(false).or(dac_override_effective(false))?;
245    debug!("{}", serde_json::to_string_pretty(&value)?);
246    let settingsfile = rc_refcell!(value.data);
247    if let Ok(true) = Migration::migrate(
248        &value.version,
249        &mut *settingsfile.as_ref().borrow_mut(),
250        SETTINGS_MIGRATIONS,
251    ) {
252        if let Ok(true) = Migration::migrate(
253            &value.version,
254            &mut *settingsfile
255                .as_ref()
256                .borrow_mut()
257                .config
258                .as_ref()
259                .borrow_mut(),
260            JSON_MIGRATIONS,
261        ) {
262            save_settings(settingsfile.clone())?;
263        } else {
264            debug!("No config migrations needed");
265        }
266    } else {
267        debug!("No settings migrations needed");
268    }
269    Ok(settingsfile)
270}