]> git.proxmox.com Git - proxmox-backup.git/blob - src/server/auth.rs
proxmox-rest-server: use new ServerAdapter trait instead of callbacks
[proxmox-backup.git] / src / server / auth.rs
1 //! Provides authentication primitives for the HTTP server
2
3 use anyhow::format_err;
4
5 use proxmox::api::UserInformation;
6
7 use pbs_tools::ticket::{self, Ticket};
8 use pbs_config::{token_shadow, CachedUserInfo};
9 use pbs_api_types::{Authid, Userid};
10 use proxmox_rest_server::{AuthError, extract_cookie};
11
12 use crate::auth_helpers::*;
13
14 use hyper::header;
15 use percent_encoding::percent_decode_str;
16
17 struct UserAuthData {
18 ticket: String,
19 csrf_token: Option<String>,
20 }
21
22 enum AuthData {
23 User(UserAuthData),
24 ApiToken(String),
25 }
26
27 fn extract_auth_data(headers: &http::HeaderMap) -> Option<AuthData> {
28 if let Some(raw_cookie) = headers.get(header::COOKIE) {
29 if let Ok(cookie) = raw_cookie.to_str() {
30 if let Some(ticket) = extract_cookie(cookie, "PBSAuthCookie") {
31 let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) {
32 Some(Ok(v)) => Some(v.to_owned()),
33 _ => None,
34 };
35 return Some(AuthData::User(UserAuthData { ticket, csrf_token }));
36 }
37 }
38 }
39
40 match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) {
41 Some(Ok(v)) => {
42 if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") {
43 Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned()))
44 } else {
45 None
46 }
47 }
48 _ => None,
49 }
50 }
51
52 pub async fn check_pbs_auth(
53 headers: &http::HeaderMap,
54 method: &hyper::Method,
55 ) -> Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError> {
56
57 // fixme: make all IO async
58
59 let user_info = CachedUserInfo::new()?;
60
61 let auth_data = extract_auth_data(headers);
62 match auth_data {
63 Some(AuthData::User(user_auth_data)) => {
64 let ticket = user_auth_data.ticket.clone();
65 let ticket_lifetime = ticket::TICKET_LIFETIME;
66
67 let userid: Userid = Ticket::<super::ticket::ApiTicket>::parse(&ticket)?
68 .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?
69 .require_full()?;
70
71 let auth_id = Authid::from(userid.clone());
72 if !user_info.is_active_auth_id(&auth_id) {
73 return Err(format_err!("user account disabled or expired.").into());
74 }
75
76 if method != hyper::Method::GET {
77 if let Some(csrf_token) = &user_auth_data.csrf_token {
78 verify_csrf_prevention_token(
79 csrf_secret(),
80 &userid,
81 &csrf_token,
82 -300,
83 ticket_lifetime,
84 )?;
85 } else {
86 return Err(format_err!("missing CSRF prevention token").into());
87 }
88 }
89
90 Ok((auth_id.to_string(), Box::new(user_info)))
91 }
92 Some(AuthData::ApiToken(api_token)) => {
93 let mut parts = api_token.splitn(2, ':');
94 let tokenid = parts
95 .next()
96 .ok_or_else(|| format_err!("failed to split API token header"))?;
97 let tokenid: Authid = tokenid.parse()?;
98
99 if !user_info.is_active_auth_id(&tokenid) {
100 return Err(format_err!("user account or token disabled or expired.").into());
101 }
102
103 let tokensecret = parts
104 .next()
105 .ok_or_else(|| format_err!("failed to split API token header"))?;
106 let tokensecret = percent_decode_str(tokensecret)
107 .decode_utf8()
108 .map_err(|_| format_err!("failed to decode API token header"))?;
109
110 token_shadow::verify_secret(&tokenid, &tokensecret)?;
111
112 Ok((tokenid.to_string(), Box::new(user_info)))
113 }
114 None => Err(AuthError::NoData),
115 }
116 }