implement access permission system

This commit is contained in:
Dietmar Maurer 2020-04-10 11:24:24 +02:00
parent 89e71adfeb
commit 7ec6448db8
3 changed files with 186 additions and 0 deletions

View File

@ -15,6 +15,7 @@ pub mod const_regex;
pub mod error;
pub mod schema;
pub mod section_config;
pub mod permission;
#[doc(inline)]
pub use const_regex::ConstRegexPattern;

View File

@ -0,0 +1,161 @@
//! Declarative permission system
//!
//! A declarative way to define API access permissions.
use std::fmt;
use serde_json::Value;
/// Access permission
pub enum Permission {
/// Allow Superuser
Superuser,
/// Allow the whole World, no authentication required
World,
/// Allow any authenticated user
Anybody,
/// Allow access for the specified user
User(&'static str),
/// Allow access for the specified group of users
Group(&'static str),
/// Use a parameter value as userid to run sub-permission tests.
WithParam(&'static str, &'static Permission),
/// Check privilege/role on the specified path. The boolean
/// attribute specifies if you want to allow partial matches (u64
/// interpreted as bitmask).
Privilege(&'static [&'static str], u64, bool),
/// Allow access if all sub-permissions match
And(&'static [&'static Permission]),
/// Allow access if any sub-permissions match
Or(&'static [&'static Permission]),
}
impl fmt::Debug for Permission {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Permission::Superuser => {
f.write_str("Superuser")
}
Permission::World => {
f.write_str("World")
}
Permission::Anybody => {
f.write_str("Anybody")
}
Permission::User(ref userid) => {
f.write_fmt(format_args!("User({})", userid))
}
Permission::Group(ref group) => {
f.write_fmt(format_args!("Group({})", group))
}
Permission::WithParam(param_name, subtest) => {
f.write_fmt(format_args!("WithParam({}, {:?})", param_name, subtest))
}
Permission::Privilege(path, privs, partial) => {
f.write_fmt(format_args!("Privilege({:?}, {:0b}, {})", path, privs, partial))
}
Permission::And(list) => {
f.write_str("And(\n")?;
for subtest in list.iter() {
f.write_fmt(format_args!(" {:?}\n", subtest))?;
}
f.write_str(")\n")
}
Permission::Or(list) => {
f.write_str("Or(\n")?;
for subtest in list.iter() {
f.write_fmt(format_args!(" {:?}\n", subtest))?;
}
f.write_str(")\n")
}
}
}
}
/// Trait to query user information (used by check_api_permission)
pub trait UserInformation {
fn is_superuser(&self, userid: &str) -> bool;
fn is_group_member(&self, userid: &str, group: &str) -> bool;
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64;
}
/// Example implementation to check access permissions
///
/// This implementation supports URI variables in Privilege path
/// components, i.e. '{storage}'. We replace this with actual
/// parameter values before calling lookup_privs().
pub fn check_api_permission(
perm: &Permission,
userid: Option<&str>,
param: &Value,
info: &dyn UserInformation,
) -> bool {
if let Some(ref userid) = userid {
if info.is_superuser(userid) { return true; }
}
match perm {
Permission::World => return true,
Permission::Anybody => {
return userid.is_some();
}
Permission::Superuser => {
match userid {
None => return false,
Some(ref userid) => return info.is_superuser(userid),
}
}
Permission::User(expected_userid) => {
match userid {
None => return false,
Some(ref userid) => return userid == expected_userid,
}
}
Permission::Group(expected_group) => {
match userid {
None => return false,
Some(ref userid) => return info.is_group_member(userid, expected_group),
}
}
Permission::WithParam(param_name, subtest) => {
return check_api_permission(subtest, param[param_name].as_str(), param, info);
}
Permission::Privilege(path, expected_privs, partial) => {
// replace uri vars
let mut new_path: Vec<&str> = Vec::new();
for comp in path.iter() {
if comp.starts_with('{') && comp.ends_with('}') {
let param_name = unsafe { comp.get_unchecked(1..comp.len()-1) };
match param[param_name].as_str() {
None => return false,
Some(value) => {
new_path.push(value);
}
}
}
}
match userid {
None => return false,
Some(userid) => {
let privs = info.lookup_privs(userid, &new_path);
if *partial {
return (expected_privs & privs) != 0;
} else {
return (*expected_privs & privs) == *expected_privs;
}
}
}
}
Permission::And(list) => {
for subtest in list.iter() {
if !check_api_permission(subtest, userid, param, info) { return false; }
}
return true;
}
Permission::Or(list) => {
for subtest in list.iter() {
if check_api_permission(subtest, userid, param, info) { return true; }
}
return false;
}
}
}

View File

@ -11,6 +11,7 @@ use serde_json::Value;
use crate::api::schema::{self, ObjectSchema, Schema};
use crate::api::RpcEnvironment;
use super::permission::Permission;
/// A synchronous API handler gets a json Value as input and returns a json Value as output.
///
@ -383,6 +384,12 @@ fn dummy_handler_fn(
const DUMMY_HANDLER: ApiHandler = ApiHandler::Sync(&dummy_handler_fn);
/// Access permission with description
pub struct ApiAccessPermissions {
pub description: &'static str,
pub permission: &'static Permission,
}
/// This struct defines a synchronous API call which returns the result as json `Value`
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
pub struct ApiMethod {
@ -398,6 +405,8 @@ pub struct ApiMethod {
pub returns: &'static schema::Schema,
/// Handler function
pub handler: &'static ApiHandler,
/// Access Permissions
pub access: ApiAccessPermissions,
}
impl std::fmt::Debug for ApiMethod {
@ -406,6 +415,7 @@ impl std::fmt::Debug for ApiMethod {
write!(f, " parameters: {:?}", self.parameters)?;
write!(f, " returns: {:?}", self.returns)?;
write!(f, " handler: {:p}", &self.handler)?;
write!(f, " permissions: {:?}", &self.access.permission)?;
write!(f, "}}")
}
}
@ -418,6 +428,10 @@ impl ApiMethod {
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
access: ApiAccessPermissions {
description: "Default access permissions (superuser only).",
permission: &Permission::Superuser
},
}
}
@ -428,6 +442,10 @@ impl ApiMethod {
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
access: ApiAccessPermissions {
description: "Default access permissions (superuser only).",
permission: &Permission::Superuser
},
}
}
@ -448,4 +466,10 @@ impl ApiMethod {
self
}
pub const fn permissions(mut self, description: &'static str, permission: &'static Permission) -> Self {
self.access = ApiAccessPermissions { description, permission };
self
}
}