]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/access/acl.rs
verify: factor out common parameters
[proxmox-backup.git] / src / api2 / access / acl.rs
CommitLineData
bf78f708
DM
1//! Manage Access Control Lists
2
f7d4e4b5 3use anyhow::{bail, Error};
ed3e60ae 4
d28ddb8e 5use proxmox::api::{api, Router, RpcEnvironment, Permission};
98c259b4 6use proxmox::tools::fs::open_file_locked;
ed3e60ae
DM
7
8use crate::api2::types::*;
9use crate::config::acl;
74c08a57 10use crate::config::acl::{Role, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY};
b2da7fbd 11use crate::config::cached_user_info::CachedUserInfo;
ed3e60ae 12
ed3e60ae
DM
13fn extract_acl_node_data(
14 node: &acl::AclTreeNode,
15 path: &str,
16 list: &mut Vec<AclListItem>,
11b6391c 17 exact: bool,
b2da7fbd 18 token_user: &Option<Authid>,
ed3e60ae 19) {
b2da7fbd
FG
20 // tokens can't have tokens, so we can early return
21 if let Some(token_user) = token_user {
22 if token_user.is_token() {
23 return;
24 }
25 }
26
ed3e60ae 27 for (user, roles) in &node.users {
b2da7fbd
FG
28 if let Some(token_user) = token_user {
29 if !user.is_token()
30 || user.user() != token_user.user() {
31 continue;
32 }
33 }
34
ed3e60ae
DM
35 for (role, propagate) in roles {
36 list.push(AclListItem {
37 path: if path.is_empty() { String::from("/") } else { path.to_string() },
38 propagate: *propagate,
39 ugid_type: String::from("user"),
40 ugid: user.to_string(),
41 roleid: role.to_string(),
42 });
43 }
44 }
45 for (group, roles) in &node.groups {
3984a5fd 46 if token_user.is_some() {
b2da7fbd
FG
47 continue;
48 }
49
ed3e60ae
DM
50 for (role, propagate) in roles {
51 list.push(AclListItem {
52 path: if path.is_empty() { String::from("/") } else { path.to_string() },
53 propagate: *propagate,
54 ugid_type: String::from("group"),
55 ugid: group.to_string(),
56 roleid: role.to_string(),
57 });
58 }
59 }
11b6391c
DC
60 if exact {
61 return;
62 }
ed3e60ae
DM
63 for (comp, child) in &node.children {
64 let new_path = format!("{}/{}", path, comp);
b2da7fbd 65 extract_acl_node_data(child, &new_path, list, exact, token_user);
ed3e60ae
DM
66 }
67}
68
69#[api(
2882c881
DC
70 input: {
71 properties: {
72 path: {
73 schema: ACL_PATH_SCHEMA,
74 optional: true,
75 },
76 exact: {
77 description: "If set, returns only ACL for the exact path.",
78 type: bool,
79 optional: true,
80 default: false,
81 },
82 },
83 },
ed3e60ae
DM
84 returns: {
85 description: "ACL entry list.",
86 type: Array,
87 items: {
88 type: AclListItem,
89 }
d28ddb8e
DM
90 },
91 access: {
b2da7fbd
FG
92 permission: &Permission::Anybody,
93 description: "Returns all ACLs if user has Sys.Audit on '/access/acl', or just the ACLs containing the user's API tokens.",
d28ddb8e 94 },
ed3e60ae
DM
95)]
96/// Read Access Control List (ACLs).
97pub fn read_acl(
2882c881
DC
98 path: Option<String>,
99 exact: bool,
100 mut rpcenv: &mut dyn RpcEnvironment,
ed3e60ae 101) -> Result<Vec<AclListItem>, Error> {
b2da7fbd 102 let auth_id = rpcenv.get_auth_id().unwrap().parse()?;
ed3e60ae 103
b2da7fbd
FG
104 let user_info = CachedUserInfo::new()?;
105
106 let top_level_privs = user_info.lookup_privs(&auth_id, &["access", "acl"]);
107 let auth_id_filter = if (top_level_privs & PRIV_SYS_AUDIT) == 0 {
108 Some(auth_id)
109 } else {
110 None
111 };
ed3e60ae 112
2882c881 113 let (mut tree, digest) = acl::config()?;
ed3e60ae
DM
114
115 let mut list: Vec<AclListItem> = Vec::new();
2882c881
DC
116 if let Some(path) = &path {
117 if let Some(node) = &tree.find_node(path) {
b2da7fbd 118 extract_acl_node_data(&node, path, &mut list, exact, &auth_id_filter);
2882c881
DC
119 }
120 } else {
b2da7fbd 121 extract_acl_node_data(&tree.root, "", &mut list, exact, &auth_id_filter);
2882c881
DC
122 }
123
124 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
ed3e60ae
DM
125
126 Ok(list)
127}
128
9765092e 129#[api(
12e38953 130 protected: true,
9765092e
DM
131 input: {
132 properties: {
133 path: {
134 schema: ACL_PATH_SCHEMA,
135 },
136 role: {
bc0d0388 137 type: Role,
9765092e
DM
138 },
139 propagate: {
140 optional: true,
141 schema: ACL_PROPAGATE_SCHEMA,
142 },
8b600f99 143 "auth-id": {
9765092e 144 optional: true,
e6dc35ac 145 type: Authid,
9765092e
DM
146 },
147 group: {
148 optional: true,
149 schema: PROXMOX_GROUP_ID_SCHEMA,
150 },
d83175dd 151 delete: {
9765092e
DM
152 optional: true,
153 description: "Remove permissions (instead of adding it).",
154 type: bool,
155 },
156 digest: {
157 optional: true,
158 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
159 },
160 },
161 },
d28ddb8e 162 access: {
b2da7fbd
FG
163 permission: &Permission::Anybody,
164 description: "Requires Permissions.Modify on '/access/acl', limited to updating ACLs of the user's API tokens otherwise."
d28ddb8e 165 },
9765092e
DM
166)]
167/// Update Access Control List (ACLs).
168pub fn update_acl(
169 path: String,
170 role: String,
171 propagate: Option<bool>,
e6dc35ac 172 auth_id: Option<Authid>,
9765092e
DM
173 group: Option<String>,
174 delete: Option<bool>,
175 digest: Option<String>,
b2da7fbd 176 rpcenv: &mut dyn RpcEnvironment,
9765092e 177) -> Result<(), Error> {
b2da7fbd
FG
178 let current_auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
179
180 let user_info = CachedUserInfo::new()?;
181
182 let top_level_privs = user_info.lookup_privs(&current_auth_id, &["access", "acl"]);
183 if top_level_privs & PRIV_PERMISSIONS_MODIFY == 0 {
3984a5fd 184 if group.is_some() {
b2da7fbd
FG
185 bail!("Unprivileged users are not allowed to create group ACL item.");
186 }
187
188 match &auth_id {
189 Some(auth_id) => {
190 if current_auth_id.is_token() {
191 bail!("Unprivileged API tokens can't set ACL items.");
192 } else if !auth_id.is_token() {
193 bail!("Unprivileged users can only set ACL items for API tokens.");
194 } else if auth_id.user() != current_auth_id.user() {
195 bail!("Unprivileged users can only set ACL items for their own API tokens.");
196 }
197 },
198 None => { bail!("Unprivileged user needs to provide auth_id to update ACL item."); },
199 };
200 }
9765092e 201
b56c111e 202 let _lock = open_file_locked(acl::ACL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
9765092e
DM
203
204 let (mut tree, expected_digest) = acl::config()?;
205
206 if let Some(ref digest) = digest {
207 let digest = proxmox::tools::hex_to_digest(digest)?;
208 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
209 }
210
9765092e
DM
211 let propagate = propagate.unwrap_or(true);
212
213 let delete = delete.unwrap_or(false);
214
9f4e47dd 215 if let Some(ref _group) = group {
d83175dd 216 bail!("parameter 'group' - groups are currently not supported.");
e6dc35ac 217 } else if let Some(ref auth_id) = auth_id {
d83175dd 218 if !delete { // Note: we allow to delete non-existent users
109d7817 219 let user_cfg = crate::config::user::cached_config()?;
e6dc35ac
FG
220 if user_cfg.sections.get(&auth_id.to_string()).is_none() {
221 bail!(format!("no such {}.",
222 if auth_id.is_token() { "API token" } else { "user" }));
d83175dd
DM
223 }
224 }
225 } else {
226 bail!("missing 'userid' or 'group' parameter.");
227 }
228
9f4e47dd 229 if !delete { // Note: we allow to delete entries with invalid path
74c08a57 230 acl::check_acl_path(&path)?;
9f4e47dd
DM
231 }
232
e6dc35ac 233 if let Some(auth_id) = auth_id {
9765092e 234 if delete {
e6dc35ac 235 tree.delete_user_role(&path, &auth_id, &role);
9765092e 236 } else {
e6dc35ac 237 tree.insert_user_role(&path, &auth_id, &role, propagate);
9765092e
DM
238 }
239 } else if let Some(group) = group {
240 if delete {
241 tree.delete_group_role(&path, &group, &role);
242 } else {
243 tree.insert_group_role(&path, &group, &role, propagate);
244 }
245 }
246
247 acl::save_config(&tree)?;
248
249 Ok(())
250}
251
ed3e60ae 252pub const ROUTER: Router = Router::new()
9765092e
DM
253 .get(&API_METHOD_READ_ACL)
254 .put(&API_METHOD_UPDATE_ACL);