]>
Commit | Line | Data |
---|---|---|
ba39ab20 | 1 | //! Provides authentication primitives for the HTTP server |
ba39ab20 | 2 | |
038f3850 | 3 | use anyhow::format_err; |
26858dba | 4 | |
98b7d58b DM |
5 | use proxmox::api::UserInformation; |
6 | ||
9eb78407 | 7 | use pbs_tools::ticket::{self, Ticket}; |
6227654a DM |
8 | use pbs_config::{token_shadow, CachedUserInfo}; |
9 | use pbs_api_types::{Authid, Userid}; | |
608806e8 | 10 | use proxmox_rest_server::{AuthError, extract_cookie}; |
9eb78407 | 11 | |
ba39ab20 | 12 | use crate::auth_helpers::*; |
ba39ab20 SR |
13 | |
14 | use hyper::header; | |
15 | use percent_encoding::percent_decode_str; | |
16 | ||
26858dba | 17 | struct UserAuthData { |
ba39ab20 SR |
18 | ticket: String, |
19 | csrf_token: Option<String>, | |
20 | } | |
21 | ||
26858dba | 22 | enum AuthData { |
ba39ab20 SR |
23 | User(UserAuthData), |
24 | ApiToken(String), | |
25 | } | |
26 | ||
608806e8 DM |
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 })); | |
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 |
52 | pub 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 | } |