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