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