From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <pdm-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id C0C511FF168 for <inbox@lore.proxmox.com>; Tue, 4 Mar 2025 13:05:57 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C25CB1DFF5; Tue, 4 Mar 2025 13:05:53 +0100 (CET) From: Shannon Sterz <s.sterz@proxmox.com> To: pdm-devel@lists.proxmox.com Date: Tue, 4 Mar 2025 13:05:01 +0100 Message-Id: <20250304120506.135617-17-s.sterz@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250304120506.135617-1-s.sterz@proxmox.com> References: <20250304120506.135617-1-s.sterz@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.018 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH proxmox v4 16/21] client: add compatibility with HttpOnly cookies X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion <pdm-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pdm-devel>, <mailto:pdm-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/pdm-devel/> List-Post: <mailto:pdm-devel@lists.proxmox.com> List-Help: <mailto:pdm-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel>, <mailto:pdm-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Proxmox Datacenter Manager development discussion <pdm-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" <pdm-devel-bounces@lists.proxmox.com> this should make it possible to use the proxmox-client crate outside of context where HttpOnly cookies are handled for us. if a cookie name is provided to a client, it tries to find a corresponding `Set-Cookie` header in the login response and passes tries to parse it as a ticket. that ticket is then passed on to proxmox-login like any regular ticket. Signed-off-by: Shannon Sterz <s.sterz@proxmox.com> --- proxmox-client/src/client.rs | 69 +++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/proxmox-client/src/client.rs b/proxmox-client/src/client.rs index 9b078a98..07a53873 100644 --- a/proxmox-client/src/client.rs +++ b/proxmox-client/src/client.rs @@ -12,6 +12,7 @@ use hyper::body::{Body, HttpBody}; use openssl::hash::MessageDigest; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; use openssl::x509::{self, X509}; +use proxmox_login::Ticket; use serde::Serialize; use proxmox_login::ticket::Validity; @@ -67,6 +68,7 @@ pub struct Client { auth: Mutex<Option<Arc<AuthenticationKind>>>, client: Arc<proxmox_http::client::Client>, pve_compat: bool, + cookie_name: Option<String>, } impl Client { @@ -75,6 +77,13 @@ impl Client { Client::with_client(api_url, Arc::new(proxmox_http::client::Client::new())) } + pub fn new_with_cookie(api_url: Uri, cookie_name: &str) -> Self { + let mut client = + Client::with_client(api_url, Arc::new(proxmox_http::client::Client::new())); + client.set_cookie_name(cookie_name); + client + } + /// Instantiate a client for an API with a given HTTP client instance. pub fn with_client(api_url: Uri, client: Arc<proxmox_http::client::Client>) -> Self { Self { @@ -82,6 +91,7 @@ impl Client { auth: Mutex::new(None), client, pve_compat: false, + cookie_name: None, } } @@ -174,6 +184,10 @@ impl Client { self.pve_compat = compatibility; } + pub fn set_cookie_name(&mut self, cookie_name: &str) { + self.cookie_name = Some(cookie_name.to_string()); + } + /// Get the currently used API url. pub fn api_url(&self) -> &Uri { &self.api_url @@ -309,7 +323,10 @@ impl Client { Ok(()) } - async fn do_login_request(&self, request: proxmox_login::Request) -> Result<Vec<u8>, Error> { + async fn do_login_request( + &self, + request: proxmox_login::Request, + ) -> Result<(Option<Ticket>, Vec<u8>), Error> { let request = http::Request::builder() .method(Method::POST) .uri(request.url) @@ -330,10 +347,26 @@ impl Client { return Err(Error::api(api_response.status(), "authentication failed")); } - let (_, body) = api_response.into_parts(); + let (parts, body) = api_response.into_parts(); let body = read_body(body).await?; - Ok(body) + let ticket: Option<Ticket> = self.cookie_name.as_ref().and_then(|cookie_name| { + parts + .headers + .get_all(http::header::SET_COOKIE) + .iter() + .filter_map(|c| c.to_str().ok()) + .filter_map(|c| match (c.find('='), c.find(';')) { + (Some(begin), Some(end)) if begin < end && &c[..begin] == cookie_name => { + Some(&c[begin + 1..end]) + } + _ => None, + }) + .filter_map(|t| t.parse().ok()) + .next() + }); + + Ok((ticket, body)) } /// Attempt to refresh the current ticket. @@ -349,10 +382,10 @@ impl Client { let login = Login::renew(self.api_url.to_string(), auth.ticket.to_string()) .map_err(Error::Ticket)?; - let api_response = self.do_login_request(login.request()).await?; + let (ticket, api_response) = self.do_login_request(login.request()).await?; - match login.response(&api_response)? { - TicketResult::Full(auth) => { + match login.response_with_cookie_ticket(ticket, &api_response)? { + TicketResult::Full(auth) | TicketResult::HttpOnly(auth) => { *self.auth.lock().unwrap() = Some(Arc::new(auth.into())); Ok(()) } @@ -373,15 +406,17 @@ impl Client { pub async fn login(&self, login: Login) -> Result<Option<SecondFactorChallenge>, Error> { let login = login.pve_compatibility(self.pve_compat); - let api_response = self.do_login_request(login.request()).await?; - - Ok(match login.response(&api_response)? { - TicketResult::TfaRequired(challenge) => Some(challenge), - TicketResult::Full(auth) => { - *self.auth.lock().unwrap() = Some(Arc::new(auth.into())); - None - } - }) + let (ticket, api_response) = self.do_login_request(login.request()).await?; + + Ok( + match login.response_with_cookie_ticket(ticket, &api_response)? { + TicketResult::TfaRequired(challenge) => Some(challenge), + TicketResult::Full(auth) | TicketResult::HttpOnly(auth) => { + *self.auth.lock().unwrap() = Some(Arc::new(auth.into())); + None + } + }, + ) } /// Attempt to finish a 2nd factor login. @@ -393,9 +428,9 @@ impl Client { challenge: SecondFactorChallenge, challenge_response: proxmox_login::Request, ) -> Result<(), Error> { - let api_response = self.do_login_request(challenge_response).await?; + let (ticket, api_response) = self.do_login_request(challenge_response).await?; - let auth = challenge.response(&api_response)?; + let auth = challenge.response_with_cookie_ticket(ticket, &api_response)?; *self.auth.lock().unwrap() = Some(Arc::new(auth.into())); Ok(()) } -- 2.39.5 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel