Start the user API

This commit is contained in:
Nadja Reitzenstein 2021-09-20 13:47:08 +02:00
parent 006ae0af68
commit 4e3bb44040
8 changed files with 364 additions and 254 deletions

View File

@ -1,3 +1,9 @@
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;
use slog::Logger;
use std::sync::Arc;
use capnp::capability::{Params, Results, Promise};
@ -21,15 +27,19 @@ use users::Users;
// TODO Session restoration by making the Bootstrap cap a SturdyRef
pub struct Bootstrap {
session: Arc<Session>,
log: Logger,
db: Databases,
nw: Arc<Network>,
session: Rc<RefCell<Option<Session>>>,
}
impl Bootstrap {
pub fn new(session: Arc<Session>, db: Databases, nw: Arc<Network>) -> Self {
info!(session.log, "Created Bootstrap");
Self { session, db, nw }
pub fn new(log: Logger, db: Databases, nw: Arc<Network>) -> Self {
info!(log, "Created Bootstrap");
let session = Rc::new(RefCell::new(None));
Self { session, db, nw, log }
}
}
@ -43,7 +53,15 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
// 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)?
res.get().set_authentication_system(capnp_rpc::new_client(auth::Auth::new(self.db.clone(), self.session.clone())));
// If this Rc has a strong count of 1 then there's no other cap issued yet meaning we can
// safely transform the inner session with an auth.
if Rc::strong_count(&self.session) == 1 {
let session = Rc::clone(&self.session);
let db = self.db.clone();
res.get().set_authentication_system(capnp_rpc::new_client(
auth::Auth::new(self.log.new(o!()), db, session))
);
}
Promise::ok(())
}
@ -52,32 +70,19 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
_: MachineSystemParams,
mut res: MachineSystemResults
) -> Promise<(), capnp::Error> {
let session = self.session.clone();
let accessdb = self.db.access.clone();
let nw = self.nw.clone();
let f = async move {
// Ensure the lock is dropped as soon as possible
if let Some(user) = { session.user.lock().await.clone() } {
let perms = accessdb.collect_permrules(&user.data)
.map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))?;
debug!(session.log, "Giving MachineSystem cap to user {} with perms:", user.id);
for r in perms.iter() {
if let Some(session) = self.session.borrow().deref() {
debug!(self.log, "Giving MachineSystem cap to user {} with perms:", session.authzid);
for r in session.perms.iter() {
debug!(session.log, " {}", r);
}
// TODO actual permission check and stuff
// Right now we only check that the user has authenticated at all.
let c = capnp_rpc::new_client(Machines::new(user.id, perms, nw));
let c = capnp_rpc::new_client(Machines::new(Rc::clone(&self.session), self.nw.clone()));
res.get().set_machine_system(c);
}
// Promise is Ok either way, just the machine system may not be set, indicating as
// usual a lack of permission.
Ok(())
};
Promise::from_future(f)
Promise::ok(())
}
fn user_system(
@ -85,25 +90,13 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
_: UserSystemParams,
mut results: UserSystemResults
) -> Promise<(), capnp::Error> {
let session = self.session.clone();
let accessdb = self.db.access.clone();
let f = async move {
// Ensure the lock is dropped as soon as possible
if let Some(user) = { session.user.lock().await.clone() } {
let perms = accessdb.collect_permrules(&user.data)
.map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))?;
if self.session.borrow().is_some() {
// TODO actual permission check and stuff
// Right now we only check that the user has authenticated at all.
let c = capnp_rpc::new_client(Users::new(perms));
let c = capnp_rpc::new_client(Users::new(Rc::clone(&self.session), self.db.userdb.clone()));
results.get().set_user_system(c);
}
// Promise is Ok either way, just the machine system may not be set, indicating as
// usual a lack of permission.
Ok(())
};
Promise::from_future(f)
Promise::ok(())
}
}

View File

@ -4,6 +4,11 @@
//! Authentication using SASL
use std::sync::Arc;
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;
use slog::Logger;
use rsasl::{
SASL,
@ -24,14 +29,15 @@ use crate::api::Session;
pub use crate::schema::authenticationsystem_capnp as auth_system;
use crate::db::Databases;
use crate::db::pass::PassDB;
use crate::db::user::{Internal as UserDB};
use crate::db::user::{Internal as UserDB, UserId, User};
use crate::db::access::AccessControl as AccessDB;
pub struct AppData {
passdb: Arc<PassDB>,
userdb: Arc<UserDB>,
}
pub struct SessionData {
session: Arc<Session>,
authz: Option<User>,
}
struct CB;
@ -45,7 +51,6 @@ impl Callback<AppData, SessionData> for CB {
Property::GSASL_VALIDATE_SIMPLE => {
// FIXME: get_property and retrieve_mut can't be used interleaved but that's
// technically safe.
let cap_session = session.retrieve_mut().map(|sd| sd.session.clone());
let authid: &str = session
.get_property(Property::GSASL_AUTHID)
@ -60,27 +65,14 @@ impl Callback<AppData, SessionData> for CB {
if let Some(appdata) = sasl.retrieve_mut() {
if let Ok(Some(b)) = appdata.passdb.check(authid, pass.to_bytes()) {
if b {
if let Some(s) = cap_session {
if let Ok(Some(user)) = appdata.userdb.get_user(authid) {
// FIXME: This should set the userid outside the callback
s.user.try_lock().unwrap().replace(user);
if let Ok(Some(user)) = appdata.userdb.login(authid, pass.to_bytes()) {
session.retrieve_mut().unwrap().authz.replace(user);
return Ok(());
}
}
// Early return, implicitly returns GSASL_OK
return Ok(());
} else {
ReturnCode::GSASL_AUTHENTICATION_ERROR
}
} else {
ReturnCode::GSASL_AUTHENTICATION_ERROR
}
} else {
ReturnCode::GSASL_AUTHENTICATION_ERROR
}
}
p => {
println!("Callback called with property {:?}", p);
ReturnCode::GSASL_NO_CALLBACK
@ -90,29 +82,28 @@ impl Callback<AppData, SessionData> for CB {
}
}
pub struct Auth {
pub struct Auth<'a> {
pub ctx: RSASL<AppData, SessionData>,
session: Arc<Session>,
session: Rc<RefCell<Option<Session>>>,
access: Arc<AccessDB>,
log: Logger,
}
impl Auth {
pub fn new(dbs: Databases, session: Arc<Session>) -> Self {
impl<'a> Auth<'a> {
pub fn new(log: Logger, dbs: Databases, session: Rc<RefCell<Option<Session>>>) -> Self {
let mut ctx = SASL::new().unwrap();
let appdata = Box::new(AppData { passdb: dbs.passdb.clone(), userdb: dbs.userdb.clone() });
ctx.store(appdata);
ctx.install_callback::<CB>();
info!(session.log, "Auth created");
Self { ctx, session }
Self { log, ctx, session, access: dbs.access.clone() }
}
}
use crate::schema::authenticationsystem_capnp::*;
impl authentication_system::Server for Auth {
impl<'a> authentication_system::Server for Auth<'a> {
fn mechanisms(&mut self,
_: authentication_system::MechanismsParams,
mut res: authentication_system::MechanismsResults
@ -166,7 +157,7 @@ impl authentication_system::Server for Auth {
}),
};
session.store(Box::new(SessionData { session: self.session.clone() }));
session.store(Box::new(SessionData { authz: None }));
// If the client has provided initial data go use that
use request::initial_response::Which;
@ -188,12 +179,28 @@ impl authentication_system::Server for Auth {
// The step may either return an error, a success or the need for more data
// TODO: Set the session user. Needs a lookup though <.>
use response::Result as Resres;
match step_res {
Ok(Step::Done(b)) => {
use response::Result;
let user = session
.retrieve_mut()
.and_then(|data| {
data.authz.take()
})
.expect("Authentication returned OK but didn't set user id");
let perms = pry!(self.access.collect_permrules(&user.data)
.map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e))));
self.session.replace(Session::new(
self.log.new(o!()),
user.id,
"".to_string(),
user.data.roles.into_boxed_slice(),
perms.into_boxed_slice()
));
let mut outcome = pry!(res.get().get_response()).init_outcome();
outcome.reborrow().set_result(Result::Successful);
outcome.reborrow().set_result(Resres::Successful);
if b.len() != 0 {
outcome.init_additional_data().set_additional(&b);
}
@ -204,11 +211,13 @@ impl authentication_system::Server for Auth {
Promise::ok(())
}
// TODO: This should really be an outcome because this is failed auth just as much atm.
Err(e) =>
return Promise::err(capnp::Error {
kind: capnp::ErrorKind::Failed,
description: format!("SASL error: {}", e),
}),
Err(e) => {
let mut outcome = pry!(res.get().get_response()).init_outcome();
outcome.reborrow().set_result(Resres::Failed);
let text = format!("{}", e);
outcome.set_help_text(&text);
Promise::ok(())
}
}
}

View File

@ -1,4 +1,7 @@
use std::sync::Arc;
use std::cell::RefCell;
use std::rc::Rc;
use std::ops::Deref;
use capnp::capability::Promise;
use capnp::Error;
@ -11,18 +14,18 @@ use crate::schema::machinesystem_capnp::machine_system::info as machines;
use crate::network::Network;
use crate::db::user::UserId;
use crate::db::access::{PermRule, admin_perm};
use crate::connection::Session;
/// An implementation of the `Machines` API
#[derive(Clone)]
pub struct Machines {
user: UserId,
permissions: Vec<PermRule>,
session: Rc<RefCell<Option<Session>>>,
network: Arc<Network>,
}
impl Machines {
pub fn new(user: UserId, permissions: Vec<PermRule>, network: Arc<Network>) -> Self {
Self { user, permissions, network }
pub fn new(session: Rc<RefCell<Option<Session>>>, network: Arc<Network>) -> Self {
Self { session, network }
}
}
@ -44,10 +47,13 @@ impl machines::Server for Machines {
mut results: machines::GetMachineListResults)
-> Promise<(), Error>
{
let session = Rc::clone(&self.session);
let session = session.borrow();
if let Some(session) = session.deref() {
let v: Vec<(String, crate::machine::Machine)> = self.network.machines.iter()
.filter(|(_name, machine)| {
let required_disclose = &machine.desc.privs.disclose;
for perm_rule in self.permissions.iter() {
for perm_rule in session.perms.iter() {
if perm_rule.match_perm(required_disclose) {
return true;
}
@ -58,8 +64,8 @@ impl machines::Server for Machines {
.map(|(n,m)| (n.clone(), m.clone()))
.collect();
let permissions = self.permissions.clone();
let user = self.user.clone();
let permissions = &session.perms;
let user = &session.authzid;
let f = async move {
let mut machines = results.get().init_machine_list(v.len() as u32);
@ -103,21 +109,24 @@ impl machines::Server for Machines {
};
Promise::from_future(f)
} else {
Promise::ok(())
}
}
fn get_machine(&mut self,
params: machines::GetMachineParams,
mut results: machines::GetMachineResults
) -> Promise<(), capnp::Error> {
if let Some(session) = self.session.borrow().deref() {
let name = {
let params = pry!(params.get());
pry!(params.get_name()).to_string()
};
let network = self.network.clone();
let user = self.user.clone();
let permissions = self.permissions.clone();
let user = session.authzid;
let permissions = session.perms;
let f = async move {
if let Some(machine) = network.machines.get(&name) {
@ -158,5 +167,8 @@ impl machines::Server for Machines {
Ok(())
};
Promise::from_future(f)
} else {
Promise::ok(())
}
}
}

View File

@ -1,27 +1,42 @@
use crate::db::access::PermRule;
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;
use crate::connection::Session;
use crate::db::user as db;
use crate::schema::user_capnp::user::*;
#[derive(Clone)]
pub struct User {
user: db::User,
perms: Vec<PermRule>,
session: Rc<RefCell<Option<Session>>>,
}
impl User {
pub fn new(user: db::User, perms: Vec<PermRule>) -> Self {
Self { user, perms }
pub fn new(session: Rc<RefCell<Option<Session>>>) -> Self {
Self { session }
}
pub fn fill(&self, builder: &mut Builder) {
builder.set_username(&self.user.id.uid);
if let Some(ref realm) = &self.user.id.realm {
pub fn fill_self(&self, builder: &mut Builder) {
if let Some(session) = self.session.borrow().deref() {
self.fill_userid(builder, &session.authzid);
}
}
pub fn fill_with(&self, builder: &mut Builder, user: db::User) {
self.fill_userid(builder, &user.id)
}
pub fn fill_userid(&self, builder: &mut Builder, uid: &db::UserId) {
builder.set_username(&uid.uid);
if let Some(ref realm) = &uid.realm {
let mut space = builder.reborrow().init_space();
space.set_name(&realm);
}
}
}
impl info::Server for User {}
impl info::Server for User {
}
impl manage::Server for User {}
impl admin::Server for User {}

View File

@ -1,17 +1,27 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use std::ops::Deref;
use capnp::capability::Promise;
use crate::api::user::User;
use crate::connection::Session;
use crate::db::access::{PermRule, Permission};
use crate::db::user::{UserId, Internal as UserDB};
use crate::schema::usersystem_capnp::user_system;
use crate::schema::usersystem_capnp::user_system::{info, manage};
use crate::error;
#[derive(Clone, Debug)]
pub struct Users {
perms: Vec<PermRule>,
session: Rc<RefCell<Option<Session>>>,
userdb: Arc<UserDB>,
}
impl Users {
pub fn new(perms: Vec<PermRule>) -> Self {
Self { perms }
pub fn new(session: Rc<RefCell<Option<Session>>>, userdb: Arc<UserDB>) -> Self {
Self { session, userdb }
}
}
@ -31,11 +41,13 @@ impl user_system::Server for Users {
mut results: user_system::ManageResults,
) -> Promise<(), capnp::Error> {
let perm: &Permission = Permission::new("bffh.users.manage");
if self.perms.iter().any(|rule| rule.match_perm(perm)) {
if let Some(session) = self.session.borrow().deref() {
if session.perms.iter().any(|rule| rule.match_perm(perm)) {
results
.get()
.set_manage(capnp_rpc::new_client(self.clone()));
}
}
Promise::ok(())
}
@ -47,8 +59,48 @@ impl info::Server for Users {
_: info::GetUserSelfParams,
mut results: info::GetUserSelfResults,
) -> Promise<(), capnp::Error> {
let user = User::new(Rc::clone(&self.session));
let mut builder = results.get().init_user();
user.fill_self(&mut builder);
Promise::ok(())
}
}
impl manage::Server for Users {}
impl manage::Server for Users {
fn get_user_list(
&mut self,
_: manage::GetUserListParams,
mut results: manage::GetUserListResults,
) -> Promise<(), capnp::Error> {
let result: Result<(), error::Error> =
self.userdb.list_users()
.and_then(|users| {
let builder = results.get().init_user_list(users.len() as u32);
let u = User::new(Rc::clone(&self.session));
for (i, user) in users.into_iter().enumerate() {
let mut b = builder.reborrow().get(i as u32);
u.fill_with(&mut b, user);
}
Ok(())
});
match result {
Ok(()) => Promise::ok(()),
Err(e) => Promise::err(capnp::Error::failed("User lookup failed: {}".to_string())),
}
}
/*fn add_user(
&mut self,
params: manage::AddUserParams,
mut results: manage::AddUserResults,
) -> Promise<(), capnp::Error> {
}
fn remove_user(
&mut self,
_: manage::RemoveUserParams,
mut results: manage::RemoveUserResults,
) -> Promise<(), capnp::Error> {
}*/
}

View File

@ -1,22 +1,22 @@
use std::sync::Arc;
use std::future::Future;
use futures::FutureExt;
use std::future::Future;
use std::sync::Arc;
use slog::Logger;
use smol::lock::Mutex;
use smol::net::TcpStream;
use crate::error::Result;
use crate::api::Bootstrap;
use crate::error::Result;
use capnp_rpc::{twoparty, rpc_twoparty_capnp};
use capnp_rpc::{rpc_twoparty_capnp, twoparty};
use crate::schema::connection_capnp;
use crate::db::access::{AccessControl, PermRule, RoleIdentifier};
use crate::db::user::UserId;
use crate::db::Databases;
use crate::db::access::{AccessControl, Permission};
use crate::db::user::User;
use crate::network::Network;
#[derive(Debug)]
@ -25,15 +25,42 @@ use crate::network::Network;
pub struct Session {
// Session-spezific log
pub log: Logger,
pub user: Mutex<Option<User>>,
pub accessdb: Arc<AccessControl>,
/// User this session has been authorized as.
///
/// Slightly different than the authnid which indicates as what this session has been
/// authenticated as (e.g. using EXTERNAL auth the authnid would be the CN of the client
/// certificate, but the authzid would be an user)
pub authzid: UserId,
pub authnid: String,
/// Roles this session has been assigned via group memberships, direct role assignment or
/// authentication types
pub roles: Box<[RoleIdentifier]>,
/// Permissions this session has.
///
/// This is a snapshot of the permissions the underlying user has
/// take at time of creation (i.e. session establishment)
pub perms: Box<[PermRule]>,
}
impl Session {
pub fn new(log: Logger, accessdb: Arc<AccessControl>) -> Self {
let user = Mutex::new(None);
Session { log, user, accessdb }
pub fn new(
log: Logger,
authzid: UserId,
authnid: String,
roles: Box<[RoleIdentifier]>,
perms: Box<[PermRule]>,
) -> Self {
Session {
log,
authzid,
authnid,
roles,
perms,
}
}
}
@ -50,12 +77,15 @@ impl ConnectionHandler {
pub fn handle(&mut self, stream: TcpStream) -> impl Future<Output = Result<()>> {
info!(self.log, "New connection from on {:?}", stream);
let session = Arc::new(Session::new(self.log.new(o!()), self.db.access.clone()));
let boots = Bootstrap::new(session, self.db.clone(), self.network.clone());
let boots = Bootstrap::new(self.log.new(o!()), self.db.clone(), self.network.clone());
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);
let network = twoparty::VatNetwork::new(stream.clone(), stream,
rpc_twoparty_capnp::Side::Server, Default::default());
let network = twoparty::VatNetwork::new(
stream.clone(),
stream,
rpc_twoparty_capnp::Side::Server,
Default::default(),
);
let rpc_system = capnp_rpc::RpcSystem::new(Box::new(network), Some(rpc.client));
// Convert the error type to one of our errors

View File

@ -89,6 +89,10 @@ pub struct UserData {
/// The higher, the higher the priority. Higher priority users overwrite lower priority ones.
pub priority: u64,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub passwd: Option<String>,
/// Additional data storage
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
kv: HashMap<String, String>,
@ -96,11 +100,7 @@ pub struct UserData {
impl UserData {
pub fn new(roles: Vec<RoleIdentifier>, priority: u64) -> Self {
Self {
roles: roles,
priority: priority,
kv: HashMap::new(),
}
Self { roles, priority, kv: HashMap::new(), passwd: None }
}
}
@ -124,30 +124,8 @@ pub fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<String, User>> {
pub fn init(log: Logger, _config: &Config, env: Arc<lmdb::Environment>) -> Result<Internal> {
let mut flags = lmdb::DatabaseFlags::empty();
flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true);
let db = env.create_db(Some("users"), flags)?;
let db = env.create_db(Some("userdb"), flags)?;
debug!(&log, "Opened user db successfully.");
Ok(Internal::new(log, env, db))
}
#[cfg(test_DISABLED)]
mod tests {
use super::*;
#[test]
fn format_uid_test() {
let uid = "testuser".to_string();
let suid = "testsuid".to_string();
let realm = "testloc".to_string();
assert_eq!("testuser",
format!("{}", UserIdentifier::new(uid.clone(), None, None)));
assert_eq!("testuser+testsuid",
format!("{}", UserIdentifier::new(uid.clone(), Some(suid.clone()), None)));
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(realm))));
}
}

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use slog::Logger;
use lmdb::{Environment, Transaction, RwTransaction};
use lmdb::{Environment, Transaction, RwTransaction, Cursor};
use crate::error::Result;
@ -46,4 +46,25 @@ impl Internal {
Ok(())
}
pub fn list_users(&self) -> Result<Vec<User>> {
let txn = self.env.begin_ro_txn()?;
Ok(self.list_users_txn(&txn)?.collect())
}
pub fn list_users_txn<T: Transaction>(&self, txn: &T) -> Result<impl Iterator<Item=User>> {
let mut cursor = txn.open_ro_cursor(self.db)?;
Ok(cursor.iter_start().map(|buf| {
let (_kbuf, vbuf) = buf.unwrap();
flexbuffers::from_slice(vbuf).unwrap()
}))
}
pub fn login(&self, uid: &str, password: &[u8]) -> Result<Option<User>> {
let txn = self.env.begin_ro_txn()?;
Ok(self.get_user_txn(&txn, uid)?
.filter(|user| {
user.data.passwd.is_some()
&& argon2::verify_encoded(user.data.passwd.as_ref().unwrap(), password).unwrap_or(false)
}))
}
}