mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-25 16:17:56 +01:00
Pushes code around until example loading compiles
This commit is contained in:
parent
5d9c1d5a64
commit
3b63e654e5
14
examples/machines.toml
Normal file
14
examples/machines.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[e5408099-d3e5-440b-a92b-3aabf7683d6b]
|
||||||
|
name = "Somemachine"
|
||||||
|
disclose = "lab.some.disclose"
|
||||||
|
read = "lab.some.read"
|
||||||
|
write = "lab.some.write"
|
||||||
|
manage = "lab.some.admin"
|
||||||
|
|
||||||
|
[eaabebae-34d1-4a3a-912a-967b495d3d6e]
|
||||||
|
name = "Testmachine"
|
||||||
|
description = "An optional description"
|
||||||
|
disclose = "lab.test.read"
|
||||||
|
read = "lab.test.read"
|
||||||
|
write = "lab.test.write"
|
||||||
|
manage = "lab.test.admin"
|
20
examples/roles.toml
Normal file
20
examples/roles.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[testrole]
|
||||||
|
name = "Testrole"
|
||||||
|
permissions = [
|
||||||
|
"lab.test.*"
|
||||||
|
]
|
||||||
|
|
||||||
|
[somerole]
|
||||||
|
name = "Somerole"
|
||||||
|
parents = ["testparent%lmdb"]
|
||||||
|
permissions = [
|
||||||
|
"lab.some.admin"
|
||||||
|
]
|
||||||
|
|
||||||
|
[testparent]
|
||||||
|
name = "Testparent"
|
||||||
|
permissions = [
|
||||||
|
"lab.some.write",
|
||||||
|
"lab.some.read",
|
||||||
|
"lab.some.disclose",
|
||||||
|
]
|
@ -1,5 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use slog::Logger;
|
||||||
|
|
||||||
use capnp::capability::{Params, Results, Promise};
|
use capnp::capability::{Params, Results, Promise};
|
||||||
|
|
||||||
use crate::schema::connection_capnp;
|
use crate::schema::connection_capnp;
|
||||||
@ -17,6 +19,7 @@ pub struct Bootstrap {
|
|||||||
|
|
||||||
impl Bootstrap {
|
impl Bootstrap {
|
||||||
pub fn new(session: Arc<Session>) -> Self {
|
pub fn new(session: Arc<Session>) -> Self {
|
||||||
|
info!(session.log, "Created Bootstrap");
|
||||||
Self { session }
|
Self { session }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,7 +34,7 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
|
|||||||
// TODO: When should we allow multiple auth and how do me make sure that does not leak
|
// 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)?
|
// priviledges (e.g. due to previously issues caps)?
|
||||||
if self.session.user.is_none() {
|
if self.session.user.is_none() {
|
||||||
res.get().set_auth(capnp_rpc::new_client(auth::Auth::new()))
|
res.get().set_auth(capnp_rpc::new_client(auth::Auth::new(self.session.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise::ok(())
|
Promise::ok(())
|
||||||
|
@ -3,23 +3,29 @@
|
|||||||
//! Authorization is over in `access.rs`
|
//! Authorization is over in `access.rs`
|
||||||
//! Authentication using SASL
|
//! Authentication using SASL
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use slog::Logger;
|
use slog::Logger;
|
||||||
|
|
||||||
use rsasl::{
|
use rsasl::{
|
||||||
SASL,
|
SASL,
|
||||||
Property,
|
Property,
|
||||||
Session,
|
Session as SaslSession,
|
||||||
ReturnCode,
|
ReturnCode,
|
||||||
Callback,
|
Callback,
|
||||||
SaslCtx,
|
SaslCtx,
|
||||||
Step,
|
Step,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use capnp::capability::{Params, Results, Promise};
|
use capnp::capability::{Params, Results, Promise};
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::config::Settings;
|
use crate::config::Settings;
|
||||||
|
|
||||||
|
use crate::api::Session;
|
||||||
|
|
||||||
pub use crate::schema::auth_capnp;
|
pub use crate::schema::auth_capnp;
|
||||||
|
|
||||||
pub struct AppData;
|
pub struct AppData;
|
||||||
@ -27,7 +33,7 @@ pub struct SessionData;
|
|||||||
|
|
||||||
struct CB;
|
struct CB;
|
||||||
impl Callback<AppData, SessionData> for CB {
|
impl Callback<AppData, SessionData> for CB {
|
||||||
fn callback(sasl: SaslCtx<AppData, SessionData>, session: Session<SessionData>, prop: Property) -> libc::c_int {
|
fn callback(sasl: SaslCtx<AppData, SessionData>, session: SaslSession<SessionData>, prop: Property) -> libc::c_int {
|
||||||
let ret = match prop {
|
let ret = match prop {
|
||||||
Property::GSASL_VALIDATE_SIMPLE => {
|
Property::GSASL_VALIDATE_SIMPLE => {
|
||||||
let authid = session.get_property(Property::GSASL_AUTHID).unwrap().to_string_lossy();
|
let authid = session.get_property(Property::GSASL_AUTHID).unwrap().to_string_lossy();
|
||||||
@ -50,10 +56,11 @@ impl Callback<AppData, SessionData> for CB {
|
|||||||
|
|
||||||
pub struct Auth {
|
pub struct Auth {
|
||||||
pub ctx: SASL<AppData, SessionData>,
|
pub ctx: SASL<AppData, SessionData>,
|
||||||
|
session: Arc<Session>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Auth {
|
impl Auth {
|
||||||
pub fn new() -> Self {
|
pub fn new(session: Arc<Session>) -> Self {
|
||||||
let mut ctx = SASL::new().unwrap();
|
let mut ctx = SASL::new().unwrap();
|
||||||
|
|
||||||
let mut appdata = Box::new(AppData);
|
let mut appdata = Box::new(AppData);
|
||||||
@ -62,7 +69,9 @@ impl Auth {
|
|||||||
|
|
||||||
ctx.install_callback::<CB>();
|
ctx.install_callback::<CB>();
|
||||||
|
|
||||||
Self { ctx }
|
info!(session.log, "Auth created");
|
||||||
|
|
||||||
|
Self { ctx, session }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,20 +165,19 @@ impl auth_capnp::authentication::Server for Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(log: Logger, config: Settings) -> Result<Auth> {
|
|
||||||
Ok(Auth::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the newtype pattern here to make the type system work for us; even though AuthCId is for all
|
// Use the newtype pattern here to make the type system work for us; even though AuthCId is for all
|
||||||
// intents and purposes just a String the compiler will still complain if you return or more
|
// intents and purposes just a String the compiler will still complain if you return or more
|
||||||
// importantly pass a String intead of a AuthCId. This prevents bugs where you get an object from
|
// importantly pass a String intead of a AuthCId. This prevents bugs where you get an object from
|
||||||
// somewhere and pass it somewhere else and in between don't check if it's the right type and
|
// somewhere and pass it somewhere else and in between don't check if it's the right type and
|
||||||
// accidentally pass the authzid where the authcid should have gone.
|
// accidentally pass the authzid where the authcid should have gone.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
/// Authentication Identity
|
/// Authentication Identity
|
||||||
///
|
///
|
||||||
/// Under the hood a string because the form depends heavily on the method
|
/// Under the hood a string because the form depends heavily on the method
|
||||||
struct AuthCId(String);
|
struct AuthCId(String);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
/// Authorization Identity
|
/// Authorization Identity
|
||||||
///
|
///
|
||||||
/// This identity is internal to FabAccess and completely independent from the authentication
|
/// This identity is internal to FabAccess and completely independent from the authentication
|
||||||
@ -191,6 +199,7 @@ struct AuthZId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// What is a man?! A miserable little pile of secrets!
|
// What is a man?! A miserable little pile of secrets!
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
/// Authentication/Authorization user object.
|
/// Authentication/Authorization user object.
|
||||||
///
|
///
|
||||||
/// This struct contains the user as is passed to the actual authentication/authorization
|
/// This struct contains the user as is passed to the actual authentication/authorization
|
||||||
@ -218,7 +227,7 @@ pub struct User {
|
|||||||
/// Contains the authentication method used
|
/// Contains the authentication method used
|
||||||
///
|
///
|
||||||
/// For the most part this is the SASL method
|
/// For the most part this is the SASL method
|
||||||
authMethod: String,
|
auth_method: String,
|
||||||
|
|
||||||
/// Method-specific key-value pairs
|
/// Method-specific key-value pairs
|
||||||
///
|
///
|
||||||
@ -236,6 +245,7 @@ pub struct User {
|
|||||||
// b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate
|
// b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate
|
||||||
// has been configured for that user, if a GSSAPI user maps to a given user,
|
// has been configured for that user, if a GSSAPI user maps to a given user,
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
/// Authentication ID is bad/unknown/..
|
/// Authentication ID is bad/unknown/..
|
||||||
BadAuthcid,
|
BadAuthcid,
|
||||||
@ -247,7 +257,3 @@ pub enum AuthError {
|
|||||||
NotAllowedAuthzid,
|
NotAllowedAuthzid,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grant_auth(user: User) -> std::result::Result<(), AuthError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use slog::Logger;
|
||||||
|
|
||||||
use capnp::capability::Promise;
|
use capnp::capability::Promise;
|
||||||
use capnp::Error;
|
use capnp::Error;
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ pub struct Machines {
|
|||||||
|
|
||||||
impl Machines {
|
impl Machines {
|
||||||
pub fn new(session: Arc<Session>) -> Self {
|
pub fn new(session: Arc<Session>) -> Self {
|
||||||
|
info!(session.log, "Machines created");
|
||||||
Self { session }
|
Self { session }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use crate::schema::connection_capnp;
|
|||||||
/// Connection context
|
/// Connection context
|
||||||
// TODO this should track over several connections
|
// TODO this should track over several connections
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
log: Logger,
|
pub log: Logger,
|
||||||
pub user: Option<auth::User>,
|
pub user: Option<auth::User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ 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) -> Result<()> {
|
||||||
//handshake(&log, &mut stream).await?;
|
//handshake(&log, &mut stream).await?;
|
||||||
|
|
||||||
|
info!(log, "New connection from on {:?}", stream);
|
||||||
let session = Arc::new(Session::new(log));
|
let session = Arc::new(Session::new(log));
|
||||||
let boots = Bootstrap::new(session);
|
let boots = Bootstrap::new(session);
|
||||||
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);
|
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);
|
||||||
|
156
src/db/access.rs
156
src/db/access.rs
@ -4,16 +4,22 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use std::convert::{TryFrom, Into};
|
||||||
|
|
||||||
use flexbuffers;
|
use flexbuffers;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{
|
||||||
|
Serialize,
|
||||||
|
Serializer,
|
||||||
|
|
||||||
|
Deserialize,
|
||||||
|
Deserializer,
|
||||||
|
};
|
||||||
|
|
||||||
use slog::Logger;
|
use slog::Logger;
|
||||||
use lmdb::{Environment, Transaction, RwTransaction, Cursor};
|
use lmdb::{Environment, Transaction, RwTransaction, Cursor};
|
||||||
@ -98,15 +104,31 @@ pub trait RoleDB {
|
|||||||
pub struct Role {
|
pub struct Role {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
|
// 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
|
/// A Role can have parents, inheriting all permissions
|
||||||
///
|
///
|
||||||
/// This makes situations where different levels of access are required easier: Each higher
|
/// 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
|
/// 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
|
/// you are allowed to manage a machine you are then also allowed to use it and so on
|
||||||
parents: Vec<RoleIdentifier>,
|
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>,
|
permissions: Vec<PermRule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Role {
|
||||||
|
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<RoleIdentifier, Role>> {
|
||||||
|
let content = fs::read(path)?;
|
||||||
|
let file_roles: HashMap<String, Role> = toml::from_slice(&content[..])?;
|
||||||
|
|
||||||
|
Ok(HashMap::from_iter(file_roles.into_iter().map(|(key, value)| {
|
||||||
|
(RoleIdentifier::local_from_str("lmdb".to_string(), key), value)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type SourceID = String;
|
type SourceID = String;
|
||||||
|
|
||||||
fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
|
fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
|
||||||
@ -116,6 +138,7 @@ fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "String")]
|
||||||
/// Universal (relative) id of a role
|
/// Universal (relative) id of a role
|
||||||
pub enum RoleIdentifier {
|
pub enum RoleIdentifier {
|
||||||
/// The role comes from this instance
|
/// The role comes from this instance
|
||||||
@ -135,6 +158,16 @@ pub enum RoleIdentifier {
|
|||||||
location: String,
|
location: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RoleIdentifier {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source),
|
||||||
|
RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for RoleIdentifier {
|
impl std::str::FromStr for RoleIdentifier {
|
||||||
type Err = RoleFromStrError;
|
type Err = RoleFromStrError;
|
||||||
|
|
||||||
@ -148,21 +181,42 @@ impl std::str::FromStr for RoleIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Display for RoleIdentifier {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
impl TryFrom<String> for RoleIdentifier {
|
||||||
match self {
|
type Error = RoleFromStrError;
|
||||||
RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source),
|
|
||||||
RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location),
|
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
|
||||||
|
if let Some((name, location)) = split_once(&s, '@') {
|
||||||
|
Ok(RoleIdentifier::Remote { name: name.to_string(), location: location.to_string() })
|
||||||
|
} else if let Some((name, source)) = split_once(&s, '%') {
|
||||||
|
Ok(RoleIdentifier::Local { name: name.to_string(), source: source.to_string() })
|
||||||
|
} else {
|
||||||
|
Err(RoleFromStrError::Invalid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RoleIdentifier {
|
||||||
|
pub fn local_from_str(source: String, name: String) -> Self {
|
||||||
|
RoleIdentifier::Local { name, source }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum RoleFromStrError {
|
pub enum RoleFromStrError {
|
||||||
/// No '@' or '%' found. That's strange, huh?
|
/// No '@' or '%' found. That's strange, huh?
|
||||||
Invalid
|
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'."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
/// An identifier for a permission
|
/// An identifier for a permission
|
||||||
// XXX: Does remote permissions ever make sense?
|
// XXX: Does remote permissions ever make sense?
|
||||||
@ -201,6 +255,7 @@ pub struct PrivilegesBuf {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
#[serde(transparent)]
|
||||||
/// An owned permission string
|
/// An owned permission string
|
||||||
///
|
///
|
||||||
/// This is under the hood just a fancy std::String.
|
/// This is under the hood just a fancy std::String.
|
||||||
@ -242,6 +297,10 @@ impl PermissionBuf {
|
|||||||
pub fn from_string(inner: String) -> Self {
|
pub fn from_string(inner: String) -> Self {
|
||||||
Self { inner }
|
Self { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_string(self) -> String {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<str> for PermissionBuf {
|
impl AsRef<str> for PermissionBuf {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -317,8 +376,16 @@ impl PartialOrd for Permission {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<Permission> for Permission {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &Permission {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "String")]
|
||||||
|
#[serde(into = "String")]
|
||||||
pub enum PermRule {
|
pub enum PermRule {
|
||||||
/// The permission is precise,
|
/// The permission is precise,
|
||||||
///
|
///
|
||||||
@ -364,6 +431,46 @@ impl fmt::Display for PermRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for PermRule {
|
||||||
|
fn into(self) -> String {
|
||||||
|
match self {
|
||||||
|
PermRule::Base(perm) => perm.into_string(),
|
||||||
|
PermRule::Children(mut perm) => {
|
||||||
|
perm.push(Permission::new("+"));
|
||||||
|
perm.into_string()
|
||||||
|
},
|
||||||
|
PermRule::Subtree(mut perm) => {
|
||||||
|
perm.push(Permission::new("+"));
|
||||||
|
perm.into_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for PermRule {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(mut input: String) -> std::result::Result<Self, Self::Error> {
|
||||||
|
// Check out specifically the last two chars
|
||||||
|
let len = input.len();
|
||||||
|
if len <= 2 {
|
||||||
|
Err("Input string for PermRule is too short")
|
||||||
|
} else {
|
||||||
|
match &input[len-2..len] {
|
||||||
|
".+" => {
|
||||||
|
input.truncate(len-2);
|
||||||
|
Ok(PermRule::Children(PermissionBuf::from_string(input)))
|
||||||
|
},
|
||||||
|
".*" => {
|
||||||
|
input.truncate(len-2);
|
||||||
|
Ok(PermRule::Subtree(PermissionBuf::from_string(input)))
|
||||||
|
},
|
||||||
|
_ => Ok(PermRule::Base(PermissionBuf::from_string(input))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -390,4 +497,35 @@ mod tests {
|
|||||||
|
|
||||||
assert!(rule.match_perm(&perm));
|
assert!(rule.match_perm(&perm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_examples_roles_test() {
|
||||||
|
let roles = Role::load_file("examples/roles.toml")
|
||||||
|
.expect("Couldn't load the example role defs. Does `examples/roles.toml` exist?");
|
||||||
|
|
||||||
|
|
||||||
|
assert!(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rules_from_string_test() {
|
||||||
|
assert_eq!(
|
||||||
|
PermRule::Base(PermissionBuf::from_string("bffh.perm".to_string())),
|
||||||
|
PermRule::try_from("bffh.perm".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
PermRule::Children(PermissionBuf::from_string("bffh.perm".to_string())),
|
||||||
|
PermRule::try_from("bffh.perm.+".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
PermRule::Subtree(PermissionBuf::from_string("bffh.perm".to_string())),
|
||||||
|
PermRule::try_from("bffh.perm.*".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rules_from_string_edgecases_test() {
|
||||||
|
assert!(PermRule::try_from("*".to_string()).is_err());
|
||||||
|
assert!(PermRule::try_from("+".to_string()).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use futures_signals::signal::Signal;
|
use futures_signals::signal::Signal;
|
||||||
@ -19,6 +23,9 @@ use crate::db::machine::{MachineIdentifier, Status, MachineState};
|
|||||||
/// machine, checking that the user who wants the machine (de)activated has the required
|
/// machine, checking that the user who wants the machine (de)activated has the required
|
||||||
/// permissions.
|
/// permissions.
|
||||||
pub struct Machine {
|
pub struct Machine {
|
||||||
|
/// Globally unique machine readable identifier
|
||||||
|
id: MachineIdentifier,
|
||||||
|
|
||||||
/// Descriptor of the machine
|
/// Descriptor of the machine
|
||||||
desc: MachineDescription,
|
desc: MachineDescription,
|
||||||
|
|
||||||
@ -30,8 +37,9 @@ pub struct Machine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Machine {
|
impl Machine {
|
||||||
pub fn new(desc: MachineDescription, perm: access::PermIdentifier) -> Machine {
|
pub fn new(id: MachineIdentifier, desc: MachineDescription, perm: access::PermIdentifier) -> Machine {
|
||||||
Machine {
|
Machine {
|
||||||
|
id: id,
|
||||||
desc: desc,
|
desc: desc,
|
||||||
state: Mutable::new(MachineState { state: Status::Free}),
|
state: Mutable::new(MachineState { state: Status::Free}),
|
||||||
}
|
}
|
||||||
@ -72,19 +80,70 @@ impl Machine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
/// A description of a machine
|
/// A description of a machine
|
||||||
///
|
///
|
||||||
/// This is the struct that a machine is serialized to/from.
|
/// This is the struct that a machine is serialized to/from.
|
||||||
/// Combining this with the actual state of the system will return a machine
|
/// Combining this with the actual state of the system will return a machine
|
||||||
pub struct MachineDescription {
|
pub struct MachineDescription {
|
||||||
/// The main machine identifier. This must be unique.
|
|
||||||
id: MachineIdentifier,
|
|
||||||
/// The name of the machine. Doesn't need to be unique but is what humans will be presented.
|
/// The name of the machine. Doesn't need to be unique but is what humans will be presented.
|
||||||
name: String,
|
name: String,
|
||||||
/// An optional description of the Machine.
|
/// An optional description of the Machine.
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
|
||||||
/// The permission required
|
/// The permission required
|
||||||
|
#[serde(flatten)]
|
||||||
privs: access::PrivilegesBuf,
|
privs: access::PrivilegesBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MachineDescription {
|
||||||
|
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
|
||||||
|
let content = fs::read(path)?;
|
||||||
|
Ok(toml::from_slice(&content[..])?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
use crate::db::access::{PermissionBuf, PrivilegesBuf};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_examples_descriptions_test() {
|
||||||
|
let machines = MachineDescription::load_file("examples/machines.toml")
|
||||||
|
.expect("Couldn't load the example machine defs. Does `examples/machines.toml` exist?");
|
||||||
|
|
||||||
|
let expected: HashMap<MachineIdentifier, MachineDescription>
|
||||||
|
= HashMap::from_iter(vec![
|
||||||
|
(Uuid::parse_str("e5408099-d3e5-440b-a92b-3aabf7683d6b").unwrap(),
|
||||||
|
MachineDescription {
|
||||||
|
name: "Somemachine".to_string(),
|
||||||
|
description: None,
|
||||||
|
privs: PrivilegesBuf {
|
||||||
|
disclose: PermissionBuf::from_string("lab.some.disclose".to_string()),
|
||||||
|
read: PermissionBuf::from_string("lab.some.read".to_string()),
|
||||||
|
write: PermissionBuf::from_string("lab.some.write".to_string()),
|
||||||
|
manage: PermissionBuf::from_string("lab.some.admin".to_string()),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
(Uuid::parse_str("eaabebae-34d1-4a3a-912a-967b495d3d6e").unwrap(),
|
||||||
|
MachineDescription {
|
||||||
|
name: "Testmachine".to_string(),
|
||||||
|
description: Some("An optional description".to_string()),
|
||||||
|
privs: PrivilegesBuf {
|
||||||
|
disclose: PermissionBuf::from_string("lab.test.read".to_string()),
|
||||||
|
read: PermissionBuf::from_string("lab.test.read".to_string()),
|
||||||
|
write: PermissionBuf::from_string("lab.test.write".to_string()),
|
||||||
|
manage: PermissionBuf::from_string("lab.test.admin".to_string()),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
].into_iter());
|
||||||
|
|
||||||
|
for u in ["e5408099-d3e5-440b-a92b-3aabf7683d6b", "eaabebae-34d1-4a3a-912a-967b495d3d6e"].iter() {
|
||||||
|
let uuid = Uuid::parse_str(u).unwrap();
|
||||||
|
assert_eq!(machines[&uuid], expected[&uuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -143,7 +143,6 @@ fn main() -> Result<(), Error> {
|
|||||||
let env = Arc::new(env);
|
let env = Arc::new(env);
|
||||||
let mdb = db::machine::init(log.new(o!("system" => "machines")), &config, env.clone());
|
let mdb = db::machine::init(log.new(o!("system" => "machines")), &config, env.clone());
|
||||||
let pdb = db::access::init(log.new(o!("system" => "permissions")), &config, env.clone());
|
let pdb = db::access::init(log.new(o!("system" => "permissions")), &config, env.clone());
|
||||||
let authentication_f = api::auth::init(log.new(o!("system" => "authentication")), config.clone());
|
|
||||||
|
|
||||||
// If --load or --dump is given we can stop at this point and load/dump the database and then
|
// If --load or --dump is given we can stop at this point and load/dump the database and then
|
||||||
// exit.
|
// exit.
|
||||||
|
Loading…
Reference in New Issue
Block a user