Let's try to get this as the next v0.3

This commit is contained in:
Nadja Reitzenstein 2022-03-10 20:52:34 +01:00
parent 487dc2270d
commit 4f36eedf6a
21 changed files with 315 additions and 251 deletions

1
Cargo.lock generated
View File

@ -734,6 +734,7 @@ dependencies = [
"clap",
"erased-serde",
"executor",
"futures-lite",
"futures-rustls",
"futures-signals",
"futures-test",

View File

@ -29,6 +29,7 @@ uuid = { version = "0.8.2", features = ["serde", "v4"] }
async-trait = "0.1.51"
pin-utils = "0.1.0"
futures-util = "0.3"
futures-lite = "1.12.0"
# Runtime
executor = { path = "runtime/executor" }

View File

@ -4,7 +4,7 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use futures_signals::signal::{MutableSignalRef, ReadOnlyMutable, Signal};
use futures_util::future::BoxFuture;
use crate::resource::state::State;
use crate::resources::state::State;
pub trait Actor {
fn apply(&mut self, state: State) -> BoxFuture<'static, ()>;

View File

@ -1,3 +1,11 @@
pub mod db;
pub struct AuthenticationHandler {
}
impl AuthenticationHandler {
}

View File

@ -0,0 +1,3 @@
pub mod permissions;
pub mod roles;

View File

@ -5,149 +5,6 @@ use std::fmt;
use std::cmp::Ordering;
use std::convert::{TryFrom, Into};
/// 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
/// role represents a real-world education or apprenticeship, which gives a person the education
/// necessary to use a machine safely.
/// Roles are assigned permissions which in most cases evaluate to granting a person the right to
/// use certain (potentially) dangerous machines.
/// Using this indirection makes administration easier in certain ways; instead of maintaining
/// permissions on users directly the user is given a role after having been educated on the safety
/// of a machine; if later on a similar enough machine is put to use the administrator can just add
/// the permission for that machine to an already existing role instead of manually having to
/// assign to all users.
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Role {
// If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A Role can have parents, inheriting all permissions
///
/// 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<RoleIdentifier>,
// If a role doesn't define permissions, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
permissions: Vec<PermRule>,
}
impl Role {
pub fn new(parents: Vec<RoleIdentifier>, permissions: Vec<PermRule>) -> Self {
Self { parents, permissions }
}
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parents:")?;
if self.parents.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.parents.iter() {
writeln!(f, " - {}", p)?;
}
}
write!(f, "permissions:")?;
if self.permissions.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.permissions.iter() {
writeln!(f, " - {}", p)?;
}
}
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<Self, Self::Err> {
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<String> for RoleIdentifier {
type Error = RoleFromStrError;
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
<RoleIdentifier as std::str::FromStr>::from_str(&s)
}
}
impl Into<String> 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'."),
}
}
}
fn is_sep_char(c: char) -> bool {
c == '.'

View File

@ -0,0 +1,146 @@
use std::fmt;
use crate::authorization::permissions::PermRule;
/// 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
/// role represents a real-world education or apprenticeship, which gives a person the education
/// necessary to use a machine safely.
/// Roles are assigned permissions which in most cases evaluate to granting a person the right to
/// use certain (potentially) dangerous machines.
/// Using this indirection makes administration easier in certain ways; instead of maintaining
/// permissions on users directly the user is given a role after having been educated on the safety
/// of a machine; if later on a similar enough machine is put to use the administrator can just add
/// the permission for that machine to an already existing role instead of manually having to
/// assign to all users.
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Role {
// If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A Role can have parents, inheriting all permissions
///
/// 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<RoleIdentifier>,
// If a role doesn't define permissions, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
permissions: Vec<PermRule>,
}
impl Role {
pub fn new(parents: Vec<RoleIdentifier>, permissions: Vec<PermRule>) -> Self {
Self { parents, permissions }
}
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parents:")?;
if self.parents.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.parents.iter() {
writeln!(f, " - {}", p)?;
}
}
write!(f, "permissions:")?;
if self.permissions.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.permissions.iter() {
writeln!(f, " - {}", p)?;
}
}
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<Self, Self::Err> {
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<String> for RoleIdentifier {
type Error = RoleFromStrError;
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
<RoleIdentifier as std::str::FromStr>::from_str(&s)
}
}
impl Into<String> 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'."),
}
}
}

View File

@ -1,12 +1,17 @@
use std::future::Future;
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::rpc_twoparty_capnp::Side;
use capnp_rpc::RpcSystem;
use capnp_rpc::twoparty::VatNetwork;
use futures_lite::StreamExt;
use futures_rustls::server::TlsStream;
use futures_util::{AsyncRead, AsyncWrite};
use futures_util::{AsyncRead, AsyncWrite, FutureExt};
use crate::error::Result;
use api::bootstrap::{
Client,
Server,
MechanismsParams,
MechanismsResults,
@ -14,10 +19,13 @@ use api::bootstrap::{
CreateSessionResults
};
mod authentication;
mod session;
mod authenticationsystem;
mod machine;
mod machinesystem;
mod permissionsystem;
mod user;
mod users;
mod resources;
mod session;
#[derive(Debug)]
pub struct APIHandler {
@ -28,8 +36,15 @@ impl APIHandler {
pub fn handle<IO: 'static + Unpin + AsyncRead + AsyncWrite>(&mut self, stream: TlsStream<IO>)
-> impl Future<Output = Result<()>>
{
unimplemented!();
futures_util::future::ready(Ok(()))
let (rx, tx) = futures_lite::io::split(stream);
let vat = VatNetwork::new(rx, tx, Side::Server, Default::default());
let bootstrap: Client = capnp_rpc::new_client(ApiSystem::new());
RpcSystem::new(Box::new(vat), Some(bootstrap.client))
.map(|res| match res {
Ok(()) => Ok(()),
Err(e) => Err(e.into())
})
}
}
@ -39,6 +54,11 @@ struct ApiSystem {
}
impl ApiSystem {
pub fn new() -> Self {
Self {}
}
}
impl Server for ApiSystem {
fn mechanisms(

View File

@ -1,5 +1,5 @@
use api::session::Builder;
use crate::capnp::resources::Resources;
use crate::capnp::machinesystem::Resources;
use crate::capnp::users::Users;
#[derive(Debug, Clone)]

View File

@ -1,4 +1,16 @@
use api::users::Server;
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::pry;
use api::users::Server as UsersServer;
use api::user::{
info,
manage,
admin,
passwd,
};
#[derive(Debug, Clone)]
pub struct Users {
@ -13,6 +25,47 @@ impl Users {
}
}
impl Server for Users {
impl UsersServer for Users {
}
struct User {
}
impl info::Server for User {
fn list_roles(
&mut self,
_params: info::ListRolesParams,
mut results: info::ListRolesResults
) -> Promise<(), Error>
{
unimplemented!()
}
}
impl manage::Server for User {
fn add_role(
&mut self,
params: manage::AddRoleParams,
_: manage::AddRoleResults
) -> Promise<(), Error> {
unimplemented!()
}
fn remove_role(
&mut self,
params: manage::RemoveRoleParams,
_: manage::RemoveRoleResults
) -> Promise<(), Error> {
unimplemented!()
}
}
impl admin::Server for User {
}
impl passwd::Server for User {
}

View File

@ -0,0 +1,30 @@
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
/// Unique ID Allocator
///
/// Helper to allocate numerical ids in shared contexts
pub struct IdAllocator {
next_id: AtomicU64,
}
impl IdAllocator {
pub fn new(next_id: u64) -> Self {
Self { next_id: AtomicU64::new(next_id) }
}
/// Return a new unused ID using an atomic fetch-add
pub fn get_next_id(&self) -> u64 {
self.next_id.fetch_add(1, Ordering::Release)
}
}
pub struct IdSegments {
segments: Vec<(u64, u64)>,
}
impl IdSegments {
pub fn new(segments: Vec<(u64, u64)>) -> Self {
Self { segments }
}
}

View File

@ -34,6 +34,8 @@ pub use hash::{
};
mod fix;
pub mod index;
pub use fix::LMDBorrow;
use lmdb::Error;
@ -43,13 +45,13 @@ use std::sync::Arc;
use std::path::Path;
use crate::users::{User, UserDB};
use std::collections::HashMap;
use crate::resource::state::{OwnedEntry, State, db::StateDB};
use crate::resources::state::{OwnedEntry, State, db::StateDB};
use std::iter::FromIterator;
use std::ops::Deref;
use crate::authentication::db::PassDB;
use crate::resource::db::ResourceDB;
use crate::resources::db::ResourceDB;
use crate::utils::oid::{ArchivedObjectIdentifier, ObjectIdentifier};
use crate::resource::state::value::SerializeValue;
use crate::resources::state::value::SerializeValue;
#[derive(Debug)]
pub enum DBError {

View File

@ -5,9 +5,9 @@ use async_channel as channel;
use async_oneshot as oneshot;
use futures_signals::signal::Signal;
use futures_util::future::BoxFuture;
use crate::resource::{Error, Update};
use crate::resource::claim::{ResourceID, UserID};
use crate::resource::state::State;
use crate::resources::{Error, Update};
use crate::resources::claim::{ResourceID, UserID};
use crate::resources::state::State;
pub enum UpdateError {
/// We're not connected to anything anymore. You can't do anything about this error and the
@ -65,7 +65,7 @@ impl UpdateSink {
struct Resource;
pub struct InitiatorDriver<S, I: Initiator> {
// TODO: make this a static reference to the resource because it's much easier and we don't
// TODO: make this a static reference to the resources because it's much easier and we don't
// need to replace resources at runtime at the moment.
resource_signal: S,
resource: Option<channel::Sender<Update>>,
@ -122,8 +122,8 @@ impl<S: Signal<Item=ResourceSink> + Unpin, I: Initiator + Unpin> Future for Init
// do while there is work to do
while {
// First things first:
// If we've send an update to the resource in question we have error channel set, so
// we poll that first to determine if the resource has acted on it yet.
// If we've send an update to the resources in question we have error channel set, so
// we poll that first to determine if the resources has acted on it yet.
if let Some(ref mut errchan) = self.error_channel {
match Pin::new(errchan).poll(cx) {
// In case there's an ongoing

View File

@ -1,4 +1,4 @@
#![forbid(unused_imports, unused_import_braces)]
#![warn(unused_imports, unused_import_braces)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(missing_crate_level_docs)]
@ -14,14 +14,11 @@ pub mod db;
/// Shared error type
pub mod error;
/// Policy decision engine
pub mod permissions;
pub mod users;
pub mod authentication;
pub mod authorization;
/// Resources
pub mod resource;
pub mod resources;
pub mod actors;

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use async_channel::Sender;
use lmdb::Environment;
use crate::resource::Update;
use crate::resources::Update;
#[derive(Clone, Debug)]
/// Database of currently valid claims, interests and notify, as far as applicable
@ -24,16 +24,16 @@ enum Level {
}
#[derive(Debug)]
/// A claim on a resource grants permission to update state
/// A claim on a resources grants permission to update state
///
/// This permission is not necessarily exclusive, depending on the resource in question.
/// This permission is not necessarily exclusive, depending on the resources in question.
pub struct Claim {
/// Sending end that can be used to send state updates to a resource.
/// Sending end that can be used to send state updates to a resources.
pub tx: Sender<Update>,
}
#[derive(Debug)]
/// An interest on a resource indicates that an user wants a resource to be in a specific state
/// An interest on a resources indicates that an user wants a resources to be in a specific state
pub struct Interest {
}

View File

@ -13,12 +13,12 @@ pub mod claim;
pub mod db;
/// A resource in BFFH has to contain several different parts;
/// A resources in BFFH has to contain several different parts;
/// - Currently set state
/// - Execution state of attached actors (⇒ BFFH's job)
/// - Output of interal logic of a resource
/// - Output of interal logic of a resources
/// ⇒ Resource logic gets read access to set state and write access to output state.
/// ⇒ state `update` happens via resource logic. This logic should do access control. If the update
/// ⇒ state `update` happens via resources logic. This logic should do access control. If the update
/// succeeds then BFFH stores those input parameters ("set" state) and results / output state.
/// Storing input parameters is relevant so that BFFH can know that an "update" is a no-op
/// without having to run the module code.
@ -42,7 +42,7 @@ pub mod db;
/// - Check authorization of updates i.e. is this user allowed to do that
#[async_trait]
pub trait ResourceModel: Debug {
/// Run whatever internal logic this resource has for the given State update, and return the
/// Run whatever internal logic this resources has for the given State update, and return the
/// new output state that this update produces.
async fn on_update(&mut self, input: &State) -> Result<State, Error>;
async fn shutdown(&mut self);
@ -59,7 +59,7 @@ impl ResourceModel for Passthrough {
async fn shutdown(&mut self) {}
}
/// Error type a resource implementation can produce
/// Error type a resources implementation can produce
#[derive(Debug)]
pub enum Error {
Internal(Box<dyn std::error::Error + Send>),
@ -99,9 +99,9 @@ impl ResourceDriver {
// the DB is not necessarily fatal, but it means that BFFH is now in an
// inconsistent state until a future update succeeds with writing to the DB.
// Not applying the new state isn't correct either since we don't know what the
// internal logic of the resource has done to make this happen.
// internal logic of the resources has done to make this happen.
// Another half right solution is to unwrap and recreate everything.
// "Best" solution would be to tell the resource to rollback their interal
// "Best" solution would be to tell the resources to rollback their interal
// changes on a fatal failure and then notify the Claimant, while simply trying
// again for temporary failures.
let _ = self.db.set(&state, &outstate);

View File

@ -24,7 +24,7 @@ use crate::db::{
LMDBorrow,
};
use crate::resource::state::State;
use crate::resources::state::State;
type StateAdapter = AllocAdapter<State>;
@ -37,7 +37,7 @@ pub struct StateDB {
input: DB<StateAdapter>,
output: DB<StateAdapter>,
// TODO: Index resource name/id/uuid -> u64
// TODO: Index resources name/id/uuid -> u64
}
impl StateDB {

View File

@ -23,7 +23,7 @@ use serde::ser::SerializeMap;
use value::{RegisteredImpl, SerializeValue};
use crate::utils::oid::ObjectIdentifier;
use crate::resource::state::value::{DynOwnedVal, DynVal, TypeOid, };
use crate::resources::state::value::{DynOwnedVal, DynVal, TypeOid, };
pub mod value;
pub mod db;
@ -32,10 +32,10 @@ pub mod db;
#[derive(Archive, Serialize, Deserialize)]
#[derive(Clone, PartialEq)]
#[archive_attr(derive(Debug))]
/// State object of a resource
/// State object of a resources
///
/// This object serves three functions:
/// 1. it is constructed by modification via Claims or via internal resource logic
/// 1. it is constructed by modification via Claims or via internal resources logic
/// 2. it is serializable and storable in the database
/// 3. it is sendable and forwarded to all Actors and Notifys
pub struct State {

View File

@ -14,18 +14,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use rkyv::{Archive, Serialize, Deserialize};
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::pry;
use api::user::{
info,
manage,
admin,
passwd,
};
use std::ops::Deref;
use std::sync::Arc;
use rkyv::{Archive, Serialize, Deserialize, Infallible};
mod db;
@ -33,7 +24,6 @@ pub use db::UserDB;
pub use crate::authentication::db::PassDB;
#[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)]
/// User API endpoint
pub struct User {
id: u128,
username: String,
@ -45,54 +35,3 @@ impl User {
User { id, username, roles }
}
}
impl info::Server for User {
fn list_roles(
&mut self,
_params: info::ListRolesParams,
mut results: info::ListRolesResults
) -> Promise<(), Error>
{
let results = results.get();
let mut roles = results.init_roles(self.roles.len() as u32);
for (i, role) in self.roles.iter().enumerate() {
let mut role_builder = roles.reborrow().get(i as u32);
role_builder.set_name(role);
}
Promise::ok(())
}
}
impl manage::Server for User {
fn add_role(
&mut self,
params: manage::AddRoleParams,
_: manage::AddRoleResults
) -> Promise<(), Error> {
let params = pry!(params.get());
let name = pry!(params.get_name()).to_string();
self.roles.push(name);
Promise::ok(())
}
fn remove_role(
&mut self,
params: manage::RemoveRoleParams,
_: manage::RemoveRoleResults
) -> Promise<(), Error> {
let params = pry!(params.get());
let name = pry!(params.get_name());
self.roles.retain(|role| role != name);
Promise::ok(())
}
}
impl admin::Server for User {
}
impl passwd::Server for User {
}

View File

@ -7,8 +7,9 @@ use serde::{Serialize, Deserialize, Deserializer, Serializer};
use std::fmt::Formatter;
use std::net::{SocketAddr, IpAddr, ToSocketAddrs};
use std::str::FromStr;
use diflouroborane::permissions::{PermRule, RoleIdentifier};
use serde::de::Error;
use diflouroborane::authorization::permissions::PermRule;
use diflouroborane::authorization::roles::RoleIdentifier;
type Result<T> = std::result::Result<T, serde_dhall::Error>;

View File

@ -0,0 +1,6 @@
/// View and Manage the current processes of this executor
pub struct Manager {
}