1 use std
::collections
::HashMap
;
2 use std
::time
::Duration
;
4 use anyhow
::{bail, format_err, Error}
;
5 use serde
::{Serialize, Deserialize}
;
6 use serde_json
::{from_value, Value}
;
8 use proxmox
::tools
::fs
::{open_file_locked, CreateOptions}
;
10 use crate::api2
::types
::Authid
;
13 const LOCK_FILE
: &str = pbs_buildcfg
::configdir
!("/token.shadow.lock");
14 const CONF_FILE
: &str = pbs_buildcfg
::configdir
!("/token.shadow");
15 const LOCK_TIMEOUT
: Duration
= Duration
::from_secs(5);
17 #[derive(Serialize, Deserialize)]
18 #[serde(rename_all="kebab-case")]
19 /// ApiToken id / secret pair
20 pub struct ApiTokenSecret
{
25 fn read_file() -> Result
<HashMap
<Authid
, String
>, Error
> {
26 let json
= proxmox
::tools
::fs
::file_get_json(CONF_FILE
, Some(Value
::Null
))?
;
28 if json
== Value
::Null
{
31 // swallow serde error which might contain sensitive data
32 from_value(json
).map_err(|_err
| format_err
!("unable to parse '{}'", CONF_FILE
))
36 fn write_file(data
: HashMap
<Authid
, String
>) -> Result
<(), Error
> {
37 let backup_user
= crate::backup
::backup_user()?
;
38 let options
= CreateOptions
::new()
39 .perm(nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640))
40 .owner(backup_user
.uid
)
41 .group(backup_user
.gid
);
43 let json
= serde_json
::to_vec(&data
)?
;
44 proxmox
::tools
::fs
::replace_file(CONF_FILE
, &json
, options
)
47 /// Verifies that an entry for given tokenid / API token secret exists
48 pub fn verify_secret(tokenid
: &Authid
, secret
: &str) -> Result
<(), Error
> {
49 if !tokenid
.is_token() {
50 bail
!("not an API token ID");
53 let data
= read_file()?
;
54 match data
.get(tokenid
) {
55 Some(hashed_secret
) => {
56 auth
::verify_crypt_pw(secret
, &hashed_secret
)
58 None
=> bail
!("invalid API token"),
62 /// Adds a new entry for the given tokenid / API token secret. The secret is stored as salted hash.
63 pub fn set_secret(tokenid
: &Authid
, secret
: &str) -> Result
<(), Error
> {
64 if !tokenid
.is_token() {
65 bail
!("not an API token ID");
68 let _guard
= open_file_locked(LOCK_FILE
, LOCK_TIMEOUT
, true)?
;
70 let mut data
= read_file()?
;
71 let hashed_secret
= auth
::encrypt_pw(secret
)?
;
72 data
.insert(tokenid
.clone(), hashed_secret
);
78 /// Deletes the entry for the given tokenid.
79 pub fn delete_secret(tokenid
: &Authid
) -> Result
<(), Error
> {
80 if !tokenid
.is_token() {
81 bail
!("not an API token ID");
84 let _guard
= open_file_locked(LOCK_FILE
, LOCK_TIMEOUT
, true)?
;
86 let mut data
= read_file()?
;