2020-10-26 12:58:55 +01:00
|
|
|
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;
|
|
|
|
|
2020-11-17 13:40:44 +01:00
|
|
|
use crate::db::access::{Permission, Role, RoleIdentifier, RoleDB};
|
2020-10-26 12:58:55 +01:00
|
|
|
use crate::db::user::{UserIdentifier, User};
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
2020-11-17 12:26:35 +01:00
|
|
|
pub struct Internal {
|
2020-10-26 12:58:55 +01:00
|
|
|
log: Logger,
|
2020-10-28 16:25:33 +01:00
|
|
|
env: Arc<Environment>,
|
2020-10-26 12:58:55 +01:00
|
|
|
roledb: lmdb::Database,
|
|
|
|
userdb: lmdb::Database,
|
|
|
|
}
|
|
|
|
|
2020-11-17 12:26:35 +01:00
|
|
|
impl Internal {
|
2020-10-28 16:25:33 +01:00
|
|
|
pub fn new(log: Logger, env: Arc<Environment>, roledb: lmdb::Database, userdb: lmdb::Database) -> Self {
|
2020-11-17 12:26:35 +01:00
|
|
|
Self { log, env, roledb, userdb }
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if a given user has the given permission
|
|
|
|
#[allow(unused)]
|
2020-11-17 13:40:44 +01:00
|
|
|
pub fn _check<T: Transaction, P: AsRef<Permission>>(&self, txn: &T, user: &User, perm: &P)
|
|
|
|
-> Result<bool>
|
|
|
|
{
|
2020-10-28 23:24:02 +01:00
|
|
|
// Tally all roles. Makes dependent roles easier
|
|
|
|
let mut roles = HashSet::new();
|
|
|
|
for roleID in user.roles.iter() {
|
|
|
|
self._tally_role(txn, &mut roles, roleID)?;
|
|
|
|
}
|
2020-10-26 12:58:55 +01:00
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
// Iter all unique role->permissions we've found and early return on match.
|
|
|
|
// TODO: Change this for negative permissions?
|
|
|
|
for role in roles.iter() {
|
2020-11-17 13:40:44 +01:00
|
|
|
for perm_rule in role.permissions.iter() {
|
|
|
|
if perm_rule.match_perm(perm) {
|
2020-10-28 23:24:02 +01:00
|
|
|
return Ok(true);
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
fn _tally_role<T: Transaction>(&self, txn: &T, roles: &mut HashSet<Role>, roleID: &RoleIdentifier) -> Result<()> {
|
|
|
|
if let Some(role) = self._get_role(txn, roleID)? {
|
2020-10-26 12:58:55 +01:00
|
|
|
// 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() {
|
2020-10-28 23:24:02 +01:00
|
|
|
self._tally_role(txn, roles, parent)?;
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
roles.insert(role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: &RoleIdentifier) -> Result<Option<Role>> {
|
|
|
|
let string = format!("{}", roleID);
|
|
|
|
match txn.get(self.roledb, &string.as_bytes()) {
|
2020-10-26 12:58:55 +01:00
|
|
|
Ok(bytes) => {
|
|
|
|
Ok(Some(flexbuffers::from_slice(bytes)?))
|
|
|
|
},
|
|
|
|
Err(lmdb::Error::NotFound) => { Ok(None) },
|
|
|
|
Err(e) => { Err(e.into()) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
fn put_role(&self, txn: &mut RwTransaction, roleID: &RoleIdentifier, role: Role) -> Result<()> {
|
2020-10-26 12:58:55 +01:00
|
|
|
let bytes = flexbuffers::to_vec(role)?;
|
2020-10-28 23:24:02 +01:00
|
|
|
let string = format!("{}", roleID);
|
|
|
|
txn.put(self.roledb, &string.as_bytes(), &bytes, lmdb::WriteFlags::empty())?;
|
2020-10-26 12:58:55 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dump_db<T: Transaction>(&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();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dump_roles<T: Transaction>(&mut self, txn: &T, mut path: PathBuf) -> Result<()> {
|
2020-11-20 10:39:10 +01:00
|
|
|
// TODO implement this for the new format
|
|
|
|
unimplemented!()
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> {
|
|
|
|
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())?;
|
|
|
|
}
|
2020-10-28 16:25:33 +01:00
|
|
|
|
2020-10-26 12:58:55 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_roles(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> {
|
2020-11-20 10:39:10 +01:00
|
|
|
if path.is_file() {
|
|
|
|
let roles = Role::load_file(path)?;
|
|
|
|
|
|
|
|
for (k,v) in roles.iter() {
|
|
|
|
self.put_role(txn, k, v.clone())?;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for entry in std::fs::read_dir(path)? {
|
|
|
|
let roles = Role::load_file(entry?.path())?;
|
|
|
|
|
|
|
|
for (k,v) in roles.iter() {
|
|
|
|
self.put_role(txn, k, v.clone())?;
|
|
|
|
}
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-28 16:25:33 +01:00
|
|
|
}
|
2020-10-26 12:58:55 +01:00
|
|
|
|
2020-11-17 12:26:35 +01:00
|
|
|
impl RoleDB for Internal {
|
2020-11-24 14:16:22 +01:00
|
|
|
fn get_type_name(&self) -> &'static str {
|
|
|
|
"Internal"
|
|
|
|
}
|
|
|
|
|
2020-11-20 13:06:55 +01:00
|
|
|
fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
|
2020-10-28 16:25:33 +01:00
|
|
|
let txn = self.env.begin_ro_txn()?;
|
2020-11-20 13:06:55 +01:00
|
|
|
self._check(&txn, user, &perm)
|
2020-10-28 16:25:33 +01:00
|
|
|
}
|
2020-10-26 12:58:55 +01:00
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>> {
|
2020-10-28 16:25:33 +01:00
|
|
|
let txn = self.env.begin_ro_txn()?;
|
2020-10-28 19:22:11 +01:00
|
|
|
self._get_role(&txn, roleID)
|
2020-10-28 16:25:33 +01:00
|
|
|
}
|
2020-10-28 23:24:02 +01:00
|
|
|
|
|
|
|
fn tally_role(&self, roles: &mut HashSet<Role>, roleID: &RoleIdentifier) -> Result<()> {
|
|
|
|
let txn = self.env.begin_ro_txn()?;
|
|
|
|
self._tally_role(&txn, roles, roleID)
|
|
|
|
}
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|
2020-10-28 16:25:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
2020-10-26 12:58:55 +01:00
|
|
|
/// Initialize the access db by loading all the lmdb databases
|
2020-10-28 23:24:02 +01:00
|
|
|
pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>)
|
2020-11-17 12:26:35 +01:00
|
|
|
-> std::result::Result<Internal, crate::error::Error>
|
2020-10-28 23:24:02 +01:00
|
|
|
{
|
2020-10-26 12:58:55 +01:00
|
|
|
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");
|
|
|
|
|
2020-11-17 12:26:35 +01:00
|
|
|
Ok(Internal::new(log, env, roledb, userdb))
|
2020-10-26 12:58:55 +01:00
|
|
|
}
|