mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-22 23:07:56 +01:00
Moves databases around a touch
This commit is contained in:
parent
c25983d48c
commit
72a9d8c639
@ -7,6 +7,12 @@ edition = "2018"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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]
|
[dependencies]
|
||||||
futures = { version = "0.3", features = ["thread-pool", "compat"] }
|
futures = { version = "0.3", features = ["thread-pool", "compat"] }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
|
456
src/db/access.rs
456
src/db/access.rs
@ -1,6 +1,7 @@
|
|||||||
//! Access control logic
|
//! Access control logic
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@ -19,6 +20,11 @@ use lmdb::{Environment, Transaction, RwTransaction, Cursor};
|
|||||||
use crate::config::Settings;
|
use crate::config::Settings;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|
||||||
|
mod adapter_lmdb;
|
||||||
|
|
||||||
|
use adapter_lmdb::PermissionsDB;
|
||||||
|
pub use adapter_lmdb::init;
|
||||||
|
|
||||||
// FIXME: fabinfra/fabaccess/bffh#3
|
// FIXME: fabinfra/fabaccess/bffh#3
|
||||||
pub type UserIdentifier = u64;
|
pub type UserIdentifier = u64;
|
||||||
pub type RoleIdentifier = u64;
|
pub type RoleIdentifier = u64;
|
||||||
@ -39,16 +45,11 @@ impl Permissions {
|
|||||||
let txn = self.env.begin_ro_txn()?;
|
let txn = self.env.begin_ro_txn()?;
|
||||||
self.inner.check(&txn, userID, permID)
|
self.inner.check(&txn, userID, permID)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// A Person, from the Authorization perspective
|
pub fn get_role(&self, roleID: RoleIdentifier) -> Result<Option<Role>> {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
let txn = self.env.begin_ro_txn()?;
|
||||||
struct User {
|
self.inner.get_role(&txn, roleID)
|
||||||
name: String,
|
}
|
||||||
|
|
||||||
/// A Person has N ≥ 0 roles.
|
|
||||||
/// Persons are only ever given roles, not permissions directly
|
|
||||||
roles: Vec<RoleIdentifier>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A "Role" from the Authorization perspective
|
/// 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
|
/// the permission for that machine to an already existing role instead of manually having to
|
||||||
/// assign to all users.
|
/// assign to all users.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct Role {
|
pub struct Role {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
/// A Role can have parents, inheriting all permissions
|
/// A Role can have parents, inheriting all permissions
|
||||||
@ -76,413 +77,32 @@ struct Role {
|
|||||||
permissions: Vec<PermIdentifier>,
|
permissions: Vec<PermIdentifier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Permission from the Authorization perspective
|
type SourceID = String;
|
||||||
///
|
|
||||||
/// Permissions are rather simple flags. A person can have or not have a permission, dictated by
|
/// Universal (relative) id of a role
|
||||||
/// its roles and the permissions assigned to those roles.
|
enum RoleID {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
/// The role comes from this instance
|
||||||
struct Perm {
|
Local {
|
||||||
name: String,
|
/// 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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
impl fmt::Display for RoleID {
|
||||||
#[derive(Clone, Debug)]
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
pub struct PermissionsDB {
|
match *self {
|
||||||
log: Logger,
|
RoleID::Local {name, source} => write!(f, "{}/{}@local", name, source),
|
||||||
roledb: lmdb::Database,
|
RoleID::Remote {name, location} => write!(f, "{}@{}", name, location),
|
||||||
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<T: Transaction>(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool> {
|
|
||||||
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<T: Transaction>(&self, txn: &T, roles: &mut HashSet<Role>, 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<Option<Role>> {
|
|
||||||
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<T: Transaction>(&self, txn: &T, userID: UserIdentifier) -> Result<Option<User>> {
|
|
||||||
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<T: Transaction>(&self, txn: &T, permID: PermIdentifier) -> Result<Option<Perm>> {
|
|
||||||
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<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();
|
|
||||||
|
|
||||||
|
|
||||||
// ====================: 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<T: Transaction>(&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::<u64>());
|
|
||||||
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<T: Transaction>(&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::<u64>());
|
|
||||||
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<T: Transaction>(&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::<u64>());
|
|
||||||
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<lmdb::Environment>) -> std::result::Result<Permissions, crate::error::Error> {
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
420
src/db/access/adapter_lmdb.rs
Normal file
420
src/db/access/adapter_lmdb.rs
Normal file
@ -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<T: Transaction>(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool> {
|
||||||
|
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<T: Transaction>(&self, txn: &T, roles: &mut HashSet<Role>, 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<Option<Role>> {
|
||||||
|
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<T: Transaction>(&self, txn: &T, userID: UserIdentifier) -> Result<Option<User>> {
|
||||||
|
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<T: Transaction>(&self, txn: &T, permID: PermIdentifier) -> Result<Option<Perm>> {
|
||||||
|
// 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<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();
|
||||||
|
|
||||||
|
|
||||||
|
// ====================: 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<T: Transaction>(&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::<u64>());
|
||||||
|
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<T: Transaction>(&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::<u64>());
|
||||||
|
// 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<T: Transaction>(&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::<u64>());
|
||||||
|
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<lmdb::Environment>) -> std::result::Result<Permissions, crate::error::Error> {
|
||||||
|
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))
|
||||||
|
}
|
@ -1 +1,13 @@
|
|||||||
|
/// Access control storage
|
||||||
|
///
|
||||||
|
/// Stores&Retrieves Permissions and Roles
|
||||||
pub mod access;
|
pub mod access;
|
||||||
|
/// User storage
|
||||||
|
///
|
||||||
|
/// Stores&Retrieves Users
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
/// Machine storage
|
||||||
|
///
|
||||||
|
/// Stores&Retrieves Machines
|
||||||
|
pub mod machine;
|
||||||
|
62
src/db/user.rs
Normal file
62
src/db/user.rs
Normal file
@ -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<RoleIdentifier>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 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<String>,
|
||||||
|
/// Location of the instance the user comes from. `None` means the local instance.
|
||||||
|
location: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserIdentifier {
|
||||||
|
pub fn new(uid: String, subuid: Option<String>, location: Option<String>) -> 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<User>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_uid_test() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -13,14 +13,15 @@ mod log;
|
|||||||
mod api;
|
mod api;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod machine;
|
|
||||||
mod connection;
|
mod connection;
|
||||||
mod registries;
|
mod registries;
|
||||||
mod network;
|
mod network;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod db;
|
mod db;
|
||||||
|
|
||||||
|
// TODO: Remove these and improve module namespacing
|
||||||
use db::access;
|
use db::access;
|
||||||
|
pub use db::machine;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user