From 2754c29929f637320a4ad63c8f8638a81343e011 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 22 Apr 2024 10:35:35 +0200 Subject: [PATCH] Expose play purchase API via gRPC --- mullvad-daemon/src/management_interface.rs | 54 +++++++++++++++++++ mullvad-jni/src/daemon_interface.rs | 2 +- mullvad-jni/src/lib.rs | 20 ++++--- .../proto/management_interface.proto | 11 ++++ .../src/types/conversions/account.rs | 29 ++++++++++ 5 files changed, 107 insertions(+), 9 deletions(-) diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 85a51fa34742..8628ec4eb864 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -949,6 +949,60 @@ impl ManagementService for ManagementServiceImpl { let blob = self.wait_for_result(rx).await??; Ok(Response::new(blob)) } + + #[cfg(target_os = "android")] + async fn init_play_purchase( + &self, + _request: Request<()>, + ) -> ServiceResult { + log::debug!("init_play_purchase"); + + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::InitPlayPurchase(tx))?; + + let payment_token = self + .wait_for_result(rx) + .await? + .map(types::PlayPurchasePaymentToken::from) + .map_err(map_daemon_error)?; + + Ok(Response::new(payment_token)) + } + + /// On non-Android platforms, the return value will be useless. + #[cfg(not(target_os = "android"))] + async fn init_play_purchase( + &self, + _: Request<()>, + ) -> ServiceResult { + log::error!("Called `init_play_purchase` on non-Android platform"); + Ok(Response::new(types::PlayPurchasePaymentToken { + token: String::default(), + })) + } + + #[cfg(target_os = "android")] + async fn verify_play_purchase( + &self, + request: Request, + ) -> ServiceResult<()> { + log::debug!("verify_play_purchase"); + + let (tx, rx) = oneshot::channel(); + let play_purchase = mullvad_types::account::PlayPurchase::try_from(request.into_inner())?; + + self.send_command_to_daemon(DaemonCommand::VerifyPlayPurchase(tx, play_purchase))?; + + self.wait_for_result(rx).await?.map_err(map_daemon_error)?; + + Ok(Response::new(())) + } + + #[cfg(not(target_os = "android"))] + async fn verify_play_purchase(&self, _: Request) -> ServiceResult<()> { + log::error!("Called `verify_play_purchase` on non-Android platform"); + Ok(Response::new(())) + } } impl ManagementServiceImpl { diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs index 97b303a1903f..9ac0e8dc48ed 100644 --- a/mullvad-jni/src/daemon_interface.rs +++ b/mullvad-jni/src/daemon_interface.rs @@ -1,7 +1,7 @@ use futures::{channel::oneshot, executor::block_on}; use mullvad_daemon::{device, DaemonCommand, DaemonCommandSender}; use mullvad_types::{ - account::{AccountData, AccountToken, PlayPurchase, VoucherSubmission}, + account::{AccountData, AccountToken, VoucherSubmission}, custom_list::CustomList, device::{Device, DeviceState}, relay_constraints::{ObfuscationSettings, RelaySettings}, diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs index ccd1924ab29d..949a4c965034 100644 --- a/mullvad-jni/src/lib.rs +++ b/mullvad-jni/src/lib.rs @@ -6,7 +6,7 @@ mod is_null; mod problem_report; mod talpid_vpn_service; -use crate::{daemon_interface::DaemonInterface}; +use crate::daemon_interface::DaemonInterface; use jnix::{ jni::{ objects::{GlobalRef, JObject, JString, JValue}, @@ -18,11 +18,11 @@ use jnix::{ }; use mullvad_api::{rest::Error as RestError, StatusCode}; use mullvad_daemon::{ - cleanup_old_rpc_socket, device, exception_logging, logging, runtime::new_multi_thread, version, Daemon, - DaemonCommandChannel, + cleanup_old_rpc_socket, device, exception_logging, logging, runtime::new_multi_thread, version, + Daemon, DaemonCommandChannel, }; use mullvad_types::{ - account::{AccountData, PlayPurchase, VoucherSubmission}, + account::{AccountData, VoucherSubmission}, custom_list::CustomList, settings::DnsOptions, }; @@ -276,7 +276,7 @@ fn spawn_daemon( command_channel: DaemonCommandChannel, android_context: AndroidContext, ) -> Result<(), Error> { -// let listener = JniEventListener::spawn(env, this).map_err(Error::SpawnJniEventListener)?; + // let listener = JniEventListener::spawn(env, this).map_err(Error::SpawnJniEventListener)?; let daemon_object = env .new_global_ref(*this) .map_err(Error::CreateGlobalReference)?; @@ -307,7 +307,9 @@ fn spawn_daemon( runtime.block_on(cleanup_old_rpc_socket()); - let event_listener = match runtime.block_on(async {spawn_management_interface(command_channel.sender()) }) { + let event_listener = match runtime + .block_on(async { spawn_management_interface(command_channel.sender()) }) + { Ok(event_listener) => event_listener, Err(error) => { let _ = tx.send(Err(error)); @@ -356,7 +358,10 @@ fn spawn_management_interface( ) -> Result { let (socket_path, event_broadcaster) = ManagementInterfaceServer::start(command_sender) .map_err(|error| { - log::error!("{}", error.display_chain_with_msg("Unable to start management interface server")); + log::error!( + "{}", + error.display_chain_with_msg("Unable to start management interface server") + ); Error::SpawnManagementInterface(error) })?; @@ -493,7 +498,6 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_shutdow } } - fn log_request_error(request: &str, error: &daemon_interface::Error) { match error { daemon_interface::Error::Api(RestError::Aborted) => { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 31d39db3069b..a5648650545c 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -99,6 +99,10 @@ service ManagementService { rpc SetSplitTunnelState(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc GetExcludedProcesses(google.protobuf.Empty) returns (ExcludedProcessList) {} + // Play payment (Android) + rpc InitPlayPurchase(google.protobuf.Empty) returns (PlayPurchasePaymentToken) {} + rpc VerifyPlayPurchase(PlayPurchase) returns (google.protobuf.Empty) {} + // Notify the split tunnel monitor that a volume was mounted or dismounted // (Windows). rpc CheckVolumes(google.protobuf.Empty) returns (google.protobuf.Empty) {} @@ -701,3 +705,10 @@ message RemoveDeviceEvent { string account_token = 1; repeated Device new_device_list = 2; } + +message PlayPurchase { + string product_id = 1; + PlayPurchasePaymentToken purchase_token = 2; +} + +message PlayPurchasePaymentToken { string token = 1; } diff --git a/mullvad-management-interface/src/types/conversions/account.rs b/mullvad-management-interface/src/types/conversions/account.rs index c98166ba8084..ec2796683e72 100644 --- a/mullvad-management-interface/src/types/conversions/account.rs +++ b/mullvad-management-interface/src/types/conversions/account.rs @@ -1,6 +1,8 @@ use crate::types; use chrono::TimeZone; use mullvad_types::account::{AccountData, VoucherSubmission}; +#[cfg(target_os = "android")] +use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use super::FromProtobufTypeError; @@ -62,3 +64,30 @@ impl TryFrom for AccountData { }) } } + +#[cfg(target_os = "android")] +impl TryFrom for PlayPurchase { + type Error = FromProtobufTypeError; + + fn try_from(value: types::PlayPurchase) -> Result { + let product_id = value.product_id; + let purchase_token = value + .purchase_token + .ok_or(FromProtobufTypeError::InvalidArgument( + "Missing purchase token", + ))? + .token; + + Ok(Self { + product_id, + purchase_token, + }) + } +} + +#[cfg(target_os = "android")] +impl From for types::PlayPurchasePaymentToken { + fn from(token: PlayPurchasePaymentToken) -> Self { + Self { token } + } +}