Lots of changes for better API stuffs

This commit is contained in:
Gregor Reitzenstein 2020-11-20 13:06:55 +01:00
parent f8b9874f08
commit e7bbc7e001
12 changed files with 154 additions and 104 deletions

View File

@ -7,6 +7,8 @@ use capnp::capability::{Params, Results, Promise};
use crate::schema::connection_capnp;
use crate::connection::Session;
use crate::db::Databases;
pub mod auth;
mod machine;
mod machines;
@ -14,13 +16,14 @@ mod machines;
use machines::Machines;
pub struct Bootstrap {
session: Arc<Session>
session: Arc<Session>,
db: Databases,
}
impl Bootstrap {
pub fn new(session: Arc<Session>) -> Self {
pub fn new(session: Arc<Session>, db: Databases) -> Self {
info!(session.log, "Created Bootstrap");
Self { session }
Self { session, db }
}
}
@ -53,7 +56,7 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
) -> Promise<(), capnp::Error> {
// TODO actual permission check and stuff
if self.session.user.is_some() {
let c = capnp_rpc::new_client(Machines::new(self.session.clone()));
let c = capnp_rpc::new_client(Machines::new(self.session.clone(), self.db.clone()));
res.get().set_machines(c);
}

View File

@ -1,18 +1,27 @@
use crate::schema::api_capnp::machine::*;
use std::sync::Arc;
use capnp::capability::Promise;
use capnp::Error;
use crate::schema::api_capnp::machine::*;
use crate::db::machine::MachineIdentifier;
use crate::connection::Session;
use crate::db::Databases;
struct Machine;
#[derive(Clone)]
pub struct Machine {
session: Arc<Session>,
id: MachineIdentifier,
db: Databases,
}
impl Machine {
pub fn new() -> Self {
Machine
pub fn new(session: Arc<Session>, id: MachineIdentifier, db: Databases) -> Self {
Machine { session, id, db }
}
}
struct Read;
struct Read(Arc<Machine>);
impl read::Server for Read {
fn info(&mut self,
@ -24,7 +33,7 @@ impl read::Server for Read {
}
}
struct Write;
struct Write(Arc<Machine>);
impl write::Server for Write {
fn use_(&mut self,
@ -36,7 +45,7 @@ impl write::Server for Write {
}
}
struct Manage;
struct Manage(Arc<Machine>);
impl manage::Server for Manage {
fn ok(&mut self,
@ -48,7 +57,7 @@ impl manage::Server for Manage {
}
}
struct Admin;
struct Admin(Arc<Machine>);
impl admin::Server for Admin {
fn force_set_state(&mut self,

View File

@ -1,24 +1,30 @@
use std::sync::Arc;
use slog::Logger;
use capnp::capability::Promise;
use capnp::Error;
use crate::schema::api_capnp::machines;
use crate::connection::Session;
use crate::db::Databases;
use crate::db::machine::uuid_from_api;
use crate::db::machine::MachineDB;
use super::machine::Machine;
/// An implementation of the `Machines` API
pub struct Machines {
/// A reference to the connection — as long as at least one API endpoint is
/// still alive the session has to be as well.
session: Arc<Session>,
db: Databases,
}
impl Machines {
pub fn new(session: Arc<Session>) -> Self {
pub fn new(session: Arc<Session>, db: Databases) -> Self {
info!(session.log, "Machines created");
Self { session }
Self { session, db }
}
}
@ -32,10 +38,25 @@ impl machines::Server for Machines {
}
fn get_machine(&mut self,
_params: machines::GetMachineParams,
params: machines::GetMachineParams,
mut results: machines::GetMachineResults)
-> Promise<(), Error>
{
match params.get() {
Ok(reader) => {
if let Ok(api_id) = reader.get_uuid() {
let id = uuid_from_api(api_id);
if self.db.machine.exists(id) {
// TODO check disclose permission
let builder = results.get().init_machine();
let m = Machine::new(self.session.clone(), id, self.db.clone());
}
}
Promise::ok(())
}
Err(e) => Promise::err(e),
}
}
}

View File

@ -25,7 +25,8 @@ pub fn read(path: &Path) -> Result<Settings> {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
pub listens: Box<[Listen]>,
pub shelly: Option<ShellyCfg>
pub shelly: Option<ShellyCfg>,
pub machines: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -53,6 +54,7 @@ impl Default for Settings {
shelly: Some(ShellyCfg {
mqtt_url: "127.0.0.1:1883".to_string()
}),
machines: PathBuf::from("/etc/bffh/machines/")
}
}
}

View File

@ -12,6 +12,9 @@ use capnp_rpc::{twoparty, rpc_twoparty_capnp};
use crate::schema::connection_capnp;
use crate::db::Databases;
#[derive(Debug, Clone)]
/// Connection context
// TODO this should track over several connections
pub struct Session {
@ -54,12 +57,12 @@ async fn handshake(log: &Logger, stream: &mut TcpStream) -> Result<()> {
}
}
pub async fn handle_connection(log: Logger, stream: TcpStream) -> Result<()> {
pub async fn handle_connection(log: Logger, stream: TcpStream, db: Databases) -> Result<()> {
//handshake(&log, &mut stream).await?;
info!(log, "New connection from on {:?}", stream);
let session = Arc::new(Session::new(log));
let boots = Bootstrap::new(session);
let boots = Bootstrap::new(session, db);
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);
let network = twoparty::VatNetwork::new(stream.clone(), stream,

View File

@ -1,3 +1,6 @@
use std::sync::Arc;
/// Access control storage
///
/// Stores&Retrieves Permissions and Roles
@ -12,7 +15,8 @@ pub mod user;
/// Stores&Retrieves Machines
pub mod machine;
#[derive(Clone)]
pub struct Databases {
pub access: access::internal::Internal,
pub machine: machine::internal::Internal,
pub access: Arc<access::AccessControl>,
pub machine: Arc<machine::MachineDB>,
}

View File

@ -3,12 +3,12 @@
use std::fmt;
use std::collections::HashSet;
use std::collections::HashMap;
use std::cmp::Ordering;
use std::path::{Path, PathBuf};
use std::fs;
use std::io::Write;
use std::sync::Arc;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::convert::{TryFrom, Into};
@ -32,6 +32,34 @@ pub mod internal;
use crate::db::user::User;
pub use internal::init;
pub struct AccessControl {
sources: HashMap<String, Box<dyn RoleDB>>,
}
impl AccessControl {
pub fn new() -> Self {
Self {
sources: HashMap::new()
}
}
/// Adds an access control source. If a source with the same name already existed it is
/// replaced.
pub fn add_source_unchecked(&mut self, name: String, source: Box<dyn RoleDB>) {
self.sources.insert(name, source);
}
pub async fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
for v in self.sources.values() {
if v.check(user, perm.as_ref())? {
return Ok(true);
}
}
return Ok(false);
}
}
pub trait RoleDB {
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>>;
@ -39,7 +67,7 @@ pub trait RoleDB {
///
/// Default implementation which adapter may overwrite with more efficient specialized
/// implementations.
fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
self.check_roles(&user.roles, perm)
}
@ -48,7 +76,7 @@ pub trait RoleDB {
///
/// A Default implementation exists which adapter may overwrite with more efficient specialized
/// implementations.
fn check_roles<P: AsRef<Permission>>(&self, roles: &[RoleIdentifier], perm: &P) -> Result<bool> {
fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> {
// Tally all roles. Makes dependent roles easier
let mut roleset = HashSet::new();
for roleID in roles {
@ -58,7 +86,7 @@ pub trait RoleDB {
// Iter all unique role->permissions we've found and early return on match.
for role in roleset.iter() {
for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(perm) {
if perm_rule.match_perm(&perm) {
return Ok(true);
}
}

View File

@ -150,9 +150,9 @@ impl Internal {
}
impl RoleDB for Internal {
fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
let txn = self.env.begin_ro_txn()?;
self._check(&txn, user, perm)
self._check(&txn, user, &perm)
}
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>> {

View File

@ -32,6 +32,8 @@ use futures_signals::signal::*;
use crate::registries::StatusSignal;
use crate::db::user::User;
use crate::machine::MachineDescription;
pub mod internal;
use internal::Internal;
@ -54,13 +56,13 @@ pub enum Status {
Reserved(UserIdentifier),
}
fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid {
pub fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid {
let uuid0 = uuid.get_uuid0() as u128;
let uuid1 = uuid.get_uuid1() as u128;
let num: u128 = (uuid1 << 64) + uuid0;
Uuid::from_u128(num)
}
fn api_from_uuid(uuid: Uuid, mut wr: crate::schema::api_capnp::u_u_i_d::Builder) {
pub fn api_from_uuid(uuid: Uuid, mut wr: crate::schema::api_capnp::u_u_i_d::Builder) {
let num = uuid.to_u128_le();
let uuid0 = num as u64;
let uuid1 = (num >> 64) as u64;
@ -75,6 +77,7 @@ pub struct MachineState {
}
pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Result<Internal> {
let mut machine_descriptions = MachineDescription::load_file(&config.machines)?;
let mut flags = lmdb::DatabaseFlags::empty();
flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true);
let machdb = env.create_db(Some("machines"), flags)?;
@ -82,3 +85,29 @@ pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Resu
Ok(Internal::new(log, env, machdb))
}
type MachMap = HashMap<MachineIdentifier, MachineDescription>;
pub struct MachineDB {
state_db: Internal,
def_db: MachMap,
}
impl MachineDB {
pub fn new(state_db: Internal, def_db: MachMap) -> Self {
Self { state_db, def_db }
}
pub fn exists(&self, id: MachineIdentifier) -> bool {
self.def_db.get(&id).is_some()
}
pub fn get_desc(&self, id: &MachineIdentifier) -> Option<&MachineDescription> {
self.def_db.get(&id)
}
pub fn get_state(&self, id: &MachineIdentifier) -> Option<MachineState> {
// TODO: Error Handling
self.state_db.get(id).unwrap_or(None)
}
}

View File

@ -15,6 +15,7 @@ use futures::future::Ready;
use futures::stream::Iter;
use super::{MachineIdentifier, MachineState};
use crate::machine::MachineDescription;
use crate::error::Result;
#[derive(Clone, Debug)]
@ -29,7 +30,7 @@ impl Internal {
Self { log, env, db }
}
pub fn get<T: Transaction>(&self, txn: &T, uuid: &Uuid)
pub fn get_with_txn<T: Transaction>(&self, txn: &T, uuid: &Uuid)
-> Result<Option<MachineState>>
{
match txn.get(self.db, uuid.as_bytes()) {
@ -42,7 +43,12 @@ impl Internal {
}
}
pub fn put(&self, txn: &mut RwTransaction, uuid: &Uuid, status: MachineState)
pub fn get(&self, id: &MachineIdentifier) -> Result<Option<MachineState>> {
let txn = self.env.begin_ro_txn()?;
self.get_with_txn(&txn, id)
}
pub fn put_with_txn(&self, txn: &mut RwTransaction, uuid: &Uuid, status: MachineState)
-> Result<()>
{
let bytes = flexbuffers::to_vec(status)?;
@ -51,68 +57,6 @@ impl Internal {
Ok(())
}
pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> {
path.push("machines");
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 machID_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 machID = match uuid::Uuid::from_str(machID_str) {
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 mach: MachineState = match toml::from_str(&s) {
Ok(r) => r,
Err(e) => {
warn!(self.log, "Failed to parse mach at path {}: {}, skipping!"
, path.display()
, e);
continue;
}
};
self.put(txn, &machID, mach)?;
debug!(self.log, "Loaded machine {}", machID);
} else {
warn!(self.log, "Path {} is not a file, skipping!", path.display());
}
}
Ok(())
}
pub fn dump_db<T: Transaction>(&self, txn: &T, mut path: PathBuf) -> Result<()> {
path.push("machines");
let mut mach_cursor = txn.open_ro_cursor(self.db)?;
for buf in mach_cursor.iter_start() {
let (kbuf, vbuf) = buf?;
let machID = uuid::Uuid::from_slice(kbuf).unwrap();
let mach: MachineState = flexbuffers::from_slice(vbuf)?;
let filename = format!("{}.yml", machID.to_hyphenated().to_string());
path.set_file_name(filename);
let mut fp = std::fs::File::create(&path)?;
let out = toml::to_vec(&mach)?;
fp.write_all(&out)?;
}
Ok(())
}
pub fn iter<T: Transaction>(&self, txn: &T) -> Result<impl Iterator<Item=MachineState>> {
let mut cursor = txn.open_ro_cursor(self.db)?;
Ok(cursor.iter_start().map(|buf| {

View File

@ -60,14 +60,14 @@ impl Machine {
/// Requests to use a machine. Returns `true` if successful.
///
/// This will update the internal state of the machine, notifying connected actors, if any.
pub fn request_use<P: access::RoleDB>
pub async fn request_use
( &mut self
, pp: &P
, access: access::AccessControl
, who: &User
) -> Result<bool>
{
// TODO: Check different levels
if pp.check(who, &self.desc.privs.write)? {
if access.check(who, &self.desc.privs.write).await? {
self.state.set(MachineState { state: Status::InUse(who.id.clone()) });
return Ok(true);
} else {
@ -97,7 +97,7 @@ pub struct MachineDescription {
}
impl MachineDescription {
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
pub fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
let content = fs::read(path)?;
Ok(toml::from_slice(&content[..])?)
}

View File

@ -112,7 +112,7 @@ fn main() -> Result<(), Error> {
// If no `config` option is given use a preset default.
let configpath = matches.value_of("config").unwrap_or("/etc/diflouroborane.toml");
let configpath = matches.value_of("config").unwrap_or("/etc/bffh/config.toml");
let config = config::read(&PathBuf::from_str(configpath).unwrap())?;
// Initialize the logging subsystem first to be able to better document the progress from now
@ -153,7 +153,7 @@ fn main() -> Result<(), Error> {
let mut txn = env.begin_rw_txn()?;
let path = path.to_path_buf();
pdb?.load_db(&mut txn, path.clone())?;
mdb?.load_db(&mut txn, path)?;
//mdb?.load_db(&mut txn, path)?;
txn.commit()?;
} else {
error!(log, "You must provide a directory path to load from");
@ -171,7 +171,7 @@ fn main() -> Result<(), Error> {
let txn = env.begin_ro_txn()?;
let path = path.to_path_buf();
pdb?.dump_db(&txn, path.clone())?;
mdb?.dump_db(&txn, path)?;
//mdb?.dump_db(&txn, path)?;
} else {
error!(log, "You must provide a directory path to dump into");
}
@ -210,8 +210,15 @@ fn main() -> Result<(), Error> {
// Error out if any of the subsystems failed to start.
let mdb = mdb?;
let defs = machine::MachineDescription::load_file(&config.machines)?;
let machdb = db::machine::MachineDB::new(mdb, defs);
let pdb = pdb?;
//let auth = auth?;
let mut ac = db::access::AccessControl::new();
ac.add_source_unchecked("Internal".to_string(), Box::new(pdb));
let db = db::Databases {
access: Arc::new(db::access::AccessControl::new()),
machine: Arc::new(machdb),
};
// Since the below closures will happen at a much later time we need to make sure all pointers
// are still valid. Thus, Arc.
@ -270,7 +277,7 @@ fn main() -> Result<(), Error> {
let elog = log.clone();
// We handle the error using map_err
let f = connection::handle_connection(log.clone(), socket)
let f = connection::handle_connection(log.clone(), socket, db.clone())
.map_err(move |e| {
error!(log, "Error occured during protocol handling: {}", e);
})