diff options
| -rw-r--r-- | mullvad-daemon/src/device/api.rs | 12 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/mod.rs | 74 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/service.rs | 4 |
3 files changed, 86 insertions, 4 deletions
diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs index 6e00b669ca..6cd46ccc48 100644 --- a/mullvad-daemon/src/device/api.rs +++ b/mullvad-daemon/src/device/api.rs @@ -1,5 +1,6 @@ use std::pin::Pin; +use chrono::{DateTime, Utc}; use futures::{future::FusedFuture, Future}; use mullvad_types::{device::Device, wireguard::WireguardData}; @@ -34,6 +35,10 @@ impl CurrentApiCall { self.current_call = Some(Call::Validation(validation)); } + pub fn set_expiry_check(&mut self, expiry_call: ApiCall<DateTime<Utc>>) { + self.current_call = Some(Call::ExpiryCheck(expiry_call)); + } + pub fn is_validating(&self) -> bool { matches!( &self.current_call, @@ -41,6 +46,10 @@ impl CurrentApiCall { ) } + pub fn is_checking_expiry(&self) -> bool { + matches!(&self.current_call, Some(Call::ExpiryCheck(_))) + } + pub fn is_running_timed_totation(&self) -> bool { matches!(&self.current_call, Some(Call::TimerKeyRotation(_))) } @@ -88,6 +97,7 @@ enum Call { TimerKeyRotation(ApiCall<WireguardData>), OneshotKeyRotation(ApiCall<WireguardData>), Validation(ApiCall<Device>), + ExpiryCheck(ApiCall<DateTime<Utc>>), } impl futures::Future for Call { @@ -110,6 +120,7 @@ impl futures::Future for Call { Pin::new(call).poll(cx).map(ApiResult::Rotation) } Validation(call) => Pin::new(call).poll(cx).map(ApiResult::Validation), + ExpiryCheck(call) => Pin::new(call).poll(cx).map(ApiResult::ExpiryCheck), } } } @@ -118,4 +129,5 @@ pub(crate) enum ApiResult { Login(Result<PrivateAccountAndDevice, Error>, ResponseTx<()>), Rotation(Result<WireguardData, Error>), Validation(Result<Device, Error>), + ExpiryCheck(Result<DateTime<Utc>, Error>), } diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs index 31ea46ea36..1eb5614f82 100644 --- a/mullvad-daemon/src/device/mod.rs +++ b/mullvad-daemon/src/device/mod.rs @@ -303,6 +303,7 @@ enum AccountManagerCommand { RotateKey(ResponseTx<()>), SetRotationInterval(RotationInterval, ResponseTx<()>), ValidateDevice(ResponseTx<()>), + CheckExpiry(ResponseTx<DateTime<Utc>>), Shutdown(oneshot::Sender<()>), } @@ -351,6 +352,10 @@ impl AccountManagerHandle { .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 @@ -373,12 +378,14 @@ impl AccountManagerHandle { pub(crate) struct AccountManager { cacher: DeviceCacher, + account_service: AccountService, device_service: DeviceService, data: PrivateDeviceState, rotation_interval: RotationInterval, listeners: Vec<Box<dyn Sender<PrivateDeviceEvent> + Send>>, last_validation: Option<SystemTime>, validation_requests: Vec<ResponseTx<()>>, + expiry_requests: Vec<ResponseTx<DateTime<Utc>>>, rotation_requests: Vec<ResponseTx<()>>, data_requests: Vec<ResponseTx<PrivateDeviceState>>, } @@ -403,12 +410,14 @@ impl AccountManager { let device_service = DeviceService::new(rest_handle, api_availability); let manager = AccountManager { cacher, + account_service: account_service.clone(), device_service: device_service.clone(), data: data.clone(), rotation_interval: initial_rotation_interval, listeners: vec![Box::new(listener_tx)], last_validation: None, validation_requests: vec![], + expiry_requests: vec![], rotation_requests: vec![], data_requests: vec![], }; @@ -489,6 +498,9 @@ impl AccountManager { Some(AccountManagerCommand::ValidateDevice(tx)) => { self.handle_validation_request(tx, &mut current_api_call); }, + Some(AccountManagerCommand::CheckExpiry(tx)) => { + self.handle_expiry_request(tx, &mut current_api_call); + }, None => { break; @@ -539,6 +551,31 @@ impl AccountManager { } } + fn handle_expiry_request( + &mut self, + 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; + } + + match self.expiry_call() { + Ok(call) => { + current_api_call.set_expiry_check(Box::pin(call)); + self.expiry_requests.push(tx); + } + Err(err) => { + let _ = tx.send(Err(err)); + } + } + } + async fn consume_api_result( &mut self, result: api::ApiResult, @@ -549,6 +586,7 @@ impl AccountManager { Login(data, tx) => self.consume_login(data, tx).await, Rotation(rotation_response) => self.consume_rotation_result(rotation_response).await, Validation(data_response) => self.consume_validation(data_response, api_call).await, + ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await, } } @@ -563,6 +601,27 @@ impl AccountManager { Self::drain_requests(&mut self.data_requests, || Ok(data.clone())); } + async fn consume_expiry_result(&mut self, response: Result<DateTime<Utc>, Error>) { + match response { + Ok(expiry) => { + Self::drain_requests(&mut self.expiry_requests, || Ok(expiry)); + } + Err(Error::InvalidAccount) => { + self.invalidate_current_data(|| Error::InvalidAccount).await; + } + Err(Error::InvalidDevice) => { + self.invalidate_current_data(|| Error::InvalidDevice).await; + } + Err(err) => { + log::error!("Failed to check account expiry: {}", err); + let cloneable_err = Arc::new(err); + Self::drain_requests(&mut self.expiry_requests, || { + Err(Error::ResponseFailure(cloneable_err.clone())) + }); + } + } + } + async fn consume_validation( &mut self, response: Result<Device, Error>, @@ -646,11 +705,10 @@ impl AccountManager { match self.set(PrivateDeviceEvent::RotatedKey(config)).await { Ok(_) => { Self::drain_requests(&mut self.rotation_requests, || Ok(())); - Self::drain_requests(&mut self.validation_requests, || Ok(())); } Err(err) => { - self.drain_requests_with_err(err); + self.drain_device_requests_with_err(err); } } } @@ -661,12 +719,12 @@ impl AccountManager { self.invalidate_current_data(|| Error::InvalidDevice).await; } Err(err) => { - self.drain_requests_with_err(err); + self.drain_device_requests_with_err(err); } } } - fn drain_requests_with_err(&mut self, err: Error) { + fn drain_device_requests_with_err(&mut self, err: Error) { let cloneable_err = Arc::new(err); Self::drain_requests(&mut self.rotation_requests, || { Err(Error::ResponseFailure(cloneable_err.clone())) @@ -713,6 +771,7 @@ impl AccountManager { Self::drain_requests(&mut self.validation_requests, || Err(err_constructor())); Self::drain_requests(&mut self.rotation_requests, || Err(err_constructor())); + Self::drain_requests(&mut self.expiry_requests, || Err(err_constructor())); self.listeners .retain(|listener| listener.send(PrivateDeviceEvent::Revoked).is_ok()); @@ -840,6 +899,13 @@ impl AccountManager { Ok(self.fetch_device_config(old_config)) } + fn expiry_call(&self) -> Result<impl Future<Output = Result<DateTime<Utc>, Error>>, Error> { + 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.check_expiry_2(account_token).await }) + } + fn needs_validation(&mut self) -> bool { if !self.data.logged_in() { return true; diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs index 90a44d5fe1..61cbe0e4bd 100644 --- a/mullvad-daemon/src/device/service.rs +++ b/mullvad-daemon/src/device/service.rs @@ -310,6 +310,10 @@ impl AccountService { result } + pub async fn check_expiry_2(&self, token: AccountToken) -> Result<DateTime<Utc>, Error> { + self.check_expiry(token).await.map_err(map_rest_error) + } + pub async fn submit_voucher( &mut self, account_token: AccountToken, |
