From 72a9d8c639dd145a486975eefa11093713e2f583 Mon Sep 17 00:00:00 2001 From: Gregor Reitzenstein Date: Mon, 26 Oct 2020 12:58:55 +0100 Subject: [PATCH] Moves databases around a touch --- Cargo.toml | 6 + src/db/access.rs | 456 +++------------------------------- src/db/access/adapter_lmdb.rs | 420 +++++++++++++++++++++++++++++++ src/{ => db}/machine.rs | 0 src/db/mod.rs | 12 + src/db/user.rs | 62 +++++ src/main.rs | 3 +- 7 files changed, 540 insertions(+), 419 deletions(-) create mode 100644 src/db/access/adapter_lmdb.rs rename src/{ => db}/machine.rs (100%) create mode 100644 src/db/user.rs diff --git a/Cargo.toml b/Cargo.toml index 30c11cb..32efcb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["lmdb"] + +# Use LMDB for internal kv-stores +lmdb = [] + [dependencies] futures = { version = "0.3", features = ["thread-pool", "compat"] } futures-util = "0.3" diff --git a/src/db/access.rs b/src/db/access.rs index 3f8d6a1..31290f8 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -1,6 +1,7 @@ //! Access control logic //! +use std::fmt; use std::collections::HashSet; use std::convert::TryInto; @@ -19,6 +20,11 @@ use lmdb::{Environment, Transaction, RwTransaction, Cursor}; use crate::config::Settings; use crate::error::Result; +mod adapter_lmdb; + +use adapter_lmdb::PermissionsDB; +pub use adapter_lmdb::init; + // FIXME: fabinfra/fabaccess/bffh#3 pub type UserIdentifier = u64; pub type RoleIdentifier = u64; @@ -39,16 +45,11 @@ impl Permissions { let txn = self.env.begin_ro_txn()?; self.inner.check(&txn, userID, permID) } -} -/// A Person, from the Authorization perspective -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct User { - name: String, - - /// A Person has N ≥ 0 roles. - /// Persons are only ever given roles, not permissions directly - roles: Vec + pub fn get_role(&self, roleID: RoleIdentifier) -> Result> { + let txn = self.env.begin_ro_txn()?; + self.inner.get_role(&txn, roleID) + } } /// A "Role" from the Authorization perspective @@ -64,7 +65,7 @@ struct User { /// the permission for that machine to an already existing role instead of manually having to /// assign to all users. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct Role { +pub struct Role { name: String, /// A Role can have parents, inheriting all permissions @@ -76,413 +77,32 @@ struct Role { permissions: Vec, } -/// A Permission from the Authorization perspective -/// -/// Permissions are rather simple flags. A person can have or not have a permission, dictated by -/// its roles and the permissions assigned to those roles. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct Perm { - name: String, +type SourceID = String; + +/// Universal (relative) id of a role +enum RoleID { + /// The role comes from this instance + Local { + /// Locally unique name for the role. No other role at this instance no matter the source + /// may have the same name + name: String, + /// Role Source, i.e. the database the role comes from + source: SourceID, + }, + /// The role comes from a federated instance + Remote { + /// Name of the role. This role is unique in that instance so the tuple (name, location) + /// refers to a unique role + name: String, + /// The federated instance this role comes from + location: String, + } } - -#[derive(Clone, Debug)] -pub struct PermissionsDB { - log: Logger, - roledb: lmdb::Database, - permdb: lmdb::Database, - userdb: lmdb::Database, -} - -impl PermissionsDB { - pub fn new(log: Logger, roledb: lmdb::Database, permdb: lmdb::Database, userdb: lmdb::Database) -> Self { - PermissionsDB { log, roledb, permdb, userdb } - } - - /// Check if a given user has the given permission - #[allow(unused)] - pub fn check(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result { - if let Some(user) = self.get_user(txn, userID)? { - // Tally all roles. Makes dependent roles easier - let mut roles = HashSet::new(); - for roleID in user.roles { - self.tally_role(txn, &mut roles, roleID)?; - } - - // Iter all unique role->permissions we've found and early return on match. - // TODO: Change this for negative permissions? - for role in roles.iter() { - for perm in role.permissions.iter() { - if permID == *perm { - return Ok(true); - } - } - } - } - - return Ok(false); - } - - fn tally_role(&self, txn: &T, roles: &mut HashSet, roleID: RoleIdentifier) -> Result<()> { - if let Some(role) = self.get_role(txn, roleID)? { - // Only check and tally parents of a role at the role itself if it's the first time we - // see it - if !roles.contains(&role) { - for parent in role.parents.iter() { - self.tally_role(txn, roles, *parent)?; - } - - roles.insert(role); - } - } - - Ok(()) - } - - fn get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result> { - match txn.get(self.roledb, &roleID.to_ne_bytes()) { - Ok(bytes) => { - Ok(Some(flexbuffers::from_slice(bytes)?)) - }, - Err(lmdb::Error::NotFound) => { Ok(None) }, - Err(e) => { Err(e.into()) } - } - } - - fn get_user(&self, txn: &T, userID: UserIdentifier) -> Result> { - match txn.get(self.userdb, &userID.to_ne_bytes()) { - Ok(bytes) => { - Ok(Some(flexbuffers::from_slice(bytes)?)) - }, - Err(lmdb::Error::NotFound) => { Ok(None) }, - Err(e) => { Err(e.into()) } - } - } - - fn get_perm(&self, txn: &T, permID: PermIdentifier) -> Result> { - match txn.get(self.permdb, &permID.to_ne_bytes()) { - Ok(bytes) => { - Ok(Some(flexbuffers::from_slice(bytes)?)) - }, - Err(lmdb::Error::NotFound) => { Ok(None) }, - Err(e) => { Err(e.into()) } - } - } - - fn put_role(&self, txn: &mut RwTransaction, roleID: RoleIdentifier, role: Role) -> Result<()> { - let bytes = flexbuffers::to_vec(role)?; - txn.put(self.roledb, &roleID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; - - Ok(()) - } - - fn put_user(&self, txn: &mut RwTransaction, userID: UserIdentifier, user: User) -> Result<()> { - let bytes = flexbuffers::to_vec(user)?; - txn.put(self.userdb, &userID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; - - Ok(()) - } - - fn put_perm(&self, txn: &mut RwTransaction, permID: PermIdentifier, perm: Perm) -> Result<()> { - let bytes = flexbuffers::to_vec(perm)?; - txn.put(self.permdb, &permID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; - - Ok(()) - } - - pub fn dump_db(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { - path.push("roles"); - let mut k = Ok(()); - if !path.is_dir() { - k = fs::create_dir(&path); - } - if let Err(e) = k { - error!(self.log, "Failed to create 'roles' directory: {}, skipping!", e); - return Ok(()) - } else { - // Rust's stdlib considers the last element the file name even when it's a directory so - // we have to put a dummy here for .set_filename() to work correctly - path.push("dummy"); - self.dump_roles(txn, path.clone())?; - path.pop(); - } - path.pop(); - - - // ====================: PERMS :==================== - - - path.push("perms"); - let mut k = Ok(()); - if !path.is_dir() { - k = fs::create_dir(&path); - } - if let Err(e) = k { - error!(self.log, "Failed to create 'perms' directory: {}, skipping!", e); - return Ok(()) - } else { - // Rust's stdlib considers the last element the file name even when it's a directory so - // we have to put a dummy here for .set_filename() to work correctly - path.push("dummy"); - self.dump_perms(txn, path.clone())?; - path.pop(); - } - path.pop(); - - - // ====================: USERS :==================== - - - path.push("users"); - let mut k = Ok(()); - if !path.is_dir() { - k = fs::create_dir(&path); - } - if let Err(e) = k { - error!(self.log, "Failed to create 'users' directory: {}, skipping!", e); - return Ok(()) - } else { - // Rust's stdlib considers the last element the file name even when it's a directory so - // we have to put a dummy here for .set_filename() to work correctly - path.push("dummy"); - self.dump_users(txn, path.clone())?; - path.pop(); - } - path.pop(); - - Ok(()) - } - - fn dump_roles(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { - let mut role_cursor = txn.open_ro_cursor(self.roledb)?; - for buf in role_cursor.iter_start() { - let (kbuf, vbuf) = buf?; - let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); - let roleID = u64::from_ne_bytes(kbytes.try_into().unwrap()); - let role: Role = flexbuffers::from_slice(vbuf)?; - let filename = format!("{:x}.toml", roleID); - path.set_file_name(filename); - let mut fp = std::fs::File::create(&path)?; - let out = toml::to_vec(&role)?; - fp.write_all(&out)?; - } - - Ok(()) - } - - fn dump_perms(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { - let mut perm_cursor = txn.open_ro_cursor(self.permdb)?; - for buf in perm_cursor.iter_start() { - let (kbuf, vbuf) = buf?; - let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); - let permID = u64::from_ne_bytes(kbytes.try_into().unwrap()); - let perm: Perm = flexbuffers::from_slice(vbuf)?; - let filename = format!("{:x}.toml", permID); - path.set_file_name(filename); - let mut fp = std::fs::File::create(&path)?; - let out = toml::to_vec(&perm)?; - fp.write_all(&out)?; - } - - Ok(()) - } - - fn dump_users(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { - let mut user_cursor = txn.open_ro_cursor(self.userdb)?; - for buf in user_cursor.iter_start() { - let (kbuf, vbuf) = buf?; - let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); - let userID = u64::from_ne_bytes(kbytes.try_into().unwrap()); - let user: User = flexbuffers::from_slice(vbuf)?; - let filename = format!("{:x}.toml", userID); - path.set_file_name(filename); - let mut fp = std::fs::File::create(&path)?; - let out = toml::to_vec(&user)?; - fp.write_all(&out)?; - } - - Ok(()) - } - - pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> { - // ====================: ROLES :==================== - path.push("roles"); - if !path.is_dir() { - error!(self.log, "Given load directory is malformed, no 'roles' subdir, not loading roles!"); - } else { - self.load_roles(txn, path.as_path())?; - } - path.pop(); - // ================================================= - - // ====================: PERMS :==================== - path.push("perms"); - if !path.is_dir() { - error!(self.log, "Given load directory is malformed, no 'perms' subdir, not loading perms!"); - } else { - self.load_perms(txn, &path)?; - } - path.pop(); - // ================================================= - - // ====================: USERS :==================== - path.push("users"); - if !path.is_dir() { - error!(self.log, "Given load directory is malformed, no 'users' subdir, not loading users!"); - } else { - self.load_users(txn, &path)?; - } - path.pop(); - // ================================================= - Ok(()) - } - - fn load_roles(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - // will only ever be none if the path has no file name and then how is it a file?! - let roleID_str = path - .file_stem().expect("Found a file with no filename?") - .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); - let roleID = match u64::from_str_radix(roleID_str, 16) { - Ok(i) => i, - Err(e) => { - warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); - continue; - } - }; - let s = match fs::read_to_string(path.as_path()) { - Ok(s) => s, - Err(e) => { - warn!(self.log, "Failed to open file {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - let role: Role = match toml::from_str(&s) { - Ok(r) => r, - Err(e) => { - warn!(self.log, "Failed to parse role at path {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - self.put_role(txn, roleID, role)?; - debug!(self.log, "Loaded role {}", roleID); - } else { - warn!(self.log, "Path {} is not a file, skipping!", path.display()); - } - } - - Ok(()) - } - - fn load_perms(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - // will only ever be none if the path has no file name and then how is it a file?! - let permID_str = path - .file_stem().expect("Found a file with no filename?") - .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); - let permID = match u64::from_str_radix(permID_str, 16) { - Ok(i) => i, - Err(e) => { - warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); - continue; - } - }; - let s = match fs::read_to_string(path.as_path()) { - Ok(s) => s, - Err(e) => { - warn!(self.log, "Failed to open file {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - let perm: Perm = match toml::from_str(&s) { - Ok(r) => r, - Err(e) => { - warn!(self.log, "Failed to parse perm at path {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - self.put_perm(txn, permID, perm)?; - debug!(self.log, "Loaded perm {}", permID); - } else { - warn!(self.log, "Path {} is not a file, skipping!", path.display()); - } - } - - Ok(()) - } - - fn load_users(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - // will only ever be none if the path has no file name and then how is it a file?! - let userID_str = path - .file_stem().expect("Found a file with no filename?") - .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); - let userID = match u64::from_str_radix(userID_str, 16) { - Ok(i) => i, - Err(e) => { - warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); - continue; - } - }; - let s = match fs::read_to_string(path.as_path()) { - Ok(s) => s, - Err(e) => { - warn!(self.log, "Failed to open file {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - let user: User = match toml::from_str(&s) { - Ok(r) => r, - Err(e) => { - warn!(self.log, "Failed to parse user at path {}: {}, skipping!" - , path.display() - , e); - continue; - } - }; - self.put_user(txn, userID, user)?; - debug!(self.log, "Loaded user {}", userID); - } else { - warn!(self.log, "Path {} is not a file, skipping!", path.display()); - } - } - - Ok(()) - } - -} - -/// Initialize the access db by loading all the lmdb databases -pub fn init(log: Logger, config: &Settings, env: Arc) -> std::result::Result { - let mut flags = lmdb::DatabaseFlags::empty(); - flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); - let roledb = env.create_db(Some("role"), flags)?; - debug!(&log, "Opened access database '{}' successfully.", "role"); - let permdb = env.create_db(Some("perm"), flags)?; - debug!(&log, "Opened access database '{}' successfully.", "perm"); - let userdb = env.create_db(Some("user"), flags)?; - debug!(&log, "Opened access database '{}' successfully.", "user"); - info!(&log, "Opened all access databases"); - - let pdb = PermissionsDB::new(log, roledb, permdb, userdb); - - Ok(Permissions::new(pdb, env)) +impl fmt::Display for RoleID { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + RoleID::Local {name, source} => write!(f, "{}/{}@local", name, source), + RoleID::Remote {name, location} => write!(f, "{}@{}", name, location), + } + } } diff --git a/src/db/access/adapter_lmdb.rs b/src/db/access/adapter_lmdb.rs new file mode 100644 index 0000000..60a1f6c --- /dev/null +++ b/src/db/access/adapter_lmdb.rs @@ -0,0 +1,420 @@ +use std::collections::HashSet; + +use std::convert::TryInto; + +use std::path::{Path, PathBuf}; +use std::fs; +use std::io::Write; +use std::sync::Arc; + +use flexbuffers; +use serde::{Serialize, Deserialize}; + +use slog::Logger; +use lmdb::{Environment, Transaction, RwTransaction, Cursor}; + +use crate::config::Settings; +use crate::error::Result; + +use crate::db::access::{PermIdentifier, Role, RoleIdentifier, Permissions}; +use crate::db::user::{UserIdentifier, User}; + +#[derive(Clone, Debug)] +pub struct PermissionsDB { + log: Logger, + roledb: lmdb::Database, + userdb: lmdb::Database, +} + +impl PermissionsDB { + pub fn new(log: Logger, roledb: lmdb::Database, userdb: lmdb::Database) -> Self { + PermissionsDB { log, roledb, userdb } + } + + /// Check if a given user has the given permission + #[allow(unused)] + pub fn check(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result { + if let Some(user) = self.get_user(txn, userID)? { + // Tally all roles. Makes dependent roles easier + let mut roles = HashSet::new(); + for roleID in user.roles { + self.tally_role(txn, &mut roles, roleID)?; + } + + // Iter all unique role->permissions we've found and early return on match. + // TODO: Change this for negative permissions? + for role in roles.iter() { + for perm in role.permissions.iter() { + if permID == *perm { + return Ok(true); + } + } + } + } + + return Ok(false); + } + + fn tally_role(&self, txn: &T, roles: &mut HashSet, roleID: RoleIdentifier) -> Result<()> { + if let Some(role) = self.get_role(txn, roleID)? { + // Only check and tally parents of a role at the role itself if it's the first time we + // see it + if !roles.contains(&role) { + for parent in role.parents.iter() { + self.tally_role(txn, roles, *parent)?; + } + + roles.insert(role); + } + } + + Ok(()) + } + + pub fn get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result> { + match txn.get(self.roledb, &roleID.to_ne_bytes()) { + Ok(bytes) => { + Ok(Some(flexbuffers::from_slice(bytes)?)) + }, + Err(lmdb::Error::NotFound) => { Ok(None) }, + Err(e) => { Err(e.into()) } + } + } + + pub fn get_user(&self, txn: &T, userID: UserIdentifier) -> Result> { + match txn.get(self.userdb, &userID.to_ne_bytes()) { + Ok(bytes) => { + Ok(Some(flexbuffers::from_slice(bytes)?)) + }, + Err(lmdb::Error::NotFound) => { Ok(None) }, + Err(e) => { Err(e.into()) } + } + } + + //fn get_perm(&self, txn: &T, permID: PermIdentifier) -> Result> { + // match txn.get(self.permdb, &permID.to_ne_bytes()) { + // Ok(bytes) => { + // Ok(Some(flexbuffers::from_slice(bytes)?)) + // }, + // Err(lmdb::Error::NotFound) => { Ok(None) }, + // Err(e) => { Err(e.into()) } + // } + //} + + fn put_role(&self, txn: &mut RwTransaction, roleID: RoleIdentifier, role: Role) -> Result<()> { + let bytes = flexbuffers::to_vec(role)?; + txn.put(self.roledb, &roleID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; + + Ok(()) + } + + fn put_user(&self, txn: &mut RwTransaction, userID: UserIdentifier, user: User) -> Result<()> { + let bytes = flexbuffers::to_vec(user)?; + txn.put(self.userdb, &userID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; + + Ok(()) + } + + //fn put_perm(&self, txn: &mut RwTransaction, permID: PermIdentifier, perm: Perm) -> Result<()> { + // let bytes = flexbuffers::to_vec(perm)?; + // txn.put(self.permdb, &permID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; + + // Ok(()) + //} + + pub fn dump_db(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + path.push("roles"); + let mut k = Ok(()); + if !path.is_dir() { + k = fs::create_dir(&path); + } + if let Err(e) = k { + error!(self.log, "Failed to create 'roles' directory: {}, skipping!", e); + return Ok(()) + } else { + // Rust's stdlib considers the last element the file name even when it's a directory so + // we have to put a dummy here for .set_filename() to work correctly + path.push("dummy"); + self.dump_roles(txn, path.clone())?; + path.pop(); + } + path.pop(); + + + // ====================: PERMS :==================== + + +// path.push("perms"); +// let mut k = Ok(()); +// if !path.is_dir() { +// k = fs::create_dir(&path); +// } +// if let Err(e) = k { +// error!(self.log, "Failed to create 'perms' directory: {}, skipping!", e); +// return Ok(()) +// } else { +// // Rust's stdlib considers the last element the file name even when it's a directory so +// // we have to put a dummy here for .set_filename() to work correctly +// path.push("dummy"); +// self.dump_perms(txn, path.clone())?; +// path.pop(); +// } +// path.pop(); + + + // ====================: USERS :==================== + + + path.push("users"); + let mut k = Ok(()); + if !path.is_dir() { + k = fs::create_dir(&path); + } + if let Err(e) = k { + error!(self.log, "Failed to create 'users' directory: {}, skipping!", e); + return Ok(()) + } else { + // Rust's stdlib considers the last element the file name even when it's a directory so + // we have to put a dummy here for .set_filename() to work correctly + path.push("dummy"); + self.dump_users(txn, path.clone())?; + path.pop(); + } + path.pop(); + + Ok(()) + } + + fn dump_roles(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + let mut role_cursor = txn.open_ro_cursor(self.roledb)?; + for buf in role_cursor.iter_start() { + let (kbuf, vbuf) = buf?; + let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); + let roleID = u64::from_ne_bytes(kbytes.try_into().unwrap()); + let role: Role = flexbuffers::from_slice(vbuf)?; + let filename = format!("{:x}.toml", roleID); + path.set_file_name(filename); + let mut fp = std::fs::File::create(&path)?; + let out = toml::to_vec(&role)?; + fp.write_all(&out)?; + } + + Ok(()) + } + + //fn dump_perms(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + // let mut perm_cursor = txn.open_ro_cursor(self.permdb)?; + // for buf in perm_cursor.iter_start() { + // let (kbuf, vbuf) = buf?; + // let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); + // let permID = u64::from_ne_bytes(kbytes.try_into().unwrap()); + // let perm: Perm = flexbuffers::from_slice(vbuf)?; + // let filename = format!("{:x}.toml", permID); + // path.set_file_name(filename); + // let mut fp = std::fs::File::create(&path)?; + // let out = toml::to_vec(&perm)?; + // fp.write_all(&out)?; + // } + + // Ok(()) + //} + + fn dump_users(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + let mut user_cursor = txn.open_ro_cursor(self.userdb)?; + for buf in user_cursor.iter_start() { + let (kbuf, vbuf) = buf?; + let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); + let userID = u64::from_ne_bytes(kbytes.try_into().unwrap()); + let user: User = flexbuffers::from_slice(vbuf)?; + let filename = format!("{:x}.toml", userID); + path.set_file_name(filename); + let mut fp = std::fs::File::create(&path)?; + let out = toml::to_vec(&user)?; + fp.write_all(&out)?; + } + + Ok(()) + } + + pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> { + // ====================: ROLES :==================== + path.push("roles"); + if !path.is_dir() { + error!(self.log, "Given load directory is malformed, no 'roles' subdir, not loading roles!"); + } else { + self.load_roles(txn, path.as_path())?; + } + path.pop(); + // ================================================= + +// // ====================: PERMS :==================== +// path.push("perms"); +// if !path.is_dir() { +// error!(self.log, "Given load directory is malformed, no 'perms' subdir, not loading perms!"); +// } else { +// self.load_perms(txn, &path)?; +// } +// path.pop(); +// // ================================================= + + // ====================: USERS :==================== + path.push("users"); + if !path.is_dir() { + error!(self.log, "Given load directory is malformed, no 'users' subdir, not loading users!"); + } else { + self.load_users(txn, &path)?; + } + path.pop(); + // ================================================= + Ok(()) + } + + fn load_roles(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + // will only ever be none if the path has no file name and then how is it a file?! + let roleID_str = path + .file_stem().expect("Found a file with no filename?") + .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); + let roleID = match u64::from_str_radix(roleID_str, 16) { + Ok(i) => i, + Err(e) => { + warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); + continue; + } + }; + let s = match fs::read_to_string(path.as_path()) { + Ok(s) => s, + Err(e) => { + warn!(self.log, "Failed to open file {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + let role: Role = match toml::from_str(&s) { + Ok(r) => r, + Err(e) => { + warn!(self.log, "Failed to parse role at path {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + self.put_role(txn, roleID, role)?; + debug!(self.log, "Loaded role {}", roleID); + } else { + warn!(self.log, "Path {} is not a file, skipping!", path.display()); + } + } + + Ok(()) + } + +// fn load_perms(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { +// for entry in std::fs::read_dir(path)? { +// let entry = entry?; +// let path = entry.path(); +// if path.is_file() { +// // will only ever be none if the path has no file name and then how is it a file?! +// let permID_str = path +// .file_stem().expect("Found a file with no filename?") +// .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); +// let permID = match u64::from_str_radix(permID_str, 16) { +// Ok(i) => i, +// Err(e) => { +// warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); +// continue; +// } +// }; +// let s = match fs::read_to_string(path.as_path()) { +// Ok(s) => s, +// Err(e) => { +// warn!(self.log, "Failed to open file {}: {}, skipping!" +// , path.display() +// , e); +// continue; +// } +// }; +// let perm: Perm = match toml::from_str(&s) { +// Ok(r) => r, +// Err(e) => { +// warn!(self.log, "Failed to parse perm at path {}: {}, skipping!" +// , path.display() +// , e); +// continue; +// } +// }; +// self.put_perm(txn, permID, perm)?; +// debug!(self.log, "Loaded perm {}", permID); +// } else { +// warn!(self.log, "Path {} is not a file, skipping!", path.display()); +// } +// } + +// Ok(()) +// } + + fn load_users(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + // will only ever be none if the path has no file name and then how is it a file?! + let userID_str = path + .file_stem().expect("Found a file with no filename?") + .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); + let userID = match u64::from_str_radix(userID_str, 16) { + Ok(i) => i, + Err(e) => { + warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); + continue; + } + }; + let s = match fs::read_to_string(path.as_path()) { + Ok(s) => s, + Err(e) => { + warn!(self.log, "Failed to open file {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + let user: User = match toml::from_str(&s) { + Ok(r) => r, + Err(e) => { + warn!(self.log, "Failed to parse user at path {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + self.put_user(txn, userID, user)?; + debug!(self.log, "Loaded user {}", userID); + } else { + warn!(self.log, "Path {} is not a file, skipping!", path.display()); + } + } + + Ok(()) + } + +} +/// Initialize the access db by loading all the lmdb databases +pub fn init(log: Logger, config: &Settings, env: Arc) -> std::result::Result { + let mut flags = lmdb::DatabaseFlags::empty(); + flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); + let roledb = env.create_db(Some("role"), flags)?; + debug!(&log, "Opened access database '{}' successfully.", "role"); + //let permdb = env.create_db(Some("perm"), flags)?; + //debug!(&log, "Opened access database '{}' successfully.", "perm"); + let userdb = env.create_db(Some("user"), flags)?; + debug!(&log, "Opened access database '{}' successfully.", "user"); + info!(&log, "Opened all access databases"); + + let pdb = PermissionsDB::new(log, roledb, userdb); + + Ok(Permissions::new(pdb, env)) +} diff --git a/src/machine.rs b/src/db/machine.rs similarity index 100% rename from src/machine.rs rename to src/db/machine.rs diff --git a/src/db/mod.rs b/src/db/mod.rs index 3750845..9786d0f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1 +1,13 @@ +/// Access control storage +/// +/// Stores&Retrieves Permissions and Roles pub mod access; +/// User storage +/// +/// Stores&Retrieves Users +pub mod user; + +/// Machine storage +/// +/// Stores&Retrieves Machines +pub mod machine; diff --git a/src/db/user.rs b/src/db/user.rs new file mode 100644 index 0000000..5b67575 --- /dev/null +++ b/src/db/user.rs @@ -0,0 +1,62 @@ +use serde::{Serialize, Deserialize}; +use std::fmt; +use crate::db::access::RoleIdentifier; + +/// A Person, from the Authorization perspective +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct User { + name: String, + + /// A Person has N ≥ 0 roles. + /// Persons are only ever given roles, not permissions directly + pub roles: Vec +} + + +/// Locally unique identifier for an user +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct UserIdentifier { + /// Main UID. Must be unique in this instance so that the tuple (uid, location) is globally + /// unique. + uid: String, + /// Subordinate ID. Must be unique for this user, i.e. the tuple (uid, subuid) must be unique + /// but two different uids can have the same subuid. `None` means no subuid is set and the ID + /// refers to the main users + subuid: Option, + /// Location of the instance the user comes from. `None` means the local instance. + location: Option, +} + +impl UserIdentifier { + pub fn new(uid: String, subuid: Option, location: Option) -> Self { + Self { uid, subuid, location } + } +} + +impl fmt::Display for UserIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let r = write!(f, "{}", self.uid); + if let Some(s) = self.subuid { + write!(f, "+{}", s)?; + } + if let Some(l) = self.location { + write!(f, "@{}", l)?; + } + r + } +} + +/// User Database Trait +pub trait UserDB { + fn get_user(&self, uid: UserIdentifier) -> Option; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn format_uid_test() { + + } +} diff --git a/src/main.rs b/src/main.rs index 2fe0bfc..2a94ea2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,14 +13,15 @@ mod log; mod api; mod config; mod error; -mod machine; mod connection; mod registries; mod network; mod schema; mod db; +// TODO: Remove these and improve module namespacing use db::access; +pub use db::machine; use clap::{App, Arg};