mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-05 23:32:50 +00:00
implement access permission system
This commit is contained in:
parent
89e71adfeb
commit
7ec6448db8
@ -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;
|
||||
|
161
proxmox/src/api/permission.rs
Normal file
161
proxmox/src/api/permission.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user