]>
Commit | Line | Data |
---|---|---|
f7d4e4b5 | 1 | use anyhow::{bail, Error}; |
579728c6 DM |
2 | use serde_json::Value; |
3 | ||
d4f020f4 | 4 | use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; |
579728c6 DM |
5 | use proxmox::api::schema::{Schema, StringSchema}; |
6 | ||
7 | use crate::api2::types::*; | |
8 | use crate::config::user; | |
4f66423f | 9 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; |
579728c6 DM |
10 | |
11 | pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") | |
12 | .format(&PASSWORD_FORMAT) | |
13 | .min_length(5) | |
14 | .max_length(64) | |
15 | .schema(); | |
16 | ||
17 | #[api( | |
18 | input: { | |
19 | properties: {}, | |
20 | }, | |
21 | returns: { | |
22 | description: "List users (with config digest).", | |
23 | type: Array, | |
24 | items: { | |
25 | type: Object, | |
26 | description: "User configuration (without password).", | |
27 | properties: { | |
28 | userid: { | |
29 | schema: PROXMOX_USER_ID_SCHEMA, | |
30 | }, | |
31 | comment: { | |
32 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
33 | optional: true, | |
34 | }, | |
35 | enable: { | |
36 | schema: user::ENABLE_USER_SCHEMA, | |
37 | optional: true, | |
38 | }, | |
39 | expire: { | |
40 | schema: user::EXPIRE_USER_SCHEMA, | |
41 | optional: true, | |
42 | }, | |
43 | firstname: { | |
44 | schema: user::FIRST_NAME_SCHEMA, | |
45 | optional: true, | |
46 | }, | |
47 | lastname: { | |
48 | schema: user::LAST_NAME_SCHEMA, | |
49 | optional: true, | |
50 | }, | |
51 | email: { | |
52 | schema: user::EMAIL_SCHEMA, | |
53 | optional: true, | |
54 | }, | |
55 | }, | |
56 | }, | |
57 | }, | |
d4f020f4 DM |
58 | access: { |
59 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
60 | }, | |
579728c6 DM |
61 | )] |
62 | /// List all users | |
63 | pub fn list_users( | |
64 | _param: Value, | |
65 | _info: &ApiMethod, | |
66 | _rpcenv: &mut dyn RpcEnvironment, | |
67 | ) -> Result<Value, Error> { | |
68 | ||
69 | let (config, digest) = user::config()?; | |
70 | ||
71 | let value = config.convert_to_array("userid", Some(&digest), &[]); | |
72 | ||
73 | Ok(value.into()) | |
74 | } | |
75 | ||
76 | #[api( | |
77 | protected: true, | |
78 | input: { | |
79 | properties: { | |
80 | userid: { | |
81 | schema: PROXMOX_USER_ID_SCHEMA, | |
82 | }, | |
83 | comment: { | |
84 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
85 | optional: true, | |
86 | }, | |
87 | password: { | |
88 | schema: PBS_PASSWORD_SCHEMA, | |
89 | optional: true, | |
90 | }, | |
91 | enable: { | |
92 | schema: user::ENABLE_USER_SCHEMA, | |
93 | optional: true, | |
94 | }, | |
95 | expire: { | |
96 | schema: user::EXPIRE_USER_SCHEMA, | |
97 | optional: true, | |
98 | }, | |
99 | firstname: { | |
100 | schema: user::FIRST_NAME_SCHEMA, | |
101 | optional: true, | |
102 | }, | |
103 | lastname: { | |
104 | schema: user::LAST_NAME_SCHEMA, | |
105 | optional: true, | |
106 | }, | |
107 | email: { | |
108 | schema: user::EMAIL_SCHEMA, | |
109 | optional: true, | |
110 | }, | |
111 | }, | |
112 | }, | |
d4f020f4 | 113 | access: { |
4f66423f | 114 | permission: &Permission::Privilege(&[], PRIV_PERMISSIONS_MODIFY, false), |
d4f020f4 | 115 | }, |
579728c6 DM |
116 | )] |
117 | /// Create new user. | |
7d817b03 | 118 | pub fn create_user(userid: String, password: Option<String>, param: Value) -> Result<(), Error> { |
579728c6 DM |
119 | |
120 | let _lock = crate::tools::open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?; | |
121 | ||
122 | let user: user::User = serde_json::from_value(param.clone())?; | |
123 | ||
124 | let (mut config, _digest) = user::config()?; | |
125 | ||
126 | if let Some(_) = config.sections.get(&userid) { | |
127 | bail!("user '{}' already exists.", userid); | |
128 | } | |
129 | ||
7d817b03 DM |
130 | let (username, realm) = crate::auth::parse_userid(&userid)?; |
131 | let authenticator = crate::auth::lookup_authenticator(&realm)?; | |
579728c6 DM |
132 | |
133 | config.set_data(&userid, "user", &user)?; | |
134 | ||
135 | user::save_config(&config)?; | |
136 | ||
7d817b03 DM |
137 | if let Some(password) = password { |
138 | authenticator.store_password(&username, &password)?; | |
139 | } | |
140 | ||
579728c6 DM |
141 | Ok(()) |
142 | } | |
143 | ||
144 | #[api( | |
145 | input: { | |
146 | properties: { | |
147 | userid: { | |
148 | schema: PROXMOX_USER_ID_SCHEMA, | |
149 | }, | |
685e1334 | 150 | }, |
579728c6 DM |
151 | }, |
152 | returns: { | |
153 | description: "The user configuration (with config digest).", | |
154 | type: user::User, | |
155 | }, | |
d4f020f4 DM |
156 | access: { |
157 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
158 | }, | |
579728c6 DM |
159 | )] |
160 | /// Read user configuration data. | |
161 | pub fn read_user(userid: String) -> Result<Value, Error> { | |
162 | let (config, digest) = user::config()?; | |
163 | let mut data = config.lookup_json("user", &userid)?; | |
164 | data.as_object_mut().unwrap() | |
165 | .insert("digest".into(), proxmox::tools::digest_to_hex(&digest).into()); | |
166 | Ok(data) | |
167 | } | |
168 | ||
169 | #[api( | |
170 | protected: true, | |
171 | input: { | |
172 | properties: { | |
173 | userid: { | |
174 | schema: PROXMOX_USER_ID_SCHEMA, | |
175 | }, | |
176 | comment: { | |
177 | optional: true, | |
178 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
179 | }, | |
180 | password: { | |
181 | schema: PBS_PASSWORD_SCHEMA, | |
182 | optional: true, | |
183 | }, | |
184 | enable: { | |
185 | schema: user::ENABLE_USER_SCHEMA, | |
186 | optional: true, | |
187 | }, | |
188 | expire: { | |
189 | schema: user::EXPIRE_USER_SCHEMA, | |
190 | optional: true, | |
191 | }, | |
192 | firstname: { | |
193 | schema: user::FIRST_NAME_SCHEMA, | |
194 | optional: true, | |
195 | }, | |
196 | lastname: { | |
197 | schema: user::LAST_NAME_SCHEMA, | |
198 | optional: true, | |
199 | }, | |
200 | email: { | |
201 | schema: user::EMAIL_SCHEMA, | |
202 | optional: true, | |
203 | }, | |
204 | digest: { | |
205 | optional: true, | |
206 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
207 | }, | |
208 | }, | |
209 | }, | |
d4f020f4 | 210 | access: { |
4f66423f | 211 | permission: &Permission::Privilege(&[], PRIV_PERMISSIONS_MODIFY, false), |
d4f020f4 | 212 | }, |
579728c6 DM |
213 | )] |
214 | /// Update user configuration. | |
215 | pub fn update_user( | |
216 | userid: String, | |
217 | comment: Option<String>, | |
218 | enable: Option<bool>, | |
219 | expire: Option<i64>, | |
220 | password: Option<String>, | |
221 | firstname: Option<String>, | |
222 | lastname: Option<String>, | |
223 | email: Option<String>, | |
224 | digest: Option<String>, | |
225 | ) -> Result<(), Error> { | |
226 | ||
227 | let _lock = crate::tools::open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?; | |
228 | ||
229 | let (mut config, expected_digest) = user::config()?; | |
230 | ||
231 | if let Some(ref digest) = digest { | |
232 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
233 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
234 | } | |
235 | ||
236 | let mut data: user::User = config.lookup("user", &userid)?; | |
237 | ||
238 | if let Some(comment) = comment { | |
239 | let comment = comment.trim().to_string(); | |
240 | if comment.is_empty() { | |
241 | data.comment = None; | |
242 | } else { | |
243 | data.comment = Some(comment); | |
244 | } | |
245 | } | |
246 | ||
247 | if let Some(enable) = enable { | |
248 | data.enable = if enable { None } else { Some(false) }; | |
249 | } | |
250 | ||
251 | if let Some(expire) = expire { | |
252 | data.expire = if expire > 0 { Some(expire) } else { None }; | |
253 | } | |
254 | ||
255 | if let Some(password) = password { | |
7d817b03 DM |
256 | let (username, realm) = crate::auth::parse_userid(&userid)?; |
257 | let authenticator = crate::auth::lookup_authenticator(&realm)?; | |
258 | authenticator.store_password(&username, &password)?; | |
579728c6 DM |
259 | } |
260 | ||
261 | if let Some(firstname) = firstname { | |
262 | data.firstname = if firstname.is_empty() { None } else { Some(firstname) }; | |
263 | } | |
264 | ||
265 | if let Some(lastname) = lastname { | |
266 | data.lastname = if lastname.is_empty() { None } else { Some(lastname) }; | |
267 | } | |
268 | if let Some(email) = email { | |
269 | data.email = if email.is_empty() { None } else { Some(email) }; | |
270 | } | |
271 | ||
272 | config.set_data(&userid, "user", &data)?; | |
273 | ||
274 | user::save_config(&config)?; | |
275 | ||
276 | Ok(()) | |
277 | } | |
278 | ||
279 | #[api( | |
280 | protected: true, | |
281 | input: { | |
282 | properties: { | |
283 | userid: { | |
284 | schema: PROXMOX_USER_ID_SCHEMA, | |
285 | }, | |
286 | digest: { | |
287 | optional: true, | |
288 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
289 | }, | |
290 | }, | |
291 | }, | |
d4f020f4 | 292 | access: { |
4f66423f | 293 | permission: &Permission::Privilege(&[], PRIV_PERMISSIONS_MODIFY, false), |
d4f020f4 | 294 | }, |
579728c6 DM |
295 | )] |
296 | /// Remove a user from the configuration file. | |
297 | pub fn delete_user(userid: String, digest: Option<String>) -> Result<(), Error> { | |
298 | ||
299 | let _lock = crate::tools::open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?; | |
300 | ||
301 | let (mut config, expected_digest) = user::config()?; | |
302 | ||
303 | if let Some(ref digest) = digest { | |
304 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
305 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
306 | } | |
307 | ||
308 | match config.sections.get(&userid) { | |
309 | Some(_) => { config.sections.remove(&userid); }, | |
310 | None => bail!("user '{}' does not exist.", userid), | |
311 | } | |
312 | ||
313 | user::save_config(&config)?; | |
314 | ||
315 | Ok(()) | |
316 | } | |
317 | ||
318 | const ITEM_ROUTER: Router = Router::new() | |
319 | .get(&API_METHOD_READ_USER) | |
320 | .put(&API_METHOD_UPDATE_USER) | |
321 | .delete(&API_METHOD_DELETE_USER); | |
322 | ||
323 | pub const ROUTER: Router = Router::new() | |
324 | .get(&API_METHOD_LIST_USERS) | |
325 | .post(&API_METHOD_CREATE_USER) | |
326 | .match_all("userid", &ITEM_ROUTER); |