From 7956616891ae4b34186456cfb0225968f68e3ad3 Mon Sep 17 00:00:00 2001 From: Gregor Reitzenstein Date: Tue, 24 Nov 2020 15:57:23 +0100 Subject: [PATCH] Actually make compile for once. --- src/api.rs | 12 ++-------- src/builtin.rs | 42 +++++++++++++++++++++++++++++++++ src/connection.rs | 14 +++++------ src/db/access.rs | 10 ++++---- src/db/access/internal.rs | 6 ++--- src/db/machine.rs | 10 ++++---- src/db/pass.rs | 40 ++++++++++++++++++++++++++++++++ src/db/user.rs | 47 +++++++++++++++++++++++++++++-------- src/db/user/internal.rs | 49 +++++++++++++++++++++++++++++++++++++++ src/error.rs | 3 +++ src/machine.rs | 3 ++- src/user.rs | 0 12 files changed, 196 insertions(+), 40 deletions(-) create mode 100644 src/builtin.rs create mode 100644 src/db/pass.rs create mode 100644 src/db/user/internal.rs create mode 100644 src/user.rs diff --git a/src/api.rs b/src/api.rs index ca51fa4..ee25cf7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -38,18 +38,10 @@ impl connection_capnp::bootstrap::Server for Bootstrap { // Forbid mutltiple authentication for now // TODO: When should we allow multiple auth and how do me make sure that does not leak // priviledges (e.g. due to previously issues caps)? - let session = self.session.clone(); - let check_perm_future = session.check_permission(&builtin::AUTH_PERM); - let f = async { - let r = check_perm_future.await.unwrap(); - if r { - res.get().set_auth(capnp_rpc::new_client(auth::Auth::new(session.clone()))) - } - Ok(()) - }; + res.get().set_auth(capnp_rpc::new_client(auth::Auth::new(self.session.clone()))); - Promise::from_future(f) + Promise::ok(()) } fn permissions(&mut self, diff --git a/src/builtin.rs b/src/builtin.rs new file mode 100644 index 0000000..dc32f11 --- /dev/null +++ b/src/builtin.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use lazy_static::lazy_static; +use crate::db::access::{ + Permission, + PermissionBuf, + PermRule, + RoleIdentifier, + Role, +}; + +lazy_static! { + static ref AUTH_PERM: &'static Permission = Permission::new("bffh.auth"); +} + +// +// lazy_static! { +// pub static ref AUTH_ROLE: RoleIdentifier = { +// RoleIdentifier::Local { +// name: "mayauth".to_string(), +// source: "builtin".to_string(), +// } +// }; +// } +// +// lazy_static! { +// pub static ref DEFAULT_ROLEIDS: [RoleIdentifier; 1] = { +// [ AUTH_ROLE.clone(), ] +// }; +// +// pub static ref DEFAULT_ROLES: HashMap = { +// let mut m = HashMap::new(); +// m.insert(AUTH_ROLE.clone(), +// Role { +// parents: vec![], +// permissions: vec![ +// PermRule::Base(PermissionBuf::from_perm(AUTH_PERM)), +// ] +// } +// ); +// m +// }; +// } diff --git a/src/connection.rs b/src/connection.rs index 74be330..3e75246 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -14,7 +14,7 @@ use crate::schema::connection_capnp; use crate::db::Databases; use crate::db::access::{AccessControl, Permission}; -use crate::db::user::AuthzContext; +use crate::db::user::User; use crate::builtin; #[derive(Debug, Clone)] @@ -23,23 +23,23 @@ use crate::builtin; pub struct Session { // Session-spezific log pub log: Logger, - authz_data: Option, + user: Option, accessdb: Arc, } impl Session { pub fn new(log: Logger, accessdb: Arc) -> Self { - let authz_data = None; + let user = None; - Session { log, authz_data, accessdb } + Session { log, user, accessdb } } /// Check if the current session has a certain permission pub async fn check_permission>(&self, perm: &P) -> Result { - if let Some(user) = self.authz_data.as_ref() { - self.accessdb.check(user, perm).await + if let Some(user) = self.user.as_ref() { + self.accessdb.check(&user.data, perm).await } else { - self.accessdb.check_roles(builtin::DEFAULT_ROLEIDS, perm).await + Ok(false) } } } diff --git a/src/db/access.rs b/src/db/access.rs index 71ceec5..9c6bad6 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -29,7 +29,7 @@ use crate::error::Result; pub mod internal; -use crate::db::user::AuthzContext; +use crate::db::user::UserData; pub use internal::init; pub struct AccessControl { @@ -49,7 +49,7 @@ impl AccessControl { self.sources.insert(name, source); } - pub async fn check>(&self, user: &AuthzContext, perm: &P) -> Result { + pub async fn check>(&self, user: &UserData, perm: &P) -> Result { for v in self.sources.values() { if v.check(user, perm.as_ref())? { return Ok(true); @@ -74,7 +74,7 @@ impl AccessControl { impl fmt::Debug for AccessControl { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let b = f.debug_struct("AccessControl"); + let mut b = f.debug_struct("AccessControl"); for (name, roledb) in self.sources.iter() { b.field(name, &roledb.get_type_name().to_string()); } @@ -91,7 +91,7 @@ pub trait RoleDB { /// /// Default implementation which adapter may overwrite with more efficient specialized /// implementations. - fn check(&self, user: &AuthzContext, perm: &Permission) -> Result { + fn check(&self, user: &UserData, perm: &Permission) -> Result { self.check_roles(&user.roles, perm) } @@ -394,7 +394,7 @@ pub struct Permission { inner: str } impl Permission { - pub const fn new + ?Sized>(s: &S) -> &Permission { + pub fn new + ?Sized>(s: &S) -> &Permission { unsafe { &*(s.as_ref() as *const str as *const Permission) } } diff --git a/src/db/access/internal.rs b/src/db/access/internal.rs index d988e66..08a39ac 100644 --- a/src/db/access/internal.rs +++ b/src/db/access/internal.rs @@ -17,7 +17,7 @@ use crate::config::Settings; use crate::error::Result; use crate::db::access::{Permission, Role, RoleIdentifier, RoleDB}; -use crate::db::user::AuthzContext; +use crate::db::user::{User, UserData}; #[derive(Clone, Debug)] pub struct Internal { @@ -34,7 +34,7 @@ impl Internal { /// Check if a given user has the given permission #[allow(unused)] - pub fn _check>(&self, txn: &T, user: &AuthzContext, perm: &P) + pub fn _check>(&self, txn: &T, user: &UserData, perm: &P) -> Result { // Tally all roles. Makes dependent roles easier @@ -154,7 +154,7 @@ impl RoleDB for Internal { "Internal" } - fn check(&self, user: &AuthzContext, perm: &Permission) -> Result { + fn check(&self, user: &UserData, perm: &Permission) -> Result { let txn = self.env.begin_ro_txn()?; self._check(&txn, user, &perm) } diff --git a/src/db/machine.rs b/src/db/machine.rs index 77b4d1f..28a2e37 100644 --- a/src/db/machine.rs +++ b/src/db/machine.rs @@ -31,6 +31,8 @@ use crate::registries::StatusSignal; use crate::machine::MachineDescription; +use crate::db::user::UserId; + pub mod internal; use internal::Internal; @@ -42,15 +44,15 @@ pub enum Status { /// Not currently used by anybody Free, /// Used by somebody - InUse(UserIdentifier), + InUse(UserId), /// Was used by somebody and now needs to be checked for cleanliness - ToCheck(UserIdentifier), + ToCheck(UserId), /// Not used by anybody but also can not be used. E.g. down for maintenance - Blocked(UserIdentifier), + Blocked(UserId), /// Disabled for some other reason Disabled, /// Reserved - Reserved(UserIdentifier), + Reserved(UserId), } pub fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid { diff --git a/src/db/pass.rs b/src/db/pass.rs new file mode 100644 index 0000000..2dd431f --- /dev/null +++ b/src/db/pass.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use argon2; +use lmdb::{Environment, Transaction, RwTransaction, Cursor}; +use rand::prelude::*; +use slog::Logger; + +use crate::error::Result; + +pub struct PassDB { + log: Logger, + env: Arc, + db: lmdb::Database, +} + +impl PassDB { + pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { + Self { log, env, db } + } + + pub fn check(&self, txn: &T, authcid: &str, password: &[u8]) -> Result> { + match txn.get(self.db, &authcid.as_bytes()) { + Ok(bytes) => { + let encoded = unsafe { std::str::from_utf8_unchecked(bytes) }; + let res = argon2::verify_encoded(encoded, password)?; + Ok(Some(res)) + }, + Err(lmdb::Error::NotFound) => { Ok(None) }, + Err(e) => { Err(e.into()) }, + } + } + + pub fn store(&self, txn: &mut RwTransaction, authcid: &str, password: &[u8]) -> Result<()> { + let config = argon2::Config::default(); + let salt: [u8; 16] = rand::random(); + let hash = argon2::hash_encoded(password, &salt, &config)?; + txn.put(self.db, &authcid.as_bytes(), &hash.as_bytes(), lmdb::WriteFlags::empty()) + .map_err(Into::into) + } +} diff --git a/src/db/user.rs b/src/db/user.rs index e624025..2acab80 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -1,14 +1,25 @@ +//! UserDB does two kinds of lookups: +//! 1. "I have this here username, what user is that" +//! 2. "I have this here user, what are their roles (and other associated data)" use serde::{Serialize, Deserialize}; use std::fmt; use crate::db::access::RoleIdentifier; use std::collections::HashMap; +mod internal; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct User { + pub id: UserId, + pub data: UserData, +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] /// Authorization Identity /// /// This identity is internal to FabAccess and completely independent from the authentication /// method or source -struct AuthZId { +pub struct UserId { /// Main User ID. Generally an user name or similar uid: String, /// Sub user ID. @@ -16,20 +27,36 @@ struct AuthZId { /// Can change scopes for permissions, e.g. having a +admin account with more permissions than /// the default account and +dashboard et.al. accounts that have restricted permissions for /// their applications - subuid: String, + subuid: Option, /// Realm this account originates. /// /// The Realm is usually described by a domain name but local policy may dictate an unrelated /// mapping - realm: String, + realm: Option, } -/// A Person, from the Authorization perspective -#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub struct AuthzContext { - /// The identification of this user. - pub id: AuthZId, +impl UserId { + pub fn new(uid: String, subuid: Option, realm: Option) -> Self { + Self { uid, subuid, realm } + } +} +impl fmt::Display for UserId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let r = write!(f, "{}", self.uid); + if let Some(ref s) = self.subuid { + write!(f, "+{}", s)?; + } + if let Some(ref l) = self.realm { + write!(f, "@{}", l)?; + } + r + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +/// A Person, from the Authorization perspective +pub struct UserData { /// A Person has N ≥ 0 roles. /// Persons are only ever given roles, not permissions directly pub roles: Vec, @@ -47,7 +74,7 @@ mod tests { fn format_uid_test() { let uid = "testuser".to_string(); let suid = "testsuid".to_string(); - let location = "testloc".to_string(); + let realm = "testloc".to_string(); assert_eq!("testuser", format!("{}", UserIdentifier::new(uid.clone(), None, None))); @@ -56,6 +83,6 @@ mod tests { assert_eq!("testuser+testsuid", format!("{}", UserIdentifier::new(uid.clone(), Some(suid.clone()), None))); assert_eq!("testuser+testsuid@testloc", - format!("{}", UserIdentifier::new(uid, Some(suid), Some(location)))); + format!("{}", UserIdentifier::new(uid, Some(suid), Some(realm)))); } } diff --git a/src/db/user/internal.rs b/src/db/user/internal.rs new file mode 100644 index 0000000..49c5580 --- /dev/null +++ b/src/db/user/internal.rs @@ -0,0 +1,49 @@ +use std::sync::Arc; + +use slog::Logger; +use lmdb::{Environment, Transaction, RwTransaction, Cursor}; + +use crate::error::Result; + +use super::*; + +#[derive(Clone, Debug)] +pub struct Internal { + log: Logger, + env: Arc, + db: lmdb::Database, +} + +impl Internal { + pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { + Self { log, env, db } + } + + pub fn get_user_txn(&self, txn: &T, uid: &str) -> Result> { + match txn.get(self.db, &uid.as_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, uid: &str) -> Result> { + let txn = self.env.begin_ro_txn()?; + self.get_user_txn(&txn, uid) + } + + pub fn put_user_txn(&self, txn: &mut RwTransaction, uid: &str, user: &User) -> Result<()> { + let bytes = flexbuffers::to_vec(user)?; + txn.put(self.db, &uid.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; + + Ok(()) + } + pub fn put_user(&self, uid: &str, user: &User) -> Result<()> { + let mut txn = self.env.begin_rw_txn()?; + self.put_user_txn(&mut txn, uid, user)?; + txn.commit()?; + + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 008a51f..c497e72 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,6 +66,9 @@ impl fmt::Display for Error { Error::Config(e) => { write!(f, "Failed to parse config: {}", e) } + Error::Argon2(e) => { + write!(f, "Argon2 en/decoding failure: {}", e) + } Error::BadVersion((major,minor)) => { write!(f, "Peer uses API version {}.{} which is incompatible!", major, minor) } diff --git a/src/machine.rs b/src/machine.rs index 87463ad..85f03b9 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -14,6 +14,7 @@ use crate::error::Result; use crate::db::access; use crate::db::machine::{MachineIdentifier, Status, MachineState}; +use crate::db::user::User; #[derive(Debug)] /// Internal machine representation @@ -66,7 +67,7 @@ impl Machine { ) -> Result { // TODO: Check different levels - if access.check(who, &self.desc.privs.write).await? { + if access.check(&who.data, &self.desc.privs.write).await? { self.state.set(MachineState { state: Status::InUse(who.id.clone()) }); return Ok(true); } else { diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..e69de29