]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/access/user.rs
b3f70dcf54c7f9365e12dcaaa79ea9ecf8f3c114
[proxmox-backup.git] / src / api2 / access / user.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3
4 use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
5 use proxmox::api::schema::{Schema, StringSchema};
6 use proxmox::tools::fs::open_file_locked;
7
8 use crate::api2::types::*;
9 use crate::config::user;
10 use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY};
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,
25 items: { type: user::User },
26 },
27 access: {
28 permission: &Permission::Privilege(&["access", "users"], PRIV_SYS_AUDIT, false),
29 },
30 )]
31 /// List all users
32 pub fn list_users(
33 _param: Value,
34 _info: &ApiMethod,
35 mut rpcenv: &mut dyn RpcEnvironment,
36 ) -> Result<Vec<user::User>, Error> {
37
38 let (config, digest) = user::config()?;
39
40 let list = config.convert_to_typed_array("user")?;
41
42 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
43
44 Ok(list)
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 },
84 access: {
85 permission: &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
86 },
87 )]
88 /// Create new user.
89 pub fn create_user(password: Option<String>, param: Value) -> Result<(), Error> {
90
91 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
92
93 let user: user::User = serde_json::from_value(param)?;
94
95 let (mut config, _digest) = user::config()?;
96
97 if let Some(_) = config.sections.get(&user.userid) {
98 bail!("user '{}' already exists.", user.userid);
99 }
100
101 let (username, realm) = crate::auth::parse_userid(&user.userid)?;
102 let authenticator = crate::auth::lookup_authenticator(&realm)?;
103
104 config.set_data(&user.userid, "user", &user)?;
105
106 user::save_config(&config)?;
107
108 if let Some(password) = password {
109 authenticator.store_password(&username, &password)?;
110 }
111
112 Ok(())
113 }
114
115 #[api(
116 input: {
117 properties: {
118 userid: {
119 schema: PROXMOX_USER_ID_SCHEMA,
120 },
121 },
122 },
123 returns: {
124 description: "The user configuration (with config digest).",
125 type: user::User,
126 },
127 access: {
128 permission: &Permission::Privilege(&["access", "users"], PRIV_SYS_AUDIT, false),
129 },
130 )]
131 /// Read user configuration data.
132 pub fn read_user(userid: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<user::User, Error> {
133 let (config, digest) = user::config()?;
134 let user = config.lookup("user", &userid)?;
135 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
136 Ok(user)
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 },
180 access: {
181 permission: &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
182 },
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
197 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
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 {
226 let (username, realm) = crate::auth::parse_userid(&userid)?;
227 let authenticator = crate::auth::lookup_authenticator(&realm)?;
228 authenticator.store_password(&username, &password)?;
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 },
262 access: {
263 permission: &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false),
264 },
265 )]
266 /// Remove a user from the configuration file.
267 pub fn delete_user(userid: String, digest: Option<String>) -> Result<(), Error> {
268
269 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
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);