]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/verify.rs
api/config: use param_bail for parameter errors
[proxmox-backup.git] / src / api2 / config / verify.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3 use ::serde::{Deserialize, Serialize};
4 use hex::FromHex;
5
6 use proxmox_router::{Router, RpcEnvironment, Permission};
7 use proxmox_schema::{api, param_bail};
8
9 use pbs_api_types::{
10 Authid, VerificationJobConfig, VerificationJobConfigUpdater, JOB_ID_SCHEMA,
11 PROXMOX_CONFIG_DIGEST_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY,
12 };
13 use pbs_config::verify;
14
15 use pbs_config::CachedUserInfo;
16
17 #[api(
18 input: {
19 properties: {},
20 },
21 returns: {
22 description: "List configured jobs.",
23 type: Array,
24 items: { type: VerificationJobConfig },
25 },
26 access: {
27 permission: &Permission::Anybody,
28 description: "Requires Datastore.Audit or Datastore.Verify on datastore.",
29 },
30 )]
31 /// List all verification jobs
32 pub fn list_verification_jobs(
33 _param: Value,
34 mut rpcenv: &mut dyn RpcEnvironment,
35 ) -> Result<Vec<VerificationJobConfig>, Error> {
36 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
37 let user_info = CachedUserInfo::new()?;
38
39 let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY;
40
41 let (config, digest) = verify::config()?;
42
43 let list = config.convert_to_typed_array("verification")?;
44
45 let list = list.into_iter()
46 .filter(|job: &VerificationJobConfig| {
47 let privs = user_info.lookup_privs(&auth_id, &["datastore", &job.store]);
48
49 privs & required_privs != 00
50 }).collect();
51
52 rpcenv["digest"] = hex::encode(&digest).into();
53
54 Ok(list)
55 }
56
57
58 #[api(
59 protected: true,
60 input: {
61 properties: {
62 config: {
63 type: VerificationJobConfig,
64 flatten: true,
65 },
66 },
67 },
68 access: {
69 permission: &Permission::Anybody,
70 description: "Requires Datastore.Verify on job's datastore.",
71 },
72 )]
73 /// Create a new verification job.
74 pub fn create_verification_job(
75 config: VerificationJobConfig,
76 rpcenv: &mut dyn RpcEnvironment
77 ) -> Result<(), Error> {
78 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
79 let user_info = CachedUserInfo::new()?;
80
81 user_info.check_privs(&auth_id, &["datastore", &config.store], PRIV_DATASTORE_VERIFY, false)?;
82
83 let _lock = verify::lock_config()?;
84
85 let (mut section_config, _digest) = verify::config()?;
86
87 if section_config.sections.get(&config.id).is_some() {
88 param_bail!("id", "job '{}' already exists.", config.id);
89 }
90
91 section_config.set_data(&config.id, "verification", &config)?;
92
93 verify::save_config(&section_config)?;
94
95 crate::server::jobstate::create_state_file("verificationjob", &config.id)?;
96
97 Ok(())
98 }
99
100 #[api(
101 input: {
102 properties: {
103 id: {
104 schema: JOB_ID_SCHEMA,
105 },
106 },
107 },
108 returns: { type: VerificationJobConfig },
109 access: {
110 permission: &Permission::Anybody,
111 description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.",
112 },
113 )]
114 /// Read a verification job configuration.
115 pub fn read_verification_job(
116 id: String,
117 mut rpcenv: &mut dyn RpcEnvironment,
118 ) -> Result<VerificationJobConfig, Error> {
119 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
120 let user_info = CachedUserInfo::new()?;
121
122 let (config, digest) = verify::config()?;
123
124 let verification_job: VerificationJobConfig = config.lookup("verification", &id)?;
125
126 let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY;
127 user_info.check_privs(&auth_id, &["datastore", &verification_job.store], required_privs, true)?;
128
129 rpcenv["digest"] = hex::encode(&digest).into();
130
131 Ok(verification_job)
132 }
133
134 #[api()]
135 #[derive(Serialize, Deserialize)]
136 #[serde(rename_all="kebab-case")]
137 /// Deletable property name
138 pub enum DeletableProperty {
139 /// Delete the ignore verified property.
140 IgnoreVerified,
141 /// Delete the comment property.
142 Comment,
143 /// Delete the job schedule.
144 Schedule,
145 /// Delete outdated after property.
146 OutdatedAfter
147 }
148
149 #[api(
150 protected: true,
151 input: {
152 properties: {
153 id: {
154 schema: JOB_ID_SCHEMA,
155 },
156 update: {
157 type: VerificationJobConfigUpdater,
158 flatten: true,
159 },
160 delete: {
161 description: "List of properties to delete.",
162 type: Array,
163 optional: true,
164 items: {
165 type: DeletableProperty,
166 }
167 },
168 digest: {
169 optional: true,
170 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
171 },
172 },
173 },
174 access: {
175 permission: &Permission::Anybody,
176 description: "Requires Datastore.Verify on job's datastore.",
177 },
178 )]
179 /// Update verification job config.
180 #[allow(clippy::too_many_arguments)]
181 pub fn update_verification_job(
182 id: String,
183 update: VerificationJobConfigUpdater,
184 delete: Option<Vec<DeletableProperty>>,
185 digest: Option<String>,
186 rpcenv: &mut dyn RpcEnvironment,
187 ) -> Result<(), Error> {
188 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
189 let user_info = CachedUserInfo::new()?;
190
191 let _lock = verify::lock_config()?;
192
193 // pass/compare digest
194 let (mut config, expected_digest) = verify::config()?;
195
196 if let Some(ref digest) = digest {
197 let digest = <[u8; 32]>::from_hex(digest)?;
198 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
199 }
200
201 let mut data: VerificationJobConfig = config.lookup("verification", &id)?;
202
203 // check existing store
204 user_info.check_privs(&auth_id, &["datastore", &data.store], PRIV_DATASTORE_VERIFY, true)?;
205
206 if let Some(delete) = delete {
207 for delete_prop in delete {
208 match delete_prop {
209 DeletableProperty::IgnoreVerified => { data.ignore_verified = None; },
210 DeletableProperty::OutdatedAfter => { data.outdated_after = None; },
211 DeletableProperty::Comment => { data.comment = None; },
212 DeletableProperty::Schedule => { data.schedule = None; },
213 }
214 }
215 }
216
217 if let Some(comment) = update.comment {
218 let comment = comment.trim().to_string();
219 if comment.is_empty() {
220 data.comment = None;
221 } else {
222 data.comment = Some(comment);
223 }
224 }
225
226 if let Some(store) = update.store {
227 // check new store
228 user_info.check_privs(&auth_id, &["datastore", &store], PRIV_DATASTORE_VERIFY, true)?;
229 data.store = store;
230 }
231
232
233 if update.ignore_verified.is_some() { data.ignore_verified = update.ignore_verified; }
234 if update.outdated_after.is_some() { data.outdated_after = update.outdated_after; }
235 let schedule_changed = data.schedule != update.schedule;
236 if update.schedule.is_some() { data.schedule = update.schedule; }
237
238 config.set_data(&id, "verification", &data)?;
239
240 verify::save_config(&config)?;
241
242 if schedule_changed {
243 crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?;
244 }
245
246 Ok(())
247 }
248
249 #[api(
250 protected: true,
251 input: {
252 properties: {
253 id: {
254 schema: JOB_ID_SCHEMA,
255 },
256 digest: {
257 optional: true,
258 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
259 },
260 },
261 },
262 access: {
263 permission: &Permission::Anybody,
264 description: "Requires Datastore.Verify on job's datastore.",
265 },
266 )]
267 /// Remove a verification job configuration
268 pub fn delete_verification_job(
269 id: String,
270 digest: Option<String>,
271 rpcenv: &mut dyn RpcEnvironment,
272 ) -> Result<(), Error> {
273 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
274 let user_info = CachedUserInfo::new()?;
275
276 let _lock = verify::lock_config()?;
277
278 let (mut config, expected_digest) = verify::config()?;
279
280 let job: VerificationJobConfig = config.lookup("verification", &id)?;
281 user_info.check_privs(&auth_id, &["datastore", &job.store], PRIV_DATASTORE_VERIFY, true)?;
282
283 if let Some(ref digest) = digest {
284 let digest = <[u8; 32]>::from_hex(digest)?;
285 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
286 }
287
288 match config.sections.get(&id) {
289 Some(_) => { config.sections.remove(&id); },
290 None => bail!("job '{}' does not exist.", id),
291 }
292
293 verify::save_config(&config)?;
294
295 crate::server::jobstate::remove_state_file("verificationjob", &id)?;
296
297 Ok(())
298 }
299
300 const ITEM_ROUTER: Router = Router::new()
301 .get(&API_METHOD_READ_VERIFICATION_JOB)
302 .put(&API_METHOD_UPDATE_VERIFICATION_JOB)
303 .delete(&API_METHOD_DELETE_VERIFICATION_JOB);
304
305 pub const ROUTER: Router = Router::new()
306 .get(&API_METHOD_LIST_VERIFICATION_JOBS)
307 .post(&API_METHOD_CREATE_VERIFICATION_JOB)
308 .match_all("id", &ITEM_ROUTER);