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