summaryrefslogtreecommitdiffhomepage
path: root/mullvad-daemon/src
diff options
context:
space:
mode:
authorJonathan <jonathan@mullvad.net>2023-10-03 18:06:55 +0200
committerJonathan <jonathan@mullvad.net>2023-10-16 18:34:07 +0200
commit9204e1c66e710d22ebf7b55ab6ec045cc53bdad9 (patch)
tree83745d24e9e09a81f6b9cb7884f04ba05a39e371 /mullvad-daemon/src
parentd32b6f81ceb8c383588678e8fbabe613fbac4142 (diff)
downloadmullvadvpn-9204e1c66e710d22ebf7b55ab6ec045cc53bdad9.tar.xz
mullvadvpn-9204e1c66e710d22ebf7b55ab6ec045cc53bdad9.zip
Add piping for google play payment API requests
This commit adds all of the basic piping in order to let Android use the JNI interface in order to make requests to our API pertaining to google play payment initialization and status.
Diffstat (limited to 'mullvad-daemon/src')
-rw-r--r--mullvad-daemon/src/device/api.rs55
-rw-r--r--mullvad-daemon/src/device/mod.rs123
-rw-r--r--mullvad-daemon/src/device/service.rs41
-rw-r--r--mullvad-daemon/src/lib.rs52
4 files changed, 267 insertions, 4 deletions
diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs
index 93dae78261..07e7b35311 100644
--- a/mullvad-daemon/src/device/api.rs
+++ b/mullvad-daemon/src/device/api.rs
@@ -2,7 +2,11 @@ use std::pin::Pin;
use chrono::{DateTime, Utc};
use futures::{future::FusedFuture, Future};
-use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData};
+use mullvad_types::{
+ account::{PlayPurchasePaymentToken, VoucherSubmission},
+ device::Device,
+ wireguard::WireguardData,
+};
use super::{Error, PrivateAccountAndDevice, ResponseTx};
@@ -47,6 +51,25 @@ impl CurrentApiCall {
self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx)));
}
+ pub fn set_init_play_purchase(
+ &mut self,
+ init_play_purchase_call: ApiCall<PlayPurchasePaymentToken>,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ ) {
+ self.current_call = Some(Call::InitPlayPurchase(init_play_purchase_call, Some(tx)));
+ }
+
+ pub fn set_verify_play_purchase(
+ &mut self,
+ verify_play_purchase_call: ApiCall<()>,
+ tx: ResponseTx<()>,
+ ) {
+ self.current_call = Some(Call::VerifyPlayPurchase(
+ verify_play_purchase_call,
+ Some(tx),
+ ));
+ }
+
pub fn is_validating(&self) -> bool {
matches!(
&self.current_call,
@@ -109,6 +132,11 @@ enum Call {
ApiCall<VoucherSubmission>,
Option<ResponseTx<VoucherSubmission>>,
),
+ InitPlayPurchase(
+ ApiCall<PlayPurchasePaymentToken>,
+ Option<ResponseTx<PlayPurchasePaymentToken>>,
+ ),
+ VerifyPlayPurchase(ApiCall<()>, Option<ResponseTx<()>>),
ExpiryCheck(ApiCall<DateTime<Utc>>),
}
@@ -142,6 +170,26 @@ impl futures::Future for Call {
std::task::Poll::Pending
}
}
+ InitPlayPurchase(call, tx) => {
+ if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
+ std::task::Poll::Ready(ApiResult::InitPlayPurchase(
+ response,
+ tx.take().unwrap(),
+ ))
+ } else {
+ std::task::Poll::Pending
+ }
+ }
+ VerifyPlayPurchase(call, tx) => {
+ if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
+ std::task::Poll::Ready(ApiResult::VerifyPlayPurchase(
+ response,
+ tx.take().unwrap(),
+ ))
+ } else {
+ std::task::Poll::Pending
+ }
+ }
ExpiryCheck(call) => Pin::new(call).poll(cx).map(ApiResult::ExpiryCheck),
}
}
@@ -155,5 +203,10 @@ pub(crate) enum ApiResult {
Result<VoucherSubmission, Error>,
ResponseTx<VoucherSubmission>,
),
+ InitPlayPurchase(
+ Result<PlayPurchasePaymentToken, Error>,
+ ResponseTx<PlayPurchasePaymentToken>,
+ ),
+ VerifyPlayPurchase(Result<(), Error>, ResponseTx<()>),
ExpiryCheck(Result<DateTime<Utc>, Error>),
}
diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs
index b54638f140..528c25ce9a 100644
--- a/mullvad-daemon/src/device/mod.rs
+++ b/mullvad-daemon/src/device/mod.rs
@@ -6,7 +6,7 @@ use futures::{
use mullvad_api::rest;
use mullvad_types::{
- account::{AccountToken, VoucherSubmission},
+ account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
device::{
AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DeviceState,
},
@@ -305,6 +305,8 @@ enum AccountManagerCommand {
SetRotationInterval(RotationInterval, ResponseTx<()>),
ValidateDevice(ResponseTx<()>),
SubmitVoucher(String, ResponseTx<VoucherSubmission>),
+ InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken>),
+ VerifyPlayPurchase(ResponseTx<()>, PlayPurchase),
CheckExpiry(ResponseTx<DateTime<Utc>>),
Shutdown(oneshot::Sender<()>),
}
@@ -359,6 +361,16 @@ impl AccountManagerHandle {
.await
}
+ pub async fn init_play_purchase(&self) -> Result<PlayPurchasePaymentToken, Error> {
+ self.send_command(AccountManagerCommand::InitPlayPurchase)
+ .await
+ }
+
+ pub async fn verify_play_purchase(&self, play_purchase: PlayPurchase) -> Result<(), Error> {
+ self.send_command(move |tx| AccountManagerCommand::VerifyPlayPurchase(tx, play_purchase))
+ .await
+ }
+
pub async fn check_expiry(&self) -> Result<DateTime<Utc>, Error> {
self.send_command(AccountManagerCommand::CheckExpiry).await
}
@@ -514,6 +526,12 @@ impl AccountManager {
Some(AccountManagerCommand::SubmitVoucher(voucher, tx)) => {
self.handle_voucher_submission(tx, voucher, &mut current_api_call);
},
+ Some(AccountManagerCommand::InitPlayPurchase(tx)) => {
+ self.handle_init_play_purchase(tx, &mut current_api_call);
+ },
+ Some(AccountManagerCommand::VerifyPlayPurchase(tx, play_purchase)) => {
+ self.handle_verify_play_purchase(tx, play_purchase, &mut current_api_call);
+ },
Some(AccountManagerCommand::CheckExpiry(tx)) => {
self.handle_expiry_request(tx, &mut current_api_call);
},
@@ -589,6 +607,65 @@ impl AccountManager {
}
}
+ fn handle_init_play_purchase(
+ &mut self,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+
+ let init_play_purchase_api_call = move || {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move { account_service.init_play_purchase(account_token).await })
+ };
+
+ match init_play_purchase_api_call() {
+ Ok(call) => {
+ current_api_call.set_init_play_purchase(Box::pin(call), tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
+ fn handle_verify_play_purchase(
+ &mut self,
+ tx: ResponseTx<()>,
+ play_purchase: PlayPurchase,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+
+ let play_purchase_verify_api_call = move || {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move {
+ account_service
+ .verify_play_purchase(account_token, play_purchase)
+ .await
+ })
+ };
+
+ match play_purchase_verify_api_call() {
+ Ok(call) => {
+ current_api_call.set_verify_play_purchase(Box::pin(call), tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
fn handle_expiry_request(
&mut self,
tx: ResponseTx<DateTime<Utc>>,
@@ -627,6 +704,14 @@ impl AccountManager {
VoucherSubmission(data_response, tx) => {
self.consume_voucher_result(data_response, tx).await
}
+ InitPlayPurchase(data_response, tx) => {
+ self.consume_init_play_purchase_result(data_response, tx)
+ .await
+ }
+ VerifyPlayPurchase(data_response, tx) => {
+ self.consume_verify_play_purchase_result(data_response, tx)
+ .await
+ }
ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await,
}
}
@@ -665,6 +750,42 @@ impl AccountManager {
let _ = tx.send(response);
}
+ async fn consume_init_play_purchase_result(
+ &mut self,
+ response: Result<PlayPurchasePaymentToken, Error>,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ ) {
+ match &response {
+ Ok(_) => (),
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => log::error!("Failed to initialize play purchase: {}", err),
+ }
+ let _ = tx.send(response);
+ }
+
+ async fn consume_verify_play_purchase_result(
+ &mut self,
+ response: Result<(), Error>,
+ tx: ResponseTx<()>,
+ ) {
+ match &response {
+ Ok(_) => (),
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => log::error!("Failed to verify play purchase: {}", err),
+ }
+ let _ = tx.send(response);
+ }
+
async fn consume_expiry_result(&mut self, response: Result<DateTime<Utc>, Error>) {
match response {
Ok(expiry) => {
diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs
index 9c967e4413..85d9d56d84 100644
--- a/mullvad-daemon/src/device/service.rs
+++ b/mullvad-daemon/src/device/service.rs
@@ -3,7 +3,7 @@ use std::{future::Future, time::Duration};
use chrono::{DateTime, Utc};
use futures::future::{abortable, AbortHandle};
use mullvad_types::{
- account::{AccountToken, VoucherSubmission},
+ account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
device::{Device, DeviceId},
wireguard::WireguardData,
};
@@ -320,6 +320,45 @@ impl AccountService {
}
result.map_err(map_rest_error)
}
+
+ pub async fn init_play_purchase(
+ &self,
+ account_token: AccountToken,
+ ) -> Result<PlayPurchasePaymentToken, Error> {
+ let mut proxy = self.proxy.clone();
+ let api_handle = self.api_availability.clone();
+ let result = retry_future(
+ move || proxy.init_play_purchase(account_token.clone()),
+ move |result| should_retry(result, &api_handle),
+ RETRY_ACTION_STRATEGY,
+ )
+ .await;
+ if result.is_ok() {
+ self.initial_check_abort_handle.abort();
+ self.api_availability.resume_background();
+ }
+ result.map_err(map_rest_error)
+ }
+
+ pub async fn verify_play_purchase(
+ &self,
+ account_token: AccountToken,
+ play_purchase: PlayPurchase,
+ ) -> Result<(), Error> {
+ let mut proxy = self.proxy.clone();
+ let api_handle = self.api_availability.clone();
+ let result = retry_future(
+ move || proxy.verify_play_purchase(account_token.clone(), play_purchase.clone()),
+ move |result| should_retry(result, &api_handle),
+ RETRY_ACTION_STRATEGY,
+ )
+ .await;
+ if result.is_ok() {
+ self.initial_check_abort_handle.abort();
+ self.api_availability.resume_background();
+ }
+ result.map_err(map_rest_error)
+ }
}
pub fn spawn_account_service(
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index f7343acc87..ceb50ca2b2 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -40,7 +40,7 @@ use mullvad_relay_selector::{
};
use mullvad_types::{
access_method::{AccessMethod, AccessMethodSetting},
- account::{AccountData, AccountToken, VoucherSubmission},
+ account::{AccountData, AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
@@ -178,6 +178,16 @@ pub enum Error {
#[cfg(target_os = "macos")]
#[error(display = "Failed to set exclusion group")]
GroupIdError(#[error(source)] io::Error),
+
+ // TODO
+ //#[cfg(target_os = "android")]
+ #[error(display = "Failed to initialize play purchase")]
+ InitPlayPurchase(#[error(source)] device::Error),
+
+ // TODO
+ //#[cfg(target_os = "android")]
+ #[error(display = "Failed to verify play purchase")]
+ VerifyPlayPurchase(#[error(source)] device::Error),
}
/// Enum representing commands that can be sent to the daemon.
@@ -327,6 +337,14 @@ pub enum DaemonCommand {
/// to bypass the tunnel in blocking states.
#[cfg(target_os = "android")]
BypassSocket(RawFd, oneshot::Sender<()>),
+ /// Initialize a google play purchase through the API.
+ ///TODO
+ //#[cfg(target_os = "android")]
+ InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken, Error>),
+ /// Verify that a google play payment was successful through the API.
+ ///TODO
+ //#[cfg(target_os = "android")]
+ VerifyPlayPurchase(ResponseTx<(), Error>, PlayPurchase),
}
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -1027,6 +1045,10 @@ where
GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token),
GetWwwAuthToken(tx) => self.on_get_www_auth_token(tx).await,
SubmitVoucher(tx, voucher) => self.on_submit_voucher(tx, voucher),
+ InitPlayPurchase(tx) => self.on_init_play_purchase(tx),
+ VerifyPlayPurchase(tx, play_purchase) => {
+ self.on_verify_play_purchase(tx, play_purchase)
+ }
GetRelayLocations(tx) => self.on_get_relay_locations(tx),
UpdateRelayLocations => self.on_update_relay_locations().await,
LoginAccount(tx, account_token) => self.on_login_account(tx, account_token),
@@ -1404,6 +1426,34 @@ where
});
}
+ fn on_init_play_purchase(&mut self, tx: ResponseTx<PlayPurchasePaymentToken, Error>) {
+ let manager = self.account_manager.clone();
+ tokio::spawn(async move {
+ Self::oneshot_send(
+ tx,
+ manager
+ .init_play_purchase()
+ .await
+ .map_err(Error::InitPlayPurchase),
+ "init_play_purchase response",
+ );
+ });
+ }
+
+ fn on_verify_play_purchase(&mut self, tx: ResponseTx<(), Error>, play_purchase: PlayPurchase) {
+ let manager = self.account_manager.clone();
+ tokio::spawn(async move {
+ Self::oneshot_send(
+ tx,
+ manager
+ .verify_play_purchase(play_purchase)
+ .await
+ .map_err(Error::VerifyPlayPurchase),
+ "verify_play_purchase response",
+ );
+ });
+ }
+
fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) {
Self::oneshot_send(tx, self.relay_selector.get_locations(), "relay locations");
}