]> git.proxmox.com Git - proxmox-backup.git/blame - src/server/auth.rs
proxmox-rest-server: use new ServerAdapter trait instead of callbacks
[proxmox-backup.git] / src / server / auth.rs
CommitLineData
ba39ab20 1//! Provides authentication primitives for the HTTP server
ba39ab20 2
038f3850 3use anyhow::format_err;
26858dba 4
98b7d58b
DM
5use proxmox::api::UserInformation;
6
9eb78407 7use pbs_tools::ticket::{self, Ticket};
6227654a
DM
8use pbs_config::{token_shadow, CachedUserInfo};
9use pbs_api_types::{Authid, Userid};
608806e8 10use proxmox_rest_server::{AuthError, extract_cookie};
9eb78407 11
ba39ab20 12use crate::auth_helpers::*;
ba39ab20
SR
13
14use hyper::header;
15use percent_encoding::percent_decode_str;
16
26858dba 17struct UserAuthData {
ba39ab20
SR
18 ticket: String,
19 csrf_token: Option<String>,
20}
21
26858dba 22enum AuthData {
ba39ab20
SR
23 User(UserAuthData),
24 ApiToken(String),
25}
26
608806e8
DM
27fn 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 }));
ba39ab20
SR
36 }
37 }
608806e8 38 }
ba39ab20 39
608806e8
DM
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
ba39ab20 46 }
26858dba 47 }
608806e8 48 _ => None,
ba39ab20 49 }
608806e8 50}
98b7d58b 51
608806e8
DM
52pub async fn check_pbs_auth(
53 headers: &http::HeaderMap,
54 method: &hyper::Method,
55) -> Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError> {
fd6d2438 56
608806e8 57 // fixme: make all IO async
038f3850 58
608806e8 59 let user_info = CachedUserInfo::new()?;
fd6d2438 60
608806e8
DM
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;
ba39ab20 66
608806e8
DM
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()?;
26858dba 70
608806e8
DM
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 }
ba39ab20 75
608806e8
DM
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());
26858dba 87 }
608806e8 88 }
ba39ab20 89
608806e8
DM
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());
ba39ab20 101 }
26858dba 102
608806e8
DM
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"))?;
ba39ab20 109
608806e8 110 token_shadow::verify_secret(&tokenid, &tokensecret)?;
ba39ab20 111
608806e8 112 Ok((tokenid.to_string(), Box::new(user_info)))
ba39ab20 113 }
608806e8 114 None => Err(AuthError::NoData),
038f3850
DM
115 }
116}