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