refactor(router): infer ip address for online mandates from request headers if absent (#1419)

This commit is contained in:
ItsMeShashank
2023-06-13 15:17:46 +05:30
committed by GitHub
parent b002c97c9c
commit a1a009d796
6 changed files with 68 additions and 42 deletions

View File

@ -432,7 +432,7 @@ pub enum AcceptanceType {
pub struct OnlineMandate {
/// Ip address of the customer machine from which the mandate was created
#[schema(value_type = String, example = "123.32.25.123")]
pub ip_address: Secret<String, pii::IpAddress>,
pub ip_address: Option<Secret<String, pii::IpAddress>>,
/// The user-agent of the customer's browser
pub user_agent: String,
}

View File

@ -615,7 +615,7 @@ impl ForeignTryFrom<(Option<MandateOption>, Option<String>)> for Option<payments
acceptance_type: payments::AcceptanceType::Online,
accepted_at: mandate.accepted_at,
online: Some(payments::OnlineMandate {
ip_address: mandate.ip_address.unwrap_or_default(),
ip_address: mandate.ip_address,
user_agent: mandate.user_agent.unwrap_or_default(),
}),
}),

View File

@ -1254,22 +1254,31 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
_ => payment_data,
};
let setup_mandate_details =
item.request
.setup_mandate_details
.as_ref()
.and_then(|mandate_details| {
mandate_details
.customer_acceptance
.as_ref()?
.online
.as_ref()
.map(|online_details| StripeMandateRequest {
let setup_mandate_details = item
.request
.setup_mandate_details
.as_ref()
.and_then(|mandate_details| {
mandate_details
.customer_acceptance
.as_ref()?
.online
.as_ref()
.map(|online_details| {
Ok::<_, error_stack::Report<errors::ConnectorError>>(StripeMandateRequest {
mandate_type: StripeMandateType::Online,
ip_address: online_details.ip_address.to_owned(),
ip_address: online_details
.ip_address
.clone()
.get_required_value("ip_address")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "ip_address",
})?,
user_agent: online_details.user_agent.to_owned(),
})
});
})
})
.transpose()?;
Ok(Self {
amount: item.request.amount, //hopefully we don't loose some cents here

View File

@ -3,13 +3,13 @@ use error_stack::ResultExt;
use crate::{
core::errors::{self, RouterResult},
headers, logger,
types::{self, api::payments as payment_types},
types::{self, api},
utils::{Encode, ValueExt},
};
pub fn populate_ip_into_browser_info(
req: &actix_web::HttpRequest,
payload: &mut payment_types::PaymentsRequest,
payload: &mut api::PaymentsRequest,
) -> RouterResult<()> {
let mut browser_info: types::BrowserInformation = payload
.browser_info
@ -32,29 +32,46 @@ pub fn populate_ip_into_browser_info(
ip_address: None,
});
browser_info.ip_address = browser_info
.ip_address
.or_else(|| {
// Parse the IP Address from the "X-Forwarded-For" header
// This header will contain multiple IP addresses for each ALB hop which has
// a comma separated list of IP addresses: 'X.X.X.X, Y.Y.Y.Y, Z.Z.Z.Z'
// The first one here will be the client IP which we want to retrieve
req.headers()
.get(headers::X_FORWARDED_FOR)
.map(|val| val.to_str())
.transpose()
.unwrap_or_else(|e| {
logger::error!(error=?e, message="failed to retrieve ip address from X-Forwarded-For header");
None
})
.and_then(|ips| ips.split(',').next())
.map(|ip| ip.parse())
.transpose()
.unwrap_or_else(|e| {
logger::error!(error=?e, message="failed to parse ip address from X-Forwarded-For");
None
})
});
// Parse the IP Address from the "X-Forwarded-For" header
// This header will contain multiple IP addresses for each ALB hop which has
// a comma separated list of IP addresses: 'X.X.X.X, Y.Y.Y.Y, Z.Z.Z.Z'
// The first one here will be the client IP which we want to retrieve
let ip_address_from_header = req.headers()
.get(headers::X_FORWARDED_FOR)
.map(|val| val.to_str())
.transpose()
.unwrap_or_else(|e| {
logger::error!(error=?e, message="failed to retrieve ip address from X-Forwarded-For header");
None
})
.and_then(|ips| ips.split(',').next());
browser_info.ip_address = browser_info.ip_address.or_else(|| {
ip_address_from_header
.map(|ip| ip.parse())
.transpose()
.unwrap_or_else(|e| {
logger::error!(error=?e, message="failed to parse ip address from X-Forwarded-For");
None
})
});
if let Some(api::MandateData {
customer_acceptance:
Some(api::CustomerAcceptance {
online:
Some(api::OnlineMandate {
ip_address: req_ip, ..
}),
..
}),
..
}) = &mut payload.mandate_data
{
*req_ip = req_ip
.clone()
.or_else(|| ip_address_from_header.map(|ip| masking::Secret::new(ip.to_string())));
}
let encoded = Encode::<types::BrowserInformation>::encode_to_value(&browser_info)
.change_context(errors::ApiErrorResponse::InternalServerError)

View File

@ -73,7 +73,7 @@ impl MandateResponseExt for MandateResponse {
},
accepted_at: mandate.customer_accepted_at,
online: Some(api::payments::OnlineMandate {
ip_address: mandate.customer_ip_address.unwrap_or_default(),
ip_address: mandate.customer_ip_address,
user_agent: mandate.customer_user_agent.unwrap_or_default(),
}),
}),

View File

@ -43,7 +43,7 @@ impl CustomerAcceptanceExt for CustomerAcceptance {
fn get_ip_address(&self) -> Option<String> {
self.online
.as_ref()
.map(|data| data.ip_address.peek().to_owned())
.and_then(|data| data.ip_address.as_ref().map(|ip| ip.peek().to_owned()))
}
fn get_user_agent(&self) -> Option<String> {