From ba39ab20fb791e0cb6e981f24995b09f917fa096 Mon Sep 17 00:00:00 2001 From: Stefan Reiter Date: Wed, 31 Mar 2021 12:21:50 +0200 Subject: [PATCH] server/rest: extract auth to seperate module Signed-off-by: Stefan Reiter --- src/server.rs | 2 + src/server/auth.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++ src/server/rest.rs | 96 +----------------------------------------- 3 files changed, 105 insertions(+), 95 deletions(-) create mode 100644 src/server/auth.rs diff --git a/src/server.rs b/src/server.rs index 7c159c23..b6a37b92 100644 --- a/src/server.rs +++ b/src/server.rs @@ -89,3 +89,5 @@ mod report; pub use report::*; pub mod ticket; + +pub mod auth; diff --git a/src/server/auth.rs b/src/server/auth.rs new file mode 100644 index 00000000..24151886 --- /dev/null +++ b/src/server/auth.rs @@ -0,0 +1,102 @@ +//! Provides authentication primitives for the HTTP server +use anyhow::{bail, format_err, Error}; + +use crate::tools::ticket::Ticket; +use crate::auth_helpers::*; +use crate::tools; +use crate::config::cached_user_info::CachedUserInfo; +use crate::api2::types::{Authid, Userid}; + +use hyper::header; +use percent_encoding::percent_decode_str; + +pub struct UserAuthData { + ticket: String, + csrf_token: Option, +} + +pub enum AuthData { + User(UserAuthData), + ApiToken(String), +} + +pub fn extract_auth_data(headers: &http::HeaderMap) -> Option { + if let Some(raw_cookie) = headers.get(header::COOKIE) { + if let Ok(cookie) = raw_cookie.to_str() { + if let Some(ticket) = tools::extract_cookie(cookie, "PBSAuthCookie") { + let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) { + Some(Ok(v)) => Some(v.to_owned()), + _ => None, + }; + return Some(AuthData::User(UserAuthData { + ticket, + csrf_token, + })); + } + } + } + + match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) { + Some(Ok(v)) => { + if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") { + Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned())) + } else { + None + } + }, + _ => None, + } +} + +pub fn check_auth( + method: &hyper::Method, + auth_data: &AuthData, + user_info: &CachedUserInfo, +) -> Result { + match auth_data { + AuthData::User(user_auth_data) => { + let ticket = user_auth_data.ticket.clone(); + let ticket_lifetime = tools::ticket::TICKET_LIFETIME; + + let userid: Userid = Ticket::::parse(&ticket)? + .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)? + .require_full()?; + + let auth_id = Authid::from(userid.clone()); + if !user_info.is_active_auth_id(&auth_id) { + bail!("user account disabled or expired."); + } + + if method != hyper::Method::GET { + if let Some(csrf_token) = &user_auth_data.csrf_token { + verify_csrf_prevention_token(csrf_secret(), &userid, &csrf_token, -300, ticket_lifetime)?; + } else { + bail!("missing CSRF prevention token"); + } + } + + Ok(auth_id) + }, + AuthData::ApiToken(api_token) => { + let mut parts = api_token.splitn(2, ':'); + let tokenid = parts.next() + .ok_or_else(|| format_err!("failed to split API token header"))?; + let tokenid: Authid = tokenid.parse()?; + + if !user_info.is_active_auth_id(&tokenid) { + bail!("user account or token disabled or expired."); + } + + let tokensecret = parts.next() + .ok_or_else(|| format_err!("failed to split API token header"))?; + let tokensecret = percent_decode_str(tokensecret) + .decode_utf8() + .map_err(|_| format_err!("failed to decode API token header"))?; + + crate::config::token_shadow::verify_secret(&tokenid, &tokensecret)?; + + Ok(tokenid) + } + } +} + diff --git a/src/server/rest.rs b/src/server/rest.rs index 150125ec..9a971890 100644 --- a/src/server/rest.rs +++ b/src/server/rest.rs @@ -34,6 +34,7 @@ use proxmox::http_err; use super::environment::RestEnvironment; use super::formatter::*; use super::ApiConfig; +use super::auth::{check_auth, extract_auth_data}; use crate::api2::types::{Authid, Userid}; use crate::auth_helpers::*; @@ -588,101 +589,6 @@ fn extract_lang_header(headers: &http::HeaderMap) -> Option { None } -struct UserAuthData { - ticket: String, - csrf_token: Option, -} - -enum AuthData { - User(UserAuthData), - ApiToken(String), -} - -fn extract_auth_data(headers: &http::HeaderMap) -> Option { - if let Some(raw_cookie) = headers.get(header::COOKIE) { - if let Ok(cookie) = raw_cookie.to_str() { - if let Some(ticket) = tools::extract_cookie(cookie, "PBSAuthCookie") { - let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) { - Some(Ok(v)) => Some(v.to_owned()), - _ => None, - }; - return Some(AuthData::User(UserAuthData { ticket, csrf_token })); - } - } - } - - match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) { - Some(Ok(v)) => { - if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") { - Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned())) - } else { - None - } - } - _ => None, - } -} - -fn check_auth( - method: &hyper::Method, - auth_data: &AuthData, - user_info: &CachedUserInfo, -) -> Result { - match auth_data { - AuthData::User(user_auth_data) => { - let ticket = user_auth_data.ticket.clone(); - let ticket_lifetime = tools::ticket::TICKET_LIFETIME; - - let userid: Userid = Ticket::::parse(&ticket)? - .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)? - .require_full()?; - - let auth_id = Authid::from(userid.clone()); - if !user_info.is_active_auth_id(&auth_id) { - bail!("user account disabled or expired."); - } - - if method != hyper::Method::GET { - if let Some(csrf_token) = &user_auth_data.csrf_token { - verify_csrf_prevention_token( - csrf_secret(), - &userid, - &csrf_token, - -300, - ticket_lifetime, - )?; - } else { - bail!("missing CSRF prevention token"); - } - } - - Ok(auth_id) - } - AuthData::ApiToken(api_token) => { - let mut parts = api_token.splitn(2, ':'); - let tokenid = parts - .next() - .ok_or_else(|| format_err!("failed to split API token header"))?; - let tokenid: Authid = tokenid.parse()?; - - if !user_info.is_active_auth_id(&tokenid) { - bail!("user account or token disabled or expired."); - } - - let tokensecret = parts - .next() - .ok_or_else(|| format_err!("failed to split API token header"))?; - let tokensecret = percent_decode_str(tokensecret) - .decode_utf8() - .map_err(|_| format_err!("failed to decode API token header"))?; - - crate::config::token_shadow::verify_secret(&tokenid, &tokensecret)?; - - Ok(tokenid) - } - } -} - async fn handle_request( api: Arc, req: Request,