diff options
| -rw-r--r-- | mullvad-api/src/lib.rs | 7 | ||||
| -rw-r--r-- | mullvad-api/src/rest.rs | 32 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/api.rs | 16 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/mod.rs | 155 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/service.rs | 7 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 72 | ||||
| -rw-r--r-- | mullvad-jni/src/lib.rs | 4 | ||||
| -rw-r--r-- | mullvad-types/src/account.rs | 6 |
8 files changed, 181 insertions, 118 deletions
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 647b5cf071..63f5c2ad5b 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -5,8 +5,10 @@ use chrono::{offset::Utc, DateTime}; use futures::channel::mpsc; use futures::Stream; use hyper::Method; +#[cfg(target_os = "android")] +use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ - account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission}, + account::{AccountToken, VoucherSubmission}, version::AppVersion, }; use proxy::ApiConnectionMode; @@ -63,6 +65,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"; +#[cfg(target_os = "android")] const GOOGLE_PAYMENTS_URL_PREFIX: &str = "payments/google-play/v1"; pub static API: LazyManual<ApiEndpoint> = LazyManual::new(ApiEndpoint::from_env_vars); @@ -458,6 +461,7 @@ impl AccountsProxy { } } + #[cfg(target_os = "android")] pub fn init_play_purchase( &mut self, account_token: AccountToken, @@ -490,6 +494,7 @@ impl AccountsProxy { } } + #[cfg(target_os = "android")] pub fn verify_play_purchase( &mut self, account_token: AccountToken, diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index 674bcf8c4e..c3687a1eee 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -394,10 +394,17 @@ impl From<Request> for RestRequest { } #[derive(serde::Deserialize)] -pub struct ErrorResponse { +struct OldErrorResponse { pub code: String, } +/// If `NewErrorResponse::type` is not defined it should default to "about:blank" +const DEFAULT_ERROR_TYPE: &str = "about:blank"; +#[derive(serde::Deserialize)] +struct NewErrorResponse { + pub r#type: Option<String>, +} + #[derive(Clone)] pub struct RequestFactory { hostname: String, @@ -600,8 +607,27 @@ pub async fn handle_error_response<T>(response: Response) -> Result<T> { status => match get_body_length(&response) { 0 => status.canonical_reason().unwrap_or("Unexpected error"), body_length => { - let err: ErrorResponse = deserialize_body_inner(response, body_length).await?; - return Err(Error::ApiError(status, err.code)); + return match response.headers().get("content-type") { + Some(content_type) if content_type == "application/problem+json" => { + // TODO: We should make sure we unify the new error format and the old + // error format so that they both produce the same Errors for the same + // problems after being processed. + let err: NewErrorResponse = + deserialize_body_inner(response, body_length).await?; + // The new error type replaces the `code` field with the `type` field. + // This is what is used to programmatically check the error. + Err(Error::ApiError( + status, + err.r#type + .unwrap_or_else(|| String::from(DEFAULT_ERROR_TYPE)), + )) + } + _ => { + let err: OldErrorResponse = + deserialize_body_inner(response, body_length).await?; + Err(Error::ApiError(status, err.code)) + } + }; } }, }; diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs index 07e7b35311..d7b73d6e7b 100644 --- a/mullvad-daemon/src/device/api.rs +++ b/mullvad-daemon/src/device/api.rs @@ -2,11 +2,9 @@ use std::pin::Pin; use chrono::{DateTime, Utc}; use futures::{future::FusedFuture, Future}; -use mullvad_types::{ - account::{PlayPurchasePaymentToken, VoucherSubmission}, - device::Device, - wireguard::WireguardData, -}; +#[cfg(target_os = "android")] +use mullvad_types::account::PlayPurchasePaymentToken; +use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData}; use super::{Error, PrivateAccountAndDevice, ResponseTx}; @@ -51,6 +49,7 @@ impl CurrentApiCall { self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx))); } + #[cfg(target_os = "android")] pub fn set_init_play_purchase( &mut self, init_play_purchase_call: ApiCall<PlayPurchasePaymentToken>, @@ -59,6 +58,7 @@ impl CurrentApiCall { self.current_call = Some(Call::InitPlayPurchase(init_play_purchase_call, Some(tx))); } + #[cfg(target_os = "android")] pub fn set_verify_play_purchase( &mut self, verify_play_purchase_call: ApiCall<()>, @@ -132,10 +132,12 @@ enum Call { ApiCall<VoucherSubmission>, Option<ResponseTx<VoucherSubmission>>, ), + #[cfg(target_os = "android")] InitPlayPurchase( ApiCall<PlayPurchasePaymentToken>, Option<ResponseTx<PlayPurchasePaymentToken>>, ), + #[cfg(target_os = "android")] VerifyPlayPurchase(ApiCall<()>, Option<ResponseTx<()>>), ExpiryCheck(ApiCall<DateTime<Utc>>), } @@ -170,6 +172,7 @@ impl futures::Future for Call { std::task::Poll::Pending } } + #[cfg(target_os = "android")] InitPlayPurchase(call, tx) => { if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) { std::task::Poll::Ready(ApiResult::InitPlayPurchase( @@ -180,6 +183,7 @@ impl futures::Future for Call { std::task::Poll::Pending } } + #[cfg(target_os = "android")] VerifyPlayPurchase(call, tx) => { if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) { std::task::Poll::Ready(ApiResult::VerifyPlayPurchase( @@ -203,10 +207,12 @@ pub(crate) enum ApiResult { Result<VoucherSubmission, Error>, ResponseTx<VoucherSubmission>, ), + #[cfg(target_os = "android")] InitPlayPurchase( Result<PlayPurchasePaymentToken, Error>, ResponseTx<PlayPurchasePaymentToken>, ), + #[cfg(target_os = "android")] 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 528c25ce9a..df5606a143 100644 --- a/mullvad-daemon/src/device/mod.rs +++ b/mullvad-daemon/src/device/mod.rs @@ -5,13 +5,16 @@ use futures::{ }; use mullvad_api::rest; +#[cfg(target_os = "android")] +use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ - account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission}, + account::{AccountToken, VoucherSubmission}, device::{ AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DeviceState, }, wireguard::{self, RotationInterval, WireguardData}, }; + use std::{ future::Future, path::Path, @@ -305,7 +308,9 @@ enum AccountManagerCommand { SetRotationInterval(RotationInterval, ResponseTx<()>), ValidateDevice(ResponseTx<()>), SubmitVoucher(String, ResponseTx<VoucherSubmission>), + #[cfg(target_os = "android")] InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken>), + #[cfg(target_os = "android")] VerifyPlayPurchase(ResponseTx<()>, PlayPurchase), CheckExpiry(ResponseTx<DateTime<Utc>>), Shutdown(oneshot::Sender<()>), @@ -361,20 +366,22 @@ impl AccountManagerHandle { .await } + pub async fn check_expiry(&self) -> Result<DateTime<Utc>, Error> { + self.send_command(AccountManagerCommand::CheckExpiry).await + } + + #[cfg(target_os = "android")] pub async fn init_play_purchase(&self) -> Result<PlayPurchasePaymentToken, Error> { self.send_command(AccountManagerCommand::InitPlayPurchase) .await } + #[cfg(target_os = "android")] 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 - } - pub async fn shutdown(self) { let (tx, rx) = oneshot::channel(); let _ = self @@ -526,15 +533,17 @@ impl AccountManager { Some(AccountManagerCommand::SubmitVoucher(voucher, tx)) => { self.handle_voucher_submission(tx, voucher, &mut current_api_call); }, + Some(AccountManagerCommand::CheckExpiry(tx)) => { + self.handle_expiry_request(tx, &mut current_api_call); + }, + #[cfg(target_os = "android")] Some(AccountManagerCommand::InitPlayPurchase(tx)) => { self.handle_init_play_purchase(tx, &mut current_api_call); }, + #[cfg(target_os = "android")] 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); - }, None => { break; @@ -607,6 +616,7 @@ impl AccountManager { } } + #[cfg(target_os = "android")] fn handle_init_play_purchase( &mut self, tx: ResponseTx<PlayPurchasePaymentToken>, @@ -634,31 +644,24 @@ impl AccountManager { } } - fn handle_verify_play_purchase( + fn handle_expiry_request( &mut self, - tx: ResponseTx<()>, - play_purchase: PlayPurchase, + tx: ResponseTx<DateTime<Utc>>, current_api_call: &mut api::CurrentApiCall, ) { if current_api_call.is_logging_in() { let _ = tx.send(Err(Error::AccountChange)); return; } + if current_api_call.is_checking_expiry() { + self.expiry_requests.push(tx); + 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() { + match self.expiry_call() { Ok(call) => { - current_api_call.set_verify_play_purchase(Box::pin(call), tx); + current_api_call.set_expiry_check(Box::pin(call)); + self.expiry_requests.push(tx); } Err(err) => { let _ = tx.send(Err(err)); @@ -666,24 +669,32 @@ impl AccountManager { } } - fn handle_expiry_request( + #[cfg(target_os = "android")] + fn handle_verify_play_purchase( &mut self, - tx: ResponseTx<DateTime<Utc>>, + 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; } - if current_api_call.is_checking_expiry() { - self.expiry_requests.push(tx); - return; - } - match self.expiry_call() { + 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_expiry_check(Box::pin(call)); - self.expiry_requests.push(tx); + current_api_call.set_verify_play_purchase(Box::pin(call), tx); } Err(err) => { let _ = tx.send(Err(err)); @@ -704,15 +715,17 @@ impl AccountManager { VoucherSubmission(data_response, tx) => { self.consume_voucher_result(data_response, tx).await } + ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await, + #[cfg(target_os = "android")] InitPlayPurchase(data_response, tx) => { self.consume_init_play_purchase_result(data_response, tx) .await } + #[cfg(target_os = "android")] VerifyPlayPurchase(data_response, tx) => { self.consume_verify_play_purchase_result(data_response, tx) .await } - ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await, } } @@ -750,42 +763,6 @@ 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) => { @@ -920,6 +897,44 @@ impl AccountManager { } } + #[cfg(target_os = "android")] + 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); + } + + #[cfg(target_os = "android")] + 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); + } + fn drain_device_requests_with_err(&mut self, err: Error) { let cloneable_err = Arc::new(err); Self::drain_requests(&mut self.rotation_requests, || { diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs index 85d9d56d84..fdda61297f 100644 --- a/mullvad-daemon/src/device/service.rs +++ b/mullvad-daemon/src/device/service.rs @@ -2,8 +2,10 @@ use std::{future::Future, time::Duration}; use chrono::{DateTime, Utc}; use futures::future::{abortable, AbortHandle}; +#[cfg(target_os = "android")] +use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ - account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission}, + account::{AccountToken, VoucherSubmission}, device::{Device, DeviceId}, wireguard::WireguardData, }; @@ -321,6 +323,7 @@ impl AccountService { result.map_err(map_rest_error) } + #[cfg(target_os = "android")] pub async fn init_play_purchase( &self, account_token: AccountToken, @@ -340,6 +343,7 @@ impl AccountService { result.map_err(map_rest_error) } + #[cfg(target_os = "android")] pub async fn verify_play_purchase( &self, account_token: AccountToken, @@ -448,6 +452,7 @@ fn should_retry_backoff<T>(result: &Result<T, RestError>) -> bool { fn map_rest_error(error: rest::Error) -> Error { match error { RestError::ApiError(_status, ref code) => match code.as_str() { + // TODO: Implement invalid payment mullvad_api::DEVICE_NOT_FOUND => Error::InvalidDevice, mullvad_api::INVALID_ACCOUNT => Error::InvalidAccount, mullvad_api::MAX_DEVICES_REACHED => Error::MaxDevicesReached, diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 8f661d981a..1077185ca3 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -38,9 +38,11 @@ use mullvad_relay_selector::{ updater::{RelayListUpdater, RelayListUpdaterHandle}, RelaySelector, SelectorConfig, }; +#[cfg(target_os = "android")] +use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ access_method::{AccessMethod, AccessMethodSetting}, - account::{AccountData, AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission}, + account::{AccountData, AccountToken, VoucherSubmission}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -1041,10 +1043,6 @@ 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), @@ -1127,6 +1125,12 @@ where PrepareRestart => self.on_prepare_restart(), #[cfg(target_os = "android")] BypassSocket(fd, tx) => self.on_bypass_socket(fd, tx), + #[cfg(target_os = "android")] + InitPlayPurchase(tx) => self.on_init_play_purchase(tx), + #[cfg(target_os = "android")] + VerifyPlayPurchase(tx, play_purchase) => { + self.on_verify_play_purchase(tx, play_purchase) + } } } @@ -1422,34 +1426,6 @@ 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"); } @@ -2416,6 +2392,36 @@ where } } + #[cfg(target_os = "android")] + 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", + ); + }); + } + + #[cfg(target_os = "android")] + 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", + ); + }); + } + /// Set the target state of the client. If it changed trigger the operations needed to /// progress towards that state. /// Returns a bool representing whether or not a state change was initiated. diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs index ff7a978799..a40bf73fc1 100644 --- a/mullvad-jni/src/lib.rs +++ b/mullvad-jni/src/lib.rs @@ -214,7 +214,7 @@ impl From<Result<String, daemon_interface::Error>> for PlayPurchaseInitResult { } impl From<daemon_interface::Error> for PlayPurchaseInitError { - fn from(error: daemon_interface::Error) -> Self { + fn from(_error: daemon_interface::Error) -> Self { PlayPurchaseInitError::OtherError } } @@ -242,7 +242,7 @@ impl From<Result<(), daemon_interface::Error>> for PlayPurchaseVerifyResult { } impl From<daemon_interface::Error> for PlayPurchaseVerifyError { - fn from(error: daemon_interface::Error) -> Self { + fn from(_error: daemon_interface::Error) -> Self { PlayPurchaseVerifyError::OtherError } } diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs index 2d12a80a2a..7adb7fbffa 100644 --- a/mullvad-types/src/account.rs +++ b/mullvad-types/src/account.rs @@ -9,9 +9,9 @@ pub type AccountToken = String; /// Identifier used to authenticate a Mullvad account. pub type AccessToken = String; -// TODO: Should be only android /// The payment token returned by initiating a google play purchase. /// In the API this is called the `obfuscated_id`. +#[cfg(target_os = "android")] pub type PlayPurchasePaymentToken = String; /// Account expiration info returned by the API via `/v1/me`. @@ -44,14 +44,14 @@ pub struct VoucherSubmission { pub new_expiry: DateTime<Utc>, } -/// TODO Should be only android /// `PlayPurchase` is provided to google in order to verify that a google play purchase was acknowledged. #[derive(Deserialize, Serialize, Debug, Clone)] #[cfg_attr(target_os = "android", derive(FromJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] +#[cfg(target_os = "android")] pub struct PlayPurchase { pub product_id: String, - pub purchase_token: String, + pub purchase_token: PlayPurchasePaymentToken, } /// Token used for authentication in the API. |
