]> git.proxmox.com Git - proxmox-backup.git/commitdiff
start ACL api
authorDietmar Maurer <dietmar@proxmox.com>
Mon, 13 Apr 2020 09:09:44 +0000 (11:09 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 13 Apr 2020 09:09:44 +0000 (11:09 +0200)
src/api2/access.rs
src/api2/access/acl.rs [new file with mode: 0644]
src/api2/types.rs
src/bin/proxmox-backup-manager.rs
src/config/acl.rs

index 72698b4a6159df87f9d42fdf7b96f42363235a43..6d81934972748587338af5ae5ae73c5672aefb48 100644 (file)
@@ -14,6 +14,7 @@ use crate::api2::types::*;
 
 pub mod user;
 pub mod domain;
+pub mod acl;
 
 fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
 
@@ -130,6 +131,7 @@ fn change_password(
 
 #[sortable]
 const SUBDIRS: SubdirMap = &sorted!([
+    ("acl", &acl::ROUTER),
     (
         "password", &Router::new()
             .put(&API_METHOD_CHANGE_PASSWORD)
diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs
new file mode 100644 (file)
index 0000000..04be51f
--- /dev/null
@@ -0,0 +1,121 @@
+use failure::*;
+use serde_json::Value;
+use ::serde::{Deserialize, Serialize};
+
+use proxmox::api::{api, ApiMethod, Router, RpcEnvironment};
+use proxmox::api::schema::{Schema, StringSchema, BooleanSchema, ApiStringFormat};
+
+use crate::api2::types::*;
+use crate::config::acl;
+
+pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
+    "Allow to propagate (inherit) permissions.")
+    .default(true)
+    .schema();
+
+pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
+    "Access control path.")
+    .format(&ACL_PATH_FORMAT)
+    .min_length(1)
+    .max_length(128)
+    .schema();
+
+pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
+    "Type of 'ugid' property.")
+    .format(&ApiStringFormat::Enum(&["user", "group"]))
+    .schema();
+
+pub const ACL_ROLE_SCHEMA: Schema = StringSchema::new(
+    "Role.")
+    .format(&ApiStringFormat::Enum(&["Admin", "User", "Audit", "NoAccess"]))
+    .schema();
+
+#[api(
+    properties: {
+        propagate: {
+            schema: ACL_PROPAGATE_SCHEMA,
+        },
+       path: {
+            schema: ACL_PATH_SCHEMA,
+        },
+        ugid_type: {
+            schema: ACL_UGID_TYPE_SCHEMA,
+        },
+       ugid: {
+            type: String,
+            description: "User or Group ID.",
+        },
+       roleid: {
+            schema: ACL_ROLE_SCHEMA,
+        }
+    }
+)]
+#[derive(Serialize, Deserialize)]
+/// ACL list entry.
+pub struct AclListItem {
+    path: String,
+    ugid: String,
+    ugid_type: String,
+    propagate: bool,
+    roleid: String,
+}
+
+fn extract_acl_node_data(
+    node: &acl::AclTreeNode,
+    path: &str,
+    list: &mut Vec<AclListItem>,
+) {
+    for (user, roles) in &node.users {
+        for (role, propagate) in roles {
+            list.push(AclListItem {
+                path: if path.is_empty() { String::from("/") } else { path.to_string() },
+                propagate: *propagate,
+                ugid_type: String::from("user"),
+                ugid: user.to_string(),
+                roleid: role.to_string(),
+            });
+        }
+    }
+    for (group, roles) in &node.groups {
+        for (role, propagate) in roles {
+            list.push(AclListItem {
+                path: if path.is_empty() { String::from("/") } else { path.to_string() },
+                propagate: *propagate,
+                ugid_type: String::from("group"),
+                ugid: group.to_string(),
+                roleid: role.to_string(),
+            });
+        }
+    }
+    for (comp, child) in &node.children {
+        let new_path = format!("{}/{}", path, comp);
+        extract_acl_node_data(child, &new_path, list);
+    }
+}
+
+#[api(
+    returns: {
+        description: "ACL entry list.",
+        type: Array,
+        items: {
+            type: AclListItem,
+        }
+    }
+)]
+/// Read Access Control List (ACLs).
+pub fn read_acl(
+    _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<AclListItem>, Error> {
+
+    //let auth_user = rpcenv.get_user().unwrap();
+
+    let (tree, digest) = acl::config()?;
+
+    let mut list: Vec<AclListItem> = Vec::new();
+    extract_acl_node_data(&tree.root, "", &mut list);
+
+    Ok(list)
+}
+
+pub const ROUTER: Router = Router::new()
+    .get(&API_METHOD_READ_ACL);
index 2377092898a80795808d68f148df7f948c0cf36a..7132b4beba20fae28af9ae1a81a503c0155f7d40 100644 (file)
@@ -55,6 +55,8 @@ const_regex!{
     pub PROXMOX_USER_ID_REGEX = concat!(r"^",  USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
 
     pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
+
+    pub ACL_PATH_REGEX = concat!(r"^(?:\/|", r"(?:\/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
 }
 
 pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
@@ -90,6 +92,9 @@ pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
 pub const PASSWORD_FORMAT: ApiStringFormat =
     ApiStringFormat::Pattern(&PASSWORD_REGEX);
 
+pub const ACL_PATH_FORMAT: ApiStringFormat =
+    ApiStringFormat::Pattern(&ACL_PATH_REGEX);
+
 
 pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
     .format(&PASSWORD_FORMAT)
index c7c2f51a0cd8d64047e66db7766ae5970418ec17..0229d1ce6a8c2796e5443cad539bdcadb95a38ce 100644 (file)
@@ -171,6 +171,60 @@ fn user_commands() -> CommandLineInterface {
     cmd_def.into()
 }
 
+#[api(
+    input: {
+        properties: {
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        }
+    }
+)]
+/// Access Control list.
+fn list_acls(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+    let output_format = get_output_format(&param);
+
+    let info = &api2::access::acl::API_METHOD_READ_ACL;
+    let mut data = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
+
+    fn render_ugid(value: &Value, record: &Value) -> Result<String, Error> {
+        if value.is_null() { return Ok(String::new()); }
+        let ugid = value.as_str().unwrap();
+        let ugid_type = record["ugid_type"].as_str().unwrap();
+
+        if ugid_type == "user" {
+            Ok(ugid.to_string())
+        } else if ugid_type == "group" {
+            Ok(format!("@{}", ugid))
+        } else {
+            bail!("render_ugid: got unknown ugid_type");
+        }
+    }
+
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("ugid").renderer(render_ugid))
+        .column(ColumnConfig::new("path"))
+        .column(ColumnConfig::new("propagate"))
+        .column(ColumnConfig::new("roleid"));
+
+    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+
+    Ok(Value::Null)
+}
+
+fn acl_commands() -> CommandLineInterface {
+
+    let cmd_def = CliCommandMap::new()
+        .insert("list", CliCommand::new(&&API_METHOD_LIST_ACLS));
+
+    cmd_def.into()
+}
+
 fn datastore_commands() -> CommandLineInterface {
 
     let cmd_def = CliCommandMap::new()
@@ -539,6 +593,7 @@ async fn pull_datastore(
 fn main() {
 
     let cmd_def = CliCommandMap::new()
+        .insert("acl", acl_commands())
         .insert("datastore", datastore_commands())
         .insert("user", user_commands())
         .insert("remote", remote_commands())
index 0105e041bc49651d63742519b5b81e230cb1591e..27b9b51a46447c8b10e902766a3007777b780923 100644 (file)
@@ -64,13 +64,13 @@ fn split_acl_path(path: &str) -> Vec<&str> {
 }
 
 pub struct AclTree {
-    root: AclTreeNode,
+    pub root: AclTreeNode,
 }
 
-struct AclTreeNode {
-    users: HashMap<String, HashMap<String, bool>>,
-    groups: HashMap<String, HashMap<String, bool>>,
-    children: BTreeMap<String, AclTreeNode>,
+pub struct AclTreeNode {
+    pub users: HashMap<String, HashMap<String, bool>>,
+    pub groups: HashMap<String, HashMap<String, bool>>,
+    pub children: BTreeMap<String, AclTreeNode>,
 }
 
 impl AclTreeNode {