mirror of
https://git.proxmox.com/git/proxmox
synced 2025-07-27 00:01:45 +00:00
login: add helpers to pass cookie values when parsing login responses
depending on the context a client may or may not have access to HttpOnly cookies. this change allows them to pass such values to `proxmox-login` to take them into account when parsing login responses. Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
This commit is contained in:
parent
f137b5e528
commit
b28e98ca99
@ -162,10 +162,27 @@ impl Login {
|
|||||||
&self,
|
&self,
|
||||||
body: &T,
|
body: &T,
|
||||||
) -> Result<TicketResult, ResponseError> {
|
) -> Result<TicketResult, ResponseError> {
|
||||||
self.response_bytes(body.as_ref())
|
self.response_bytes(None, body.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_bytes(&self, body: &[u8]) -> Result<TicketResult, ResponseError> {
|
/// Parse the result body of a [`CreateTicket`](api::CreateTicket) API request taking into
|
||||||
|
/// account potential tickets obtained via a `Set-Cookie` header.
|
||||||
|
///
|
||||||
|
/// On success, this will either yield an [`Authentication`] or a [`SecondFactorChallenge`] if
|
||||||
|
/// Two-Factor-Authentication is required.
|
||||||
|
pub fn response_with_cookie_ticket<T: ?Sized + AsRef<[u8]>>(
|
||||||
|
&self,
|
||||||
|
cookie_ticket: Option<Ticket>,
|
||||||
|
body: &T,
|
||||||
|
) -> Result<TicketResult, ResponseError> {
|
||||||
|
self.response_bytes(cookie_ticket, body.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_bytes(
|
||||||
|
&self,
|
||||||
|
cookie_ticket: Option<Ticket>,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<TicketResult, ResponseError> {
|
||||||
use ticket::TicketResponse;
|
use ticket::TicketResponse;
|
||||||
|
|
||||||
let response: api::ApiResponse<api::CreateTicketResponse> = serde_json::from_slice(body)?;
|
let response: api::ApiResponse<api::CreateTicketResponse> = serde_json::from_slice(body)?;
|
||||||
@ -175,6 +192,14 @@ impl Login {
|
|||||||
return Err("ticket response contained unexpected userid".into());
|
return Err("ticket response contained unexpected userid".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if a ticket was provided via a cookie, use it like a normal ticket
|
||||||
|
if let Some(ticket) = cookie_ticket {
|
||||||
|
check_ticket_userid(ticket.userid(), &self.userid)?;
|
||||||
|
return Ok(TicketResult::Full(
|
||||||
|
self.authentication_for(ticket, response)?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let ticket: TicketResponse = match response.ticket {
|
let ticket: TicketResponse = match response.ticket {
|
||||||
Some(ticket) => ticket.parse()?,
|
Some(ticket) => ticket.parse()?,
|
||||||
None => return Err("missing ticket".into()),
|
None => return Err("missing ticket".into()),
|
||||||
@ -183,15 +208,7 @@ impl Login {
|
|||||||
Ok(match ticket {
|
Ok(match ticket {
|
||||||
TicketResponse::Full(ticket) => {
|
TicketResponse::Full(ticket) => {
|
||||||
check_ticket_userid(ticket.userid(), &self.userid)?;
|
check_ticket_userid(ticket.userid(), &self.userid)?;
|
||||||
TicketResult::Full(Authentication {
|
TicketResult::Full(self.authentication_for(ticket, response)?)
|
||||||
csrfprevention_token: response
|
|
||||||
.csrfprevention_token
|
|
||||||
.ok_or("missing CSRFPreventionToken in ticket response")?,
|
|
||||||
clustername: response.clustername,
|
|
||||||
api_url: self.api_url.clone(),
|
|
||||||
userid: response.username,
|
|
||||||
ticket,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TicketResponse::Tfa(ticket, challenge) => {
|
TicketResponse::Tfa(ticket, challenge) => {
|
||||||
@ -205,6 +222,22 @@ impl Login {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authentication_for(
|
||||||
|
&self,
|
||||||
|
ticket: Ticket,
|
||||||
|
response: api::CreateTicketResponse,
|
||||||
|
) -> Result<Authentication, ResponseError> {
|
||||||
|
Ok(Authentication {
|
||||||
|
csrfprevention_token: response
|
||||||
|
.csrfprevention_token
|
||||||
|
.ok_or("missing CSRFPreventionToken in ticket response")?,
|
||||||
|
clustername: response.clustername,
|
||||||
|
api_url: self.api_url.clone(),
|
||||||
|
userid: response.username,
|
||||||
|
ticket,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the result of a ticket call. It will either yield a final ticket, or a TFA challenge.
|
/// This is the result of a ticket call. It will either yield a final ticket, or a TFA challenge.
|
||||||
@ -310,10 +343,24 @@ impl SecondFactorChallenge {
|
|||||||
&self,
|
&self,
|
||||||
body: &T,
|
body: &T,
|
||||||
) -> Result<Authentication, ResponseError> {
|
) -> Result<Authentication, ResponseError> {
|
||||||
self.response_bytes(body.as_ref())
|
self.response_bytes(None, body.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_bytes(&self, body: &[u8]) -> Result<Authentication, ResponseError> {
|
/// Deal with the API's response object to extract the ticket either from a cookie or the
|
||||||
|
/// response itself.
|
||||||
|
pub fn response_with_cookie_ticket<T: ?Sized + AsRef<[u8]>>(
|
||||||
|
&self,
|
||||||
|
cookie_ticket: Option<Ticket>,
|
||||||
|
body: &T,
|
||||||
|
) -> Result<Authentication, ResponseError> {
|
||||||
|
self.response_bytes(cookie_ticket, body.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_bytes(
|
||||||
|
&self,
|
||||||
|
cookie_ticket: Option<Ticket>,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<Authentication, ResponseError> {
|
||||||
let response: api::ApiResponse<api::CreateTicketResponse> = serde_json::from_slice(body)?;
|
let response: api::ApiResponse<api::CreateTicketResponse> = serde_json::from_slice(body)?;
|
||||||
let response = response.data.ok_or("missing response data")?;
|
let response = response.data.ok_or("missing response data")?;
|
||||||
|
|
||||||
@ -321,7 +368,21 @@ impl SecondFactorChallenge {
|
|||||||
return Err("ticket response contained unexpected userid".into());
|
return Err("ticket response contained unexpected userid".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let ticket: Ticket = response.ticket.ok_or("no ticket in response")?.parse()?;
|
// get the ticket from:
|
||||||
|
// 1. the cookie if possible -> new HttpOnly authentication outside of the browser
|
||||||
|
// 2. just the `ticket_info` -> new HttpOnly authentication inside a browser context or
|
||||||
|
// similar, assume the ticket is handle by that
|
||||||
|
// 3. the `ticket` field -> old authentication flow where we handle the ticket ourselves
|
||||||
|
let ticket: Ticket = cookie_ticket
|
||||||
|
.ok_or(ResponseError::from("no ticket in response"))
|
||||||
|
.or_else(|e| {
|
||||||
|
response
|
||||||
|
.ticket_info
|
||||||
|
.or(response.ticket)
|
||||||
|
.ok_or(e)
|
||||||
|
.and_then(|t| t.parse().map_err(|e: TicketError| e.into()))
|
||||||
|
})?;
|
||||||
|
|
||||||
check_ticket_userid(ticket.userid(), &self.userid)?;
|
check_ticket_userid(ticket.userid(), &self.userid)?;
|
||||||
|
|
||||||
Ok(Authentication {
|
Ok(Authentication {
|
||||||
|
Loading…
Reference in New Issue
Block a user