]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-config/src/user.rs
move user configuration to pbs_config workspace
[proxmox-backup.git] / pbs-config / src / user.rs
1 use std::collections::HashMap;
2 use std::sync::{Arc, RwLock};
3
4 use anyhow::{bail, Error};
5 use lazy_static::lazy_static;
6
7 use proxmox::api::{
8 schema::*,
9 section_config::{
10 SectionConfig,
11 SectionConfigData,
12 SectionConfigPlugin,
13 }
14 };
15
16 use pbs_api_types::{
17 Authid, Userid, ApiToken, User,
18 };
19
20 use crate::memcom::Memcom;
21
22 use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard};
23
24 lazy_static! {
25 pub static ref CONFIG: SectionConfig = init();
26 }
27
28 fn init() -> SectionConfig {
29 let mut config = SectionConfig::new(&Authid::API_SCHEMA);
30
31 let user_schema = match User::API_SCHEMA {
32 Schema::Object(ref user_schema) => user_schema,
33 _ => unreachable!(),
34 };
35 let user_plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema);
36 config.register_plugin(user_plugin);
37
38 let token_schema = match ApiToken::API_SCHEMA {
39 Schema::Object(ref token_schema) => token_schema,
40 _ => unreachable!(),
41 };
42 let token_plugin = SectionConfigPlugin::new("token".to_string(), Some("tokenid".to_string()), token_schema);
43 config.register_plugin(token_plugin);
44
45 config
46 }
47
48 pub const USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg";
49 pub const USER_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.user.lck";
50
51 /// Get exclusive lock
52 pub fn lock_config() -> Result<BackupLockGuard, Error> {
53 open_backup_lockfile(USER_CFG_LOCKFILE, None, true)
54 }
55
56 pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
57
58 let content = proxmox::tools::fs::file_read_optional_string(USER_CFG_FILENAME)?
59 .unwrap_or_else(|| "".to_string());
60
61 let digest = openssl::sha::sha256(content.as_bytes());
62 let mut data = CONFIG.parse(USER_CFG_FILENAME, &content)?;
63
64 if data.sections.get("root@pam").is_none() {
65 let user: User = User {
66 userid: Userid::root_userid().clone(),
67 comment: Some("Superuser".to_string()),
68 enable: None,
69 expire: None,
70 firstname: None,
71 lastname: None,
72 email: None,
73 };
74 data.set_data("root@pam", "user", &user).unwrap();
75 }
76
77 Ok((data, digest))
78 }
79
80 pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> {
81
82 struct ConfigCache {
83 data: Option<Arc<SectionConfigData>>,
84 last_mtime: i64,
85 last_mtime_nsec: i64,
86 }
87
88 lazy_static! {
89 static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
90 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 });
91 }
92
93 let stat = match nix::sys::stat::stat(USER_CFG_FILENAME) {
94 Ok(stat) => Some(stat),
95 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => None,
96 Err(err) => bail!("unable to stat '{}' - {}", USER_CFG_FILENAME, err),
97 };
98
99 { // limit scope
100 let cache = CACHED_CONFIG.read().unwrap();
101 if let Some(ref config) = cache.data {
102 if let Some(stat) = stat {
103 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec {
104 return Ok(config.clone());
105 }
106 } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 {
107 return Ok(config.clone());
108 }
109 }
110 }
111
112 let (config, _digest) = config()?;
113 let config = Arc::new(config);
114
115 let mut cache = CACHED_CONFIG.write().unwrap();
116 if let Some(stat) = stat {
117 cache.last_mtime = stat.st_mtime;
118 cache.last_mtime_nsec = stat.st_mtime_nsec;
119 }
120 cache.data = Some(config.clone());
121
122 Ok(config)
123 }
124
125 pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
126 let raw = CONFIG.write(USER_CFG_FILENAME, &config)?;
127 replace_backup_config(USER_CFG_FILENAME, raw.as_bytes())?;
128
129 // increase user cache generation
130 // We use this in CachedUserInfo
131 let memcom = Memcom::new()?;
132 memcom.increase_user_cache_generation();
133
134 Ok(())
135 }
136
137 /// Only exposed for testing
138 #[doc(hidden)]
139 pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8;32]), Error> {
140 let cfg = init();
141 let parsed = cfg.parse("test_user_cfg", raw)?;
142
143 Ok((parsed, [0;32]))
144 }
145
146 // shell completion helper
147 pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
148 match config() {
149 Ok((data, _digest)) => {
150 data.sections.iter()
151 .filter_map(|(id, (section_type, _))| {
152 if section_type == "user" {
153 Some(id.to_string())
154 } else {
155 None
156 }
157 }).collect()
158 },
159 Err(_) => return vec![],
160 }
161 }
162
163 // shell completion helper
164 pub fn complete_authid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
165 match config() {
166 Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
167 Err(_) => vec![],
168 }
169 }
170
171 // shell completion helper
172 pub fn complete_token_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
173 let data = match config() {
174 Ok((data, _digest)) => data,
175 Err(_) => return Vec::new(),
176 };
177
178 match param.get("userid") {
179 Some(userid) => {
180 let user = data.lookup::<User>("user", userid);
181 let tokens = data.convert_to_typed_array("token");
182 match (user, tokens) {
183 (Ok(_), Ok(tokens)) => {
184 tokens
185 .into_iter()
186 .filter_map(|token: ApiToken| {
187 let tokenid = token.tokenid;
188 if tokenid.is_token() && tokenid.user() == userid {
189 Some(tokenid.tokenname().unwrap().as_str().to_string())
190 } else {
191 None
192 }
193 }).collect()
194 },
195 _ => vec![],
196 }
197 },
198 None => vec![],
199 }
200 }