Skip to content

Commit

Permalink
Add piping for google play payment API requests
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Jontified committed Oct 4, 2023
1 parent 53b7f1c commit 02b91ed
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 8 deletions.
70 changes: 69 additions & 1 deletion mullvad-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use futures::channel::mpsc;
use futures::Stream;
use hyper::Method;
use mullvad_types::{
account::{AccountToken, VoucherSubmission},
account::{AccountToken, PlayPurchase, PlayPurchaseInitResult, VoucherSubmission},
version::AppVersion,
};
use proxy::ApiConnectionMode;
Expand Down Expand Up @@ -63,6 +63,7 @@ pub const API_IP_CACHE_FILENAME: &str = "api-ip-address.txt";

const ACCOUNTS_URL_PREFIX: &str = "accounts/v1";
const APP_URL_PREFIX: &str = "app/v1";
const GOOGLE_PAYMENTS_URL_PREFIX: &str = "payments/google-play/v1";

pub static API: LazyManual<ApiEndpoint> = LazyManual::new(ApiEndpoint::from_env_vars);

Expand Down Expand Up @@ -457,6 +458,73 @@ impl AccountsProxy {
}
}

pub fn init_play_purchase(
&mut self,
account_token: AccountToken,
) -> impl Future<Output = Result<PlayPurchaseInitResult, rest::Error>> {
#[derive(serde::Deserialize)]
struct PlayPurchaseInitResponse {
obfuscated_id: String,
}

let service = self.handle.service.clone();
let factory = self.handle.factory.clone();
let access_proxy = self.handle.token_store.clone();

async move {
let response = rest::send_json_request(
&factory,
service,
&format!("{GOOGLE_PAYMENTS_URL_PREFIX}/init"),
Method::POST,
&(),
Some((access_proxy, account_token)),
&[StatusCode::OK],
)
.await;

let PlayPurchaseInitResponse { obfuscated_id } =
rest::deserialize_body(response?).await?;

Ok(obfuscated_id)
}
}

pub fn verify_play_purchase(
&mut self,
account_token: AccountToken,
play_purchase: PlayPurchase,
) -> impl Future<Output = Result<(), rest::Error>> {
#[derive(serde::Serialize)]
struct PlayPurchaseSubmission {
product_id: String,
purchase_token: String,
}

let submission = PlayPurchaseSubmission {
product_id: play_purchase.product_id,
purchase_token: play_purchase.purchase_token,
};

let service = self.handle.service.clone();
let factory = self.handle.factory.clone();
let access_proxy = self.handle.token_store.clone();

async move {
rest::send_json_request(
&factory,
service,
&format!("{GOOGLE_PAYMENTS_URL_PREFIX}/acknowledge"),
Method::POST,
&submission,
Some((access_proxy, account_token)),
&[StatusCode::ACCEPTED],
)
.await?;
Ok(())
}
}

pub fn get_www_auth_token(
&self,
account: AccountToken,
Expand Down
55 changes: 54 additions & 1 deletion mullvad-daemon/src/device/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{PlayPurchaseInitResult, VoucherSubmission},
device::Device,
wireguard::WireguardData,
};

use super::{Error, PrivateAccountAndDevice, ResponseTx};

Expand Down Expand Up @@ -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<PlayPurchaseInitResult>,
tx: ResponseTx<PlayPurchaseInitResult>,
) {
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,
Expand Down Expand Up @@ -109,6 +132,11 @@ enum Call {
ApiCall<VoucherSubmission>,
Option<ResponseTx<VoucherSubmission>>,
),
InitPlayPurchase(
ApiCall<PlayPurchaseInitResult>,
Option<ResponseTx<PlayPurchaseInitResult>>,
),
VerifyPlayPurchase(ApiCall<()>, Option<ResponseTx<()>>),
ExpiryCheck(ApiCall<DateTime<Utc>>),
}

Expand Down Expand Up @@ -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),
}
}
Expand All @@ -155,5 +203,10 @@ pub(crate) enum ApiResult {
Result<VoucherSubmission, Error>,
ResponseTx<VoucherSubmission>,
),
InitPlayPurchase(
Result<PlayPurchaseInitResult, Error>,
ResponseTx<PlayPurchaseInitResult>,
),
VerifyPlayPurchase(Result<(), Error>, ResponseTx<()>),
ExpiryCheck(Result<DateTime<Utc>, Error>),
}
123 changes: 122 additions & 1 deletion mullvad-daemon/src/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use futures::{

use mullvad_api::rest;
use mullvad_types::{
account::{AccountToken, VoucherSubmission},
account::{AccountToken, PlayPurchase, PlayPurchaseInitResult, VoucherSubmission},
device::{
AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DeviceState,
},
Expand Down Expand Up @@ -305,6 +305,8 @@ enum AccountManagerCommand {
SetRotationInterval(RotationInterval, ResponseTx<()>),
ValidateDevice(ResponseTx<()>),
SubmitVoucher(String, ResponseTx<VoucherSubmission>),
InitPlayPurchase(ResponseTx<PlayPurchaseInitResult>),
VerifyPlayPurchase(ResponseTx<()>, PlayPurchase),
CheckExpiry(ResponseTx<DateTime<Utc>>),
Shutdown(oneshot::Sender<()>),
}
Expand Down Expand Up @@ -359,6 +361,16 @@ impl AccountManagerHandle {
.await
}

pub async fn init_play_purchase(&self) -> Result<PlayPurchaseInitResult, 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
}
Expand Down Expand Up @@ -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);
},
Expand Down Expand Up @@ -589,6 +607,65 @@ impl AccountManager {
}
}

fn handle_init_play_purchase(
&mut self,
tx: ResponseTx<PlayPurchaseInitResult>,
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>>,
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -665,6 +750,42 @@ impl AccountManager {
let _ = tx.send(response);
}

async fn consume_init_play_purchase_result(
&mut self,
response: Result<PlayPurchaseInitResult, Error>,
tx: ResponseTx<PlayPurchaseInitResult>,
) {
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) => {
Expand Down
Loading

0 comments on commit 02b91ed

Please sign in to comment.