]> git.proxmox.com Git - proxmox-backup.git/blame - src/config/cached_user_info.rs
api: add permissions endpoint
[proxmox-backup.git] / src / config / cached_user_info.rs
CommitLineData
423e6561
DM
1//! Cached user info for fast ACL permission checks
2
6e695960 3use std::sync::{RwLock, Arc};
423e6561 4
a737179e 5use anyhow::{Error, bail};
423e6561
DM
6
7use proxmox::api::section_config::SectionConfigData;
6e695960 8use lazy_static::lazy_static;
423e6561
DM
9use proxmox::api::UserInformation;
10
1347b115 11use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
e6dc35ac
FG
12use super::user::{ApiToken, User};
13use crate::api2::types::{Authid, Userid};
423e6561 14
e6dc35ac 15/// Cache User/Group/Token/Acl configuration data for fast permission tests
423e6561
DM
16pub struct CachedUserInfo {
17 user_cfg: Arc<SectionConfigData>,
18 acl_tree: Arc<AclTree>,
19}
20
6e695960
DM
21fn now() -> i64 { unsafe { libc::time(std::ptr::null_mut()) } }
22
23struct ConfigCache {
24 data: Option<Arc<CachedUserInfo>>,
25 last_update: i64,
26}
27
28lazy_static! {
29 static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
30 ConfigCache { data: None, last_update: 0 }
31 );
32}
33
423e6561
DM
34impl CachedUserInfo {
35
6e695960
DM
36 /// Returns a cached instance (up to 5 seconds old).
37 pub fn new() -> Result<Arc<Self>, Error> {
38 let now = now();
39 { // limit scope
40 let cache = CACHED_CONFIG.read().unwrap();
41 if (now - cache.last_update) < 5 {
42 if let Some(ref config) = cache.data {
43 return Ok(config.clone());
44 }
45 }
46 }
47
48 let config = Arc::new(CachedUserInfo {
423e6561
DM
49 user_cfg: super::user::cached_config()?,
50 acl_tree: super::acl::cached_config()?,
6e695960
DM
51 });
52
53 let mut cache = CACHED_CONFIG.write().unwrap();
54 cache.last_update = now;
55 cache.data = Some(config.clone());
56
57 Ok(config)
423e6561
DM
58 }
59
e6dc35ac
FG
60 /// Test if a authentication id is enabled and not expired
61 pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
62 let userid = auth_id.user();
63
e7cb4dc5 64 if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
423e6561
DM
65 if !info.enable.unwrap_or(true) {
66 return false;
67 }
68 if let Some(expire) = info.expire {
20813274
WB
69 if expire > 0 && expire <= now() {
70 return false;
423e6561
DM
71 }
72 }
423e6561
DM
73 } else {
74 return false;
75 }
e6dc35ac
FG
76
77 if auth_id.is_token() {
78 if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) {
79 if !info.enable.unwrap_or(true) {
80 return false;
81 }
82 if let Some(expire) = info.expire {
83 if expire > 0 && expire <= now() {
84 return false;
85 }
86 }
87 return true;
88 } else {
89 return false;
90 }
91 }
92
93 return true;
423e6561 94 }
a737179e
DM
95
96 pub fn check_privs(
97 &self,
e6dc35ac 98 auth_id: &Authid,
a737179e
DM
99 path: &[&str],
100 required_privs: u64,
101 partial: bool,
102 ) -> Result<(), Error> {
e6dc35ac 103 let privs = self.lookup_privs(&auth_id, path);
a737179e 104 let allowed = if partial {
e6dc35ac 105 (privs & required_privs) != 0
a737179e 106 } else {
e6dc35ac 107 (privs & required_privs) == required_privs
a737179e
DM
108 };
109 if !allowed {
3cfc56f5
TL
110 // printing the path doesn't leaks any information as long as we
111 // always check privilege before resource existence
112 bail!("no permissions on '/{}'", path.join("/"));
a737179e
DM
113 }
114 Ok(())
115 }
423e6561 116
e6dc35ac
FG
117 pub fn is_superuser(&self, auth_id: &Authid) -> bool {
118 !auth_id.is_token() && auth_id.user() == "root@pam"
423e6561
DM
119 }
120
e7cb4dc5 121 pub fn is_group_member(&self, _userid: &Userid, _group: &str) -> bool {
423e6561
DM
122 false
123 }
124
e6dc35ac 125 pub fn lookup_privs(&self, auth_id: &Authid, path: &[&str]) -> u64 {
babab85b
FG
126 let (privs, _) = self.lookup_privs_details(auth_id, path);
127 privs
128 }
129
130 pub fn lookup_privs_details(&self, auth_id: &Authid, path: &[&str]) -> (u64, u64) {
e6dc35ac 131 if self.is_superuser(auth_id) {
babab85b 132 return (ROLE_ADMIN, ROLE_ADMIN);
e7cb4dc5 133 }
1347b115 134
e6dc35ac 135 let roles = self.acl_tree.roles(auth_id, path);
423e6561 136 let mut privs: u64 = 0;
babab85b
FG
137 let mut propagated_privs: u64 = 0;
138 for (role, propagate) in roles {
3fff55b2 139 if let Some((role_privs, _)) = ROLE_NAMES.get(role.as_str()) {
babab85b
FG
140 if propagate {
141 propagated_privs |= role_privs;
142 }
423e6561
DM
143 privs |= role_privs;
144 }
145 }
e6dc35ac
FG
146
147 if auth_id.is_token() {
148 // limit privs to that of owning user
149 let user_auth_id = Authid::from(auth_id.user().clone());
150 privs &= self.lookup_privs(&user_auth_id, path);
babab85b
FG
151 let (owner_privs, owner_propagated_privs) = self.lookup_privs_details(&user_auth_id, path);
152 privs &= owner_privs;
153 propagated_privs &= owner_propagated_privs;
e6dc35ac
FG
154 }
155
babab85b 156 (privs, propagated_privs)
423e6561 157 }
babab85b 158
423e6561 159}
e7cb4dc5
WB
160
161impl UserInformation for CachedUserInfo {
162 fn is_superuser(&self, userid: &str) -> bool {
163 userid == "root@pam"
164 }
165
166 fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
167 false
168 }
169
e6dc35ac
FG
170 fn lookup_privs(&self, auth_id: &str, path: &[&str]) -> u64 {
171 match auth_id.parse::<Authid>() {
172 Ok(auth_id) => Self::lookup_privs(self, &auth_id, path),
e7cb4dc5
WB
173 Err(_) => 0,
174 }
175 }
176}