1 use anyhow
::{bail, Error}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox
::api
::{api, Permission, Router, RpcEnvironment}
;
6 use proxmox
::tools
::fs
::open_file_locked
;
8 use crate::api2
::types
::*;
10 use crate::config
::acl
::{
12 PRIV_DATASTORE_BACKUP
,
13 PRIV_DATASTORE_MODIFY
,
19 use crate::config
::cached_user_info
::CachedUserInfo
;
20 use crate::config
::sync
::{self, SyncJobConfig}
;
22 pub fn check_sync_job_read_access(
23 user_info
: &CachedUserInfo
,
27 let datastore_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &job
.store
]);
28 if datastore_privs
& PRIV_DATASTORE_AUDIT
== 0 {
32 let remote_privs
= user_info
.lookup_privs(&auth_id
, &["remote", &job
.remote
]);
33 remote_privs
& PRIV_REMOTE_AUDIT
!= 0
35 // user can run the corresponding pull job
36 pub fn check_sync_job_modify_access(
37 user_info
: &CachedUserInfo
,
41 let datastore_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &job
.store
]);
42 if datastore_privs
& PRIV_DATASTORE_BACKUP
== 0 {
46 if let Some(true) = job
.remove_vanished
{
47 if datastore_privs
& PRIV_DATASTORE_PRUNE
== 0 {
52 let correct_owner
= match job
.owner
{
56 && !auth_id
.is_token()
57 && owner
.user() == auth_id
.user())
60 None
=> auth_id
== Authid
::backup_auth_id(),
63 // same permission as changing ownership after syncing
64 if !correct_owner
&& datastore_privs
& PRIV_DATASTORE_MODIFY
== 0 {
68 let remote_privs
= user_info
.lookup_privs(&auth_id
, &["remote", &job
.remote
, &job
.remote_store
]);
69 remote_privs
& PRIV_REMOTE_READ
!= 0
77 description
: "List configured jobs.",
79 items
: { type: sync::SyncJobConfig }
,
82 description
: "Limited to sync job entries where user has Datastore.Audit on target datastore, and Remote.Audit on source remote.",
83 permission
: &Permission
::Anybody
,
86 /// List all sync jobs
87 pub fn list_sync_jobs(
89 mut rpcenv
: &mut dyn RpcEnvironment
,
90 ) -> Result
<Vec
<SyncJobConfig
>, Error
> {
91 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
92 let user_info
= CachedUserInfo
::new()?
;
94 let (config
, digest
) = sync
::config()?
;
96 let list
= config
.convert_to_typed_array("sync")?
;
98 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
102 .filter(|sync_job
| check_sync_job_read_access(&user_info
, &auth_id
, &sync_job
))
112 schema
: JOB_ID_SCHEMA
,
115 schema
: DATASTORE_SCHEMA
,
122 schema
: REMOTE_ID_SCHEMA
,
125 schema
: DATASTORE_SCHEMA
,
128 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
133 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
137 schema
: SYNC_SCHEDULE_SCHEMA
,
142 description
: "User needs Datastore.Backup on target datastore, and Remote.Read on source remote. Additionally, remove_vanished requires Datastore.Prune, and any owner other than the user themselves requires Datastore.Modify",
143 permission
: &Permission
::Anybody
,
146 /// Create a new sync job.
147 pub fn create_sync_job(
149 rpcenv
: &mut dyn RpcEnvironment
,
150 ) -> Result
<(), Error
> {
151 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
152 let user_info
= CachedUserInfo
::new()?
;
154 let _lock
= open_file_locked(sync
::SYNC_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
156 let sync_job
: sync
::SyncJobConfig
= serde_json
::from_value(param
.clone())?
;
157 if !check_sync_job_modify_access(&user_info
, &auth_id
, &sync_job
) {
158 bail
!("permission check failed");
161 let (mut config
, _digest
) = sync
::config()?
;
163 if let Some(_
) = config
.sections
.get(&sync_job
.id
) {
164 bail
!("job '{}' already exists.", sync_job
.id
);
167 config
.set_data(&sync_job
.id
, "sync", &sync_job
)?
;
169 sync
::save_config(&config
)?
;
171 crate::server
::jobstate
::create_state_file("syncjob", &sync_job
.id
)?
;
180 schema
: JOB_ID_SCHEMA
,
185 description
: "The sync job configuration.",
186 type: sync
::SyncJobConfig
,
189 description
: "Limited to sync job entries where user has Datastore.Audit on target datastore, and Remote.Audit on source remote.",
190 permission
: &Permission
::Anybody
,
193 /// Read a sync job configuration.
194 pub fn read_sync_job(
196 mut rpcenv
: &mut dyn RpcEnvironment
,
197 ) -> Result
<SyncJobConfig
, Error
> {
198 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
199 let user_info
= CachedUserInfo
::new()?
;
201 let (config
, digest
) = sync
::config()?
;
203 let sync_job
= config
.lookup("sync", &id
)?
;
204 if !check_sync_job_read_access(&user_info
, &auth_id
, &sync_job
) {
205 bail
!("permission check failed");
208 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
214 #[derive(Serialize, Deserialize)]
215 #[serde(rename_all="kebab-case")]
216 #[allow(non_camel_case_types)]
217 /// Deletable property name
218 pub enum DeletableProperty
{
219 /// Delete the owner property.
221 /// Delete the comment property.
223 /// Delete the job schedule.
225 /// Delete the remove-vanished flag.
234 schema
: JOB_ID_SCHEMA
,
237 schema
: DATASTORE_SCHEMA
,
245 schema
: REMOTE_ID_SCHEMA
,
249 schema
: DATASTORE_SCHEMA
,
253 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
258 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
262 schema
: SYNC_SCHEDULE_SCHEMA
,
265 description
: "List of properties to delete.",
269 type: DeletableProperty
,
274 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
279 permission
: &Permission
::Anybody
,
280 description
: "User needs Datastore.Backup on target datastore, and Remote.Read on source remote. Additionally, remove_vanished requires Datastore.Prune, and any owner other than the user themselves requires Datastore.Modify",
283 /// Update sync job config.
284 pub fn update_sync_job(
286 store
: Option
<String
>,
287 owner
: Option
<Authid
>,
288 remote
: Option
<String
>,
289 remote_store
: Option
<String
>,
290 remove_vanished
: Option
<bool
>,
291 comment
: Option
<String
>,
292 schedule
: Option
<String
>,
293 delete
: Option
<Vec
<DeletableProperty
>>,
294 digest
: Option
<String
>,
295 rpcenv
: &mut dyn RpcEnvironment
,
296 ) -> Result
<(), Error
> {
297 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
298 let user_info
= CachedUserInfo
::new()?
;
300 let _lock
= open_file_locked(sync
::SYNC_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
302 // pass/compare digest
303 let (mut config
, expected_digest
) = sync
::config()?
;
305 if let Some(ref digest
) = digest
{
306 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
307 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
310 let mut data
: sync
::SyncJobConfig
= config
.lookup("sync", &id
)?
;
312 if let Some(delete
) = delete
{
313 for delete_prop
in delete
{
315 DeletableProperty
::owner
=> { data.owner = None; }
,
316 DeletableProperty
::comment
=> { data.comment = None; }
,
317 DeletableProperty
::schedule
=> { data.schedule = None; }
,
318 DeletableProperty
::remove_vanished
=> { data.remove_vanished = None; }
,
323 if let Some(comment
) = comment
{
324 let comment
= comment
.trim().to_string();
325 if comment
.is_empty() {
328 data
.comment
= Some(comment
);
332 if let Some(store
) = store { data.store = store; }
333 if let Some(remote
) = remote { data.remote = remote; }
334 if let Some(remote_store
) = remote_store { data.remote_store = remote_store; }
335 if let Some(owner
) = owner { data.owner = Some(owner); }
337 if schedule
.is_some() { data.schedule = schedule; }
338 if remove_vanished
.is_some() { data.remove_vanished = remove_vanished; }
340 if !check_sync_job_modify_access(&user_info
, &auth_id
, &data
) {
341 bail
!("permission check failed");
344 config
.set_data(&id
, "sync", &data
)?
;
346 sync
::save_config(&config
)?
;
356 schema
: JOB_ID_SCHEMA
,
360 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
365 permission
: &Permission
::Anybody
,
366 description
: "User needs Datastore.Backup on target datastore, and Remote.Read on source remote. Additionally, remove_vanished requires Datastore.Prune, and any owner other than the user themselves requires Datastore.Modify",
369 /// Remove a sync job configuration
370 pub fn delete_sync_job(
372 digest
: Option
<String
>,
373 rpcenv
: &mut dyn RpcEnvironment
,
374 ) -> Result
<(), Error
> {
375 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
376 let user_info
= CachedUserInfo
::new()?
;
378 let _lock
= open_file_locked(sync
::SYNC_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
380 let (mut config
, expected_digest
) = sync
::config()?
;
382 if let Some(ref digest
) = digest
{
383 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
384 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
387 match config
.lookup("sync", &id
) {
389 if !check_sync_job_modify_access(&user_info
, &auth_id
, &job
) {
390 bail
!("permission check failed");
392 config
.sections
.remove(&id
);
394 Err(_
) => { bail!("job '{}' does not exist
.", id) },
397 sync::save_config(&config)?;
399 crate::server::jobstate::remove_state_file("syncjob
", &id)?;
404 const ITEM_ROUTER: Router = Router::new()
405 .get(&API_METHOD_READ_SYNC_JOB)
406 .put(&API_METHOD_UPDATE_SYNC_JOB)
407 .delete(&API_METHOD_DELETE_SYNC_JOB);
409 pub const ROUTER: Router = Router::new()
410 .get(&API_METHOD_LIST_SYNC_JOBS)
411 .post(&API_METHOD_CREATE_SYNC_JOB)
412 .match_all("id
", &ITEM_ROUTER);