1 use anyhow
::{bail, format_err, Error}
;
3 use serde_json
::{json, Value}
;
4 use std
::collections
::HashMap
;
5 use std
::collections
::HashSet
;
7 use proxmox
::api
::{api, RpcEnvironment, Permission}
;
8 use proxmox
::api
::router
::{Router, SubdirMap}
;
9 use proxmox
::{sortable, identity}
;
10 use proxmox
::{http_err, list_subdirs_api_method}
;
12 use crate::tools
::ticket
::{self, Empty, Ticket}
;
13 use crate::auth_helpers
::*;
14 use crate::api2
::types
::*;
15 use crate::tools
::{FileLogOptions, FileLogger}
;
17 use crate::config
::acl
as acl_config
;
18 use crate::config
::acl
::{PRIVILEGES, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}
;
19 use crate::config
::cached_user_info
::CachedUserInfo
;
26 /// returns Ok(true) if a ticket has to be created
27 /// and Ok(false) if not
32 privs
: Option
<String
>,
34 ) -> Result
<bool
, Error
> {
35 let user_info
= CachedUserInfo
::new()?
;
37 let auth_id
= Authid
::from(userid
.clone());
38 if !user_info
.is_active_auth_id(&auth_id
) {
39 bail
!("user account disabled or expired.");
42 if password
.starts_with("PBS:") {
43 if let Ok(ticket_userid
) = Ticket
::<Userid
>::parse(password
)
44 .and_then(|ticket
| ticket
.verify(public_auth_key(), "PBS", None
))
46 if *userid
== ticket_userid
{
49 bail
!("ticket login failed - wrong userid");
51 } else if password
.starts_with("PBSTERM:") {
52 if path
.is_none() || privs
.is_none() || port
.is_none() {
53 bail
!("cannot check termnal ticket without path, priv and port");
56 let path
= path
.ok_or_else(|| format_err
!("missing path for termproxy ticket"))?
;
57 let privilege_name
= privs
58 .ok_or_else(|| format_err
!("missing privilege name for termproxy ticket"))?
;
59 let port
= port
.ok_or_else(|| format_err
!("missing port for termproxy ticket"))?
;
61 if let Ok(Empty
) = Ticket
::parse(password
)
62 .and_then(|ticket
| ticket
.verify(
65 Some(&ticket
::term_aad(userid
, &path
, port
)),
68 for (name
, privilege
) in PRIVILEGES
{
69 if *name
== privilege_name
{
70 let mut path_vec
= Vec
::new();
71 for part
in path
.split('
/'
) {
76 user_info
.check_privs(&auth_id
, &path_vec
, *privilege
, false)?
;
81 bail
!("No such privilege");
85 let _
= crate::auth
::authenticate_user(userid
, password
)?
;
96 schema
: PASSWORD_SCHEMA
,
100 description
: "Path for verifying terminal tickets.",
105 description
: "Privilege for verifying terminal tickets.",
110 description
: "Port for verifying terminal tickets.",
119 description
: "User name.",
123 description
: "Auth ticket.",
125 CSRFPreventionToken
: {
127 description
: "Cross Site Request Forgery Prevention Token.",
133 permission
: &Permission
::World
,
136 /// Create or verify authentication ticket.
138 /// Returns: An authentication ticket with additional infos.
142 path
: Option
<String
>,
143 privs
: Option
<String
>,
145 rpcenv
: &mut dyn RpcEnvironment
,
146 ) -> Result
<Value
, Error
> {
147 let logger_options
= FileLogOptions
{
152 let mut auth_log
= FileLogger
::new("/var/log/proxmox-backup/api/auth.log", logger_options
)?
;
154 match authenticate_user(&username
, &password
, path
, privs
, port
) {
156 let ticket
= Ticket
::new("PBS", &username
)?
.sign(private_auth_key(), None
)?
;
158 let token
= assemble_csrf_prevention_token(csrf_secret(), &username
);
160 auth_log
.log(format
!("successful auth for user '{}'", username
));
163 "username": username
,
165 "CSRFPreventionToken": token
,
168 Ok(false) => Ok(json
!({
169 "username": username
,
172 let client_ip
= match rpcenv
.get_client_ip().map(|addr
| addr
.ip()) {
173 Some(ip
) => format
!("{}", ip
),
174 None
=> "unknown".into(),
178 "authentication failure; rhost={} user={} msg={}",
184 log
::error
!("{}", msg
);
186 Err(http_err
!(UNAUTHORIZED
, "permission check failed."))
198 schema
: PASSWORD_SCHEMA
,
203 description
: "Anybody is allowed to change there own password. In addition, users with 'Permissions:Modify' privilege may change any password.",
204 permission
: &Permission
::Anybody
,
208 /// Change user password
210 /// Each user is allowed to change his own password. Superuser
211 /// can change all passwords.
215 rpcenv
: &mut dyn RpcEnvironment
,
216 ) -> Result
<Value
, Error
> {
218 let current_user
: Userid
= rpcenv
220 .ok_or_else(|| format_err
!("unknown user"))?
222 let current_auth
= Authid
::from(current_user
.clone());
224 let mut allowed
= userid
== current_user
;
226 if userid
== "root@pam" { allowed = true; }
229 let user_info
= CachedUserInfo
::new()?
;
230 let privs
= user_info
.lookup_privs(¤t_auth
, &[]);
231 if (privs
& PRIV_PERMISSIONS_MODIFY
) != 0 { allowed = true; }
235 bail
!("you are not authorized to change the password.");
238 let authenticator
= crate::auth
::lookup_authenticator(userid
.realm())?
;
239 authenticator
.store_password(userid
.name(), &password
)?
;
252 schema
: ACL_PATH_SCHEMA
,
258 permission
: &Permission
::Anybody
,
259 description
: "Requires Sys.Audit on '/access', limited to own privileges otherwise.",
262 description
: "Map of ACL path to Map of privilege to propagate bit",
265 additional_properties
: true,
268 /// List permissions of given or currently authenticated user / API token.
270 /// Optionally limited to specific path.
271 pub fn list_permissions(
272 auth_id
: Option
<Authid
>,
273 path
: Option
<String
>,
274 rpcenv
: &dyn RpcEnvironment
,
275 ) -> Result
<HashMap
<String
, HashMap
<String
, bool
>>, Error
> {
276 let current_auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
278 let user_info
= CachedUserInfo
::new()?
;
279 let user_privs
= user_info
.lookup_privs(¤t_auth_id
, &["access"]);
281 let auth_id
= if user_privs
& PRIV_SYS_AUDIT
== 0 {
284 if auth_id
== current_auth_id
{
286 } else if auth_id
.is_token()
287 && !current_auth_id
.is_token()
288 && auth_id
.user() == current_auth_id
.user() {
291 bail
!("not allowed to list permissions of {}", auth_id
);
294 None
=> current_auth_id
,
298 Some(auth_id
) => auth_id
,
299 None
=> current_auth_id
,
304 fn populate_acl_paths(
305 mut paths
: HashSet
<String
>,
306 node
: acl_config
::AclTreeNode
,
308 ) -> HashSet
<String
> {
309 for (sub_path
, child_node
) in node
.children
{
310 let sub_path
= format
!("{}/{}", path
, &sub_path
);
311 paths
= populate_acl_paths(paths
, child_node
, &sub_path
);
312 paths
.insert(sub_path
);
317 let paths
= match path
{
319 let mut paths
= HashSet
::new();
324 let mut paths
= HashSet
::new();
326 let (acl_tree
, _
) = acl_config
::config()?
;
327 paths
= populate_acl_paths(paths
, acl_tree
.root
, "");
329 // default paths, returned even if no ACL exists
330 paths
.insert("/".to_string());
331 paths
.insert("/access".to_string());
332 paths
.insert("/datastore".to_string());
333 paths
.insert("/remote".to_string());
334 paths
.insert("/system".to_string());
342 .fold(HashMap
::new(), |mut map
: HashMap
<String
, HashMap
<String
, bool
>>, path
: String
| {
343 let split_path
= acl_config
::split_acl_path(path
.as_str());
344 let (privs
, propagated_privs
) = user_info
.lookup_privs_details(&auth_id
, &split_path
);
347 0 => map
, // Don't leak ACL paths where we don't have any privileges
349 let priv_map
= PRIVILEGES
351 .fold(HashMap
::new(), |mut priv_map
, (name
, value
)| {
352 if value
& privs
!= 0 {
353 priv_map
.insert(name
.to_string(), value
& propagated_privs
!= 0);
358 map
.insert(path
, priv_map
);
367 const SUBDIRS
: SubdirMap
= &sorted
!([
368 ("acl", &acl
::ROUTER
),
370 "password", &Router
::new()
371 .put(&API_METHOD_CHANGE_PASSWORD
)
374 "permissions", &Router
::new()
375 .get(&API_METHOD_LIST_PERMISSIONS
)
378 "ticket", &Router
::new()
379 .post(&API_METHOD_CREATE_TICKET
)
381 ("domains", &domain
::ROUTER
),
382 ("roles", &role
::ROUTER
),
383 ("users", &user
::ROUTER
),
386 pub const ROUTER
: Router
= Router
::new()
387 .get(&list_subdirs_api_method
!(SUBDIRS
))