diff --git a/bffhd/authentication/mod.rs b/bffhd/authentication/mod.rs index 6921a39..11cfeb9 100644 --- a/bffhd/authentication/mod.rs +++ b/bffhd/authentication/mod.rs @@ -1,11 +1,12 @@ -use std::sync::Arc; -use rsasl::error::{SASLError, SessionError}; -use rsasl::mechname::Mechname; -use rsasl::{Property, SASL}; -use rsasl::session::{Session, SessionData}; -use rsasl::validate::Validation; use crate::users::db::UserDB; use crate::users::Users; +use rsasl::error::{SASLError, SessionError}; +use rsasl::mechname::Mechname; +use rsasl::property::{AuthId, Password}; +use rsasl::session::{Session, SessionData}; +use rsasl::validate::{validations, Validation}; +use rsasl::{Property, SASL}; +use std::sync::Arc; pub mod db; @@ -14,12 +15,40 @@ struct Callback { } impl Callback { pub fn new(users: Users) -> Self { - Self { users, } + Self { users } } } impl rsasl::callback::Callback for Callback { - fn validate(&self, session: &mut SessionData, validation: Validation, mechanism: &Mechname) -> Result<(), SessionError> { - todo!() + fn validate( + &self, + session: &mut SessionData, + validation: Validation, + mechanism: &Mechname, + ) -> Result<(), SessionError> { + match validation { + validations::SIMPLE => { + let authnid = session + .get_property::() + .ok_or(SessionError::no_property::())?; + let user = self + .users + .get_user(authnid.as_str()) + .ok_or(SessionError::AuthenticationFailure)?; + let passwd = session + .get_property::() + .ok_or(SessionError::no_property::())?; + + if user + .check_password(passwd.as_bytes()) + .map_err(|e| SessionError::AuthenticationFailure)? + { + Ok(()) + } else { + Err(SessionError::AuthenticationFailure) + } + } + _ => Err(SessionError::no_validate(validation)), + } } } @@ -41,14 +70,20 @@ impl AuthenticationHandle { pub fn new(userdb: Users) -> Self { let mut rsasl = SASL::new(); rsasl.install_callback(Arc::new(Callback::new(userdb))); - Self { inner: Arc::new(Inner::new(rsasl)) } + Self { + inner: Arc::new(Inner::new(rsasl)), + } } pub fn start(&self, mechanism: &Mechname) -> anyhow::Result { Ok(self.inner.rsasl.server_start(mechanism)?) } - pub fn list_available_mechs(&self) -> impl IntoIterator { - self.inner.rsasl.server_mech_list().into_iter().map(|m| m.mechanism) + pub fn list_available_mechs(&self) -> impl IntoIterator { + self.inner + .rsasl + .server_mech_list() + .into_iter() + .map(|m| m.mechanism) } -} \ No newline at end of file +} diff --git a/bffhd/authorization/mod.rs b/bffhd/authorization/mod.rs index e34547e..93e043b 100644 --- a/bffhd/authorization/mod.rs +++ b/bffhd/authorization/mod.rs @@ -1,34 +1,26 @@ use std::sync::Arc; use crate::authorization::permissions::Permission; -use crate::authorization::roles::Role; +use crate::authorization::roles::{Role, Roles}; use crate::Users; -use crate::users::User; +use crate::users::UserRef; pub mod permissions; pub mod roles; -struct Inner { - users: Users, -} -impl Inner { - pub fn new(users: Users) -> Self { - Self { users } - } -} - #[derive(Clone)] pub struct AuthorizationHandle { users: Users, + roles: Roles, } impl AuthorizationHandle { - pub fn new(users: Users) -> Self { - Self { users } + pub fn new(users: Users, roles: Roles) -> Self { + Self { users, roles } } - pub fn get_user_roles(&self, uid: impl AsRef) -> Option> { + pub fn get_user_roles(&self, uid: impl AsRef) -> Option> { let user = self.users.get_user(uid.as_ref())?; - Some([]) + Some(user.userdata.roles.clone()) } pub fn is_permitted<'a>(&self, roles: impl IntoIterator, perm: impl AsRef) -> bool { diff --git a/bffhd/authorization/roles.rs b/bffhd/authorization/roles.rs index 72a368d..d722c00 100644 --- a/bffhd/authorization/roles.rs +++ b/bffhd/authorization/roles.rs @@ -1,6 +1,32 @@ +use std::collections::HashMap; use std::fmt; +use once_cell::sync::OnceCell; use crate::authorization::permissions::PermRule; +static ROLES: OnceCell> = OnceCell::new(); + +#[derive(Copy, Clone)] +pub struct Roles { + roles: &'static HashMap, +} + +impl Roles { + pub fn new(roles: HashMap) -> Self { + let span = tracing::debug_span!("roles", "Creating Roles handle"); + let _guard = span.enter(); + + let this = ROLES.get_or_init(|| { + tracing::debug!("Initializing global roles…"); + roles + }); + Self { roles: this } + } + + pub fn get(self, roleid: &str) -> Option<&Role> { + self.roles.get(roleid) + } +} + /// A "Role" from the Authorization perspective /// /// You can think of a role as a bundle of permissions relating to other roles. In most cases a @@ -22,7 +48,7 @@ pub struct Role { /// This makes situations where different levels of access are required easier: Each higher /// level of access sets the lower levels of access as parent, inheriting their permission; if /// you are allowed to manage a machine you are then also allowed to use it and so on - parents: Vec, + parents: Vec, // If a role doesn't define permissions, default to an empty Vec. #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -30,7 +56,7 @@ pub struct Role { } impl Role { - pub fn new(parents: Vec, permissions: Vec) -> Self { + pub fn new(parents: Vec, permissions: Vec) -> Self { Self { parents, permissions } } } @@ -58,89 +84,4 @@ impl fmt::Display for Role { Ok(()) } -} - -type SourceID = String; - -fn split_once(s: &str, split: char) -> Option<(&str, &str)> { - s - .find(split) - .map(|idx| (&s[..idx], &s[(idx+1)..])) -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -#[serde(try_from = "String")] -#[serde(into = "String")] -/// Universal (relative) id of a role -pub struct RoleIdentifier { - /// 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, -} - -impl RoleIdentifier { - pub fn new<>(name: &str, source: &str) -> Self { - Self { name: name.to_string(), source: source.to_string() } - } - pub fn from_strings(name: String, source: String) -> Self { - Self { name, source } - } -} - -impl fmt::Display for RoleIdentifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.source != "" { - write!(f, "{}/{}", self.name, self.source) - } else { - write!(f, "{}", self.name) - } - } -} - -impl std::str::FromStr for RoleIdentifier { - type Err = RoleFromStrError; - - fn from_str(s: &str) -> std::result::Result { - if let Some((name, source)) = split_once(s, '/') { - Ok(RoleIdentifier { name: name.to_string(), source: source.to_string() }) - } else { - Ok(RoleIdentifier { name: s.to_string(), source: String::new() }) - } - } -} - -impl TryFrom for RoleIdentifier { - type Error = RoleFromStrError; - - fn try_from(s: String) -> std::result::Result { - ::from_str(&s) - } -} -impl Into for RoleIdentifier { - fn into(self) -> String { - format!("{}", self) - } -} - -impl RoleIdentifier { - pub fn local_from_str(source: String, name: String) -> Self { - RoleIdentifier { name, source } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum RoleFromStrError { - /// No '@' or '%' found. That's strange, huh? - Invalid -} - -impl fmt::Display for RoleFromStrError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RoleFromStrError::Invalid - => write!(f, "Rolename are of form 'name%source' or 'name@realm'."), - } - } -} +} \ No newline at end of file diff --git a/bffhd/capnp/session.rs b/bffhd/capnp/session.rs index dfef19a..c02ec9e 100644 --- a/bffhd/capnp/session.rs +++ b/bffhd/capnp/session.rs @@ -5,7 +5,7 @@ use crate::capnp::machinesystem::Machines; use crate::capnp::permissionsystem::Permissions; use crate::capnp::user_system::Users; use crate::session::{SessionHandle, SessionManager}; -use crate::users::User; +use crate::users::UserRef; #[derive(Debug, Clone)] pub struct APISession; diff --git a/bffhd/config.rs b/bffhd/config.rs index b161700..034739a 100644 --- a/bffhd/config.rs +++ b/bffhd/config.rs @@ -9,7 +9,6 @@ use std::net::{SocketAddr, IpAddr, ToSocketAddrs}; use std::str::FromStr; use serde::de::Error; use crate::authorization::permissions::{PermRule, PrivilegesBuf}; -use crate::authorization::roles::RoleIdentifier; type Result = std::result::Result; @@ -66,7 +65,7 @@ pub struct Config { pub db_path: PathBuf, pub auditlog_path: PathBuf, - pub roles: HashMap, + pub roles: HashMap, #[serde(flatten)] pub tlsconfig: TlsListen, @@ -90,7 +89,7 @@ impl Config { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoleConfig { #[serde(default = "Vec::new")] - pub parents: Vec, + pub parents: Vec, #[serde(default = "Vec::new")] pub permissions: Vec, } diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 92df9af..e0b97ea 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -119,7 +119,7 @@ impl Diflouroborane { let tlsconfig = TlsConfig::new(config.tlskeylog.as_ref(), !config.is_quiet())?; let acceptor = tlsconfig.make_tls_acceptor(&config.tlsconfig)?; - let sessionmanager = SessionManager::new(); + let sessionmanager = SessionManager::new(userdb.clone()); let authentication = AuthenticationHandle::new(userdb.clone()); let mut apiserver = self.executor.run(APIServer::bind(self.executor.clone(), &config.listens, acceptor, sessionmanager, authentication))?; diff --git a/bffhd/resources/mod.rs b/bffhd/resources/mod.rs index 2f098a4..63d6422 100644 --- a/bffhd/resources/mod.rs +++ b/bffhd/resources/mod.rs @@ -9,7 +9,7 @@ use crate::resources::modules::fabaccess::{MachineState, Status}; use crate::resources::state::db::StateDB; use crate::resources::state::State; use crate::session::SessionHandle; -use crate::users::User; +use crate::users::UserRef; pub mod claim; pub mod db; @@ -173,7 +173,7 @@ impl Resource { session.has_disclose(self) || self.is_owned_by(session.get_user()) } - pub fn is_owned_by(&self, owner: User) -> bool { + pub fn is_owned_by(&self, owner: UserRef) -> bool { match self.get_state().state { Status::Free | Status::Disabled => false, diff --git a/bffhd/resources/modules/fabaccess.rs b/bffhd/resources/modules/fabaccess.rs index aff7275..adeef2c 100644 --- a/bffhd/resources/modules/fabaccess.rs +++ b/bffhd/resources/modules/fabaccess.rs @@ -9,7 +9,7 @@ use crate::oidvalue; use crate::resources::state::{State}; use crate::resources::state::value::Value; use crate::session::SessionHandle; -use crate::users::User; +use crate::users::UserRef; /// Status of a Machine #[derive( @@ -28,15 +28,15 @@ pub enum Status { /// Not currently used by anybody Free, /// Used by somebody - InUse(User), + InUse(UserRef), /// Was used by somebody and now needs to be checked for cleanliness - ToCheck(User), + ToCheck(UserRef), /// Not used by anybody but also can not be used. E.g. down for maintenance - Blocked(User), + Blocked(UserRef), /// Disabled for some other reason Disabled, /// Reserved - Reserved(User), + Reserved(UserRef), } #[derive( @@ -54,7 +54,7 @@ pub enum Status { /// The status of the machine pub struct MachineState { pub state: Status, - pub previous: Option, + pub previous: Option, } impl MachineState { @@ -76,42 +76,42 @@ impl MachineState { } } - pub fn free(previous: Option) -> Self { + pub fn free(previous: Option) -> Self { Self { state: Status::Free, previous, } } - pub fn used(user: User, previous: Option) -> Self { + pub fn used(user: UserRef, previous: Option) -> Self { Self { state: Status::InUse(user), previous, } } - pub fn blocked(user: User, previous: Option) -> Self { + pub fn blocked(user: UserRef, previous: Option) -> Self { Self { state: Status::Blocked(user), previous, } } - pub fn disabled(previous: Option) -> Self { + pub fn disabled(previous: Option) -> Self { Self { state: Status::Disabled, previous, } } - pub fn reserved(user: User, previous: Option) -> Self { + pub fn reserved(user: UserRef, previous: Option) -> Self { Self { state: Status::Reserved(user), previous, } } - pub fn check(user: User) -> Self { + pub fn check(user: UserRef) -> Self { Self { state: Status::ToCheck(user.clone()), previous: Some(user), diff --git a/bffhd/session/db.rs b/bffhd/session/db.rs new file mode 100644 index 0000000..37e0eb3 --- /dev/null +++ b/bffhd/session/db.rs @@ -0,0 +1,37 @@ +use std::sync::Arc; +use lmdb::{DatabaseFlags, Environment}; + +use rkyv::{Archive, Serialize, Deserialize}; + +use crate::db::{AllocAdapter, DB, RawDB}; +use crate::users::UserRef; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Archive, Serialize, Deserialize)] +pub struct Session { + userid: UserRef, +} + +type Adapter = AllocAdapter; +pub struct SessionCache { + env: Arc, + db: DB, +} + +impl SessionCache { + pub unsafe fn new(env: Arc, db: RawDB) -> Self { + let db = DB::new_unchecked(db); + Self { env, db } + } + + pub unsafe fn open(env: Arc) -> lmdb::Result { + let db = RawDB::open(&env, Some("sessions"))?; + Ok(Self::new(env, db)) + } + + pub unsafe fn create(env: Arc) -> lmdb::Result { + let flags = DatabaseFlags::empty(); + let db = RawDB::create(&env, Some("sessions"), flags)?; + Ok(Self::new(env, db)) + } +} \ No newline at end of file diff --git a/bffhd/session/mod.rs b/bffhd/session/mod.rs index b1a0c3a..e39f513 100644 --- a/bffhd/session/mod.rs +++ b/bffhd/session/mod.rs @@ -1,38 +1,46 @@ +use std::path::PathBuf; use std::sync::Arc; +use anyhow::Context; +use lmdb::Environment; +use once_cell::sync::OnceCell; use crate::authorization::roles::Role; use crate::resources::Resource; -use crate::users::User; +use crate::session::db::SessionCache; +use crate::Users; +use crate::users::UserRef; -struct Inner { +mod db; -} -impl Inner { - pub fn new() -> Self { - Self { } - } -} +static SESSION_CACHE: OnceCell = OnceCell::new(); #[derive(Clone)] pub struct SessionManager { - inner: Arc, + users: Users, } impl SessionManager { - pub fn new() -> Self { - Self { - inner: Arc::new(Inner::new()), - } + pub fn new(users: Users) -> Self { + Self { users } } + + // TODO: make infallible pub fn open(&self, uid: impl AsRef) -> Option { - unimplemented!() + let uid = uid.as_ref(); + if let Some(user) = self.users.get_user(uid) { + tracing::trace!(uid, "opening new session for user"); + Some(SessionHandle { user: UserRef::new(user.id) }) + } else { + None + } } } #[derive(Clone, Debug)] pub struct SessionHandle { + user: UserRef, } impl SessionHandle { - pub fn get_user(&self) -> User { + pub fn get_user(&self) -> UserRef { unimplemented!() } diff --git a/bffhd/users/db.rs b/bffhd/users/db.rs index 62a4370..18ecda9 100644 --- a/bffhd/users/db.rs +++ b/bffhd/users/db.rs @@ -4,9 +4,9 @@ use lmdb::{RwTransaction, Transaction}; use std::collections::{HashMap, HashSet}; use std::path::Path; use std::sync::Arc; +use anyhow::Context; use rkyv::{Archived, Deserialize}; -use crate::authorization::roles::RoleIdentifier; #[derive( Clone, @@ -24,6 +24,17 @@ pub struct User { pub userdata: UserData, } +impl User { + pub fn check_password(&self, pwd: &[u8]) -> anyhow::Result { + if let Some(ref encoded) = self.userdata.passwd { + argon2::verify_encoded(encoded, pwd) + .context("Stored password is an invalid string") + } else { + Ok(false) + } + } +} + #[derive( Clone, PartialEq, diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index a2714e2..bfec026 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -26,7 +26,7 @@ use std::sync::Arc; pub mod db; pub use crate::authentication::db::PassDB; -use crate::authorization::roles::{Role, RoleIdentifier}; +use crate::authorization::roles::Role; use crate::db::LMDBorrow; use crate::users::db::UserData; use crate::UserDB; @@ -43,13 +43,13 @@ use crate::UserDB; serde::Deserialize, )] #[archive_attr(derive(Debug, PartialEq))] -pub struct User { +pub struct UserRef { id: String, } -impl User { +impl UserRef { pub fn new(id: String) -> Self { - User { id } + UserRef { id } } pub fn get_username(&self) -> &str { @@ -73,7 +73,7 @@ impl Users { let userdb = USERDB .get_or_try_init(|| { tracing::debug!("Global resource not yet initialized, initializing…"); - unsafe { UserDB::create(env.clone()) } + unsafe { UserDB::create(env) } }) .context("Failed to open userdb")?;