diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2019-10-08 17:52:43 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-10-08 17:52:43 +0100 |
| commit | 5368c6f3dd866e640144cf27166762345f3cd634 (patch) | |
| tree | 7b3c1d795171dd0020ef86f0e2bef6411eaaab11 | |
| parent | 0b5b3440a740721fb454ce33f717241a6b8d775e (diff) | |
| parent | e6201eff4b8ef817c8672650ebd9db9837356232 (diff) | |
| download | mullvadvpn-5368c6f3dd866e640144cf27166762345f3cd634.tar.xz mullvadvpn-5368c6f3dd866e640144cf27166762345f3cd634.zip | |
Merge branch 'add-redeem-voucher-rpc'
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/account.rs | 57 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 13 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 25 | ||||
| -rw-r--r-- | mullvad-ipc-client/src/lib.rs | 8 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 7 | ||||
| -rw-r--r-- | mullvad-types/src/account.rs | 38 |
7 files changed, 146 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cc07c108..8565aa01ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,10 +23,14 @@ Line wrap the file at 100 chars. Th ## [Unreleased] +### Added +- Add ability to submit vouchers from the CLI. + ### Fixed - Fix Norwegian (Bokmal) language detection. - Fix missing localizations when formatting date and time in Norwegian (Bokmal). + ## [2019.9-beta1] - 2019-10-08 ### Added - Add ability to change the desktop GUI language from within Settings. diff --git a/mullvad-cli/src/cmds/account.rs b/mullvad-cli/src/cmds/account.rs index 13de5db554..3baac8d192 100644 --- a/mullvad-cli/src/cmds/account.rs +++ b/mullvad-cli/src/cmds/account.rs @@ -1,6 +1,6 @@ use crate::{new_rpc_client, Command, Result}; use clap::value_t_or_exit; -use mullvad_types::account::AccountToken; +use mullvad_types::account::{AccountToken, VoucherError}; pub struct Account; @@ -34,6 +34,15 @@ impl Command for Account { clap::SubCommand::with_name("create") .about("Creates a new account and sets it as the active one"), ) + .subcommand( + clap::SubCommand::with_name("redeem") + .about("Redeems a voucher") + .arg( + clap::Arg::with_name("voucher") + .help("The Mullvad voucher code to be submitted") + .required(true), + ), + ) } fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { @@ -46,6 +55,9 @@ impl Command for Account { self.get() } else if let Some(_matches) = matches.subcommand_matches("create") { self.create() + } else if let Some(matches) = matches.subcommand_matches("redeem") { + let voucher = value_t_or_exit!(matches.value_of("voucher"), String); + self.redeem_voucher(voucher) } else { unreachable!("No account command given"); } @@ -83,4 +95,47 @@ impl Account { println!("New account created!"); self.get() } + + fn redeem_voucher(&self, mut voucher: String) -> Result<()> { + let mut rpc = new_rpc_client()?; + voucher.retain(|c| c.is_alphanumeric()); + + match rpc.submit_voucher(voucher) { + Ok(submission) => { + println!( + "Added {} to the account", + Self::format_duration(submission.time_added) + ); + println!("New expiry date: {}", submission.new_expiry); + Ok(()) + } + Err(err) => { + eprintln!( + "Failed to submit voucher.\n{}", + VoucherError::from_rpc_error_code(Self::get_redeem_rpc_error_code(&err)) + ); + Err(err.into()) + } + } + } + + fn format_duration(seconds: u64) -> String { + let dur = chrono::Duration::seconds(seconds as i64); + if dur.num_days() > 0 { + format!("{} days", dur.num_days()) + } else if dur.num_hours() > 0 { + format!("{} hours", dur.num_hours()) + } else if dur.num_minutes() > 0 { + format!("{} minutes", dur.num_minutes()) + } else { + format!("{} seconds", dur.num_seconds()) + } + } + + fn get_redeem_rpc_error_code(error: &mullvad_ipc_client::Error) -> i64 { + match error.kind() { + mullvad_ipc_client::ErrorKind::JsonRpcError(ref rpc_error) => rpc_error.code.code(), + _ => 0, + } + } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index b4ca11d32e..e6e6c39ab8 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -34,7 +34,7 @@ use futures::{ use log::{debug, error, info, warn}; use mullvad_rpc::{AccountsProxy, HttpHandle, WireguardKeyProxy}; use mullvad_types::{ - account::{AccountData, AccountToken}, + account::{AccountData, AccountToken, VoucherSubmission}, endpoint::MullvadEndpoint, location::GeoIpLocation, relay_constraints::{ @@ -781,6 +781,7 @@ where CreateNewAccount(tx) => self.on_create_new_account(tx), GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token), GetWwwAuthToken(tx) => self.on_get_www_auth_token(tx), + SubmitVoucher(tx, voucher) => self.on_submit_voucher(tx, voucher), GetRelayLocations(tx) => self.on_get_relay_locations(tx), UpdateRelayLocations => self.on_update_relay_locations(), SetAccount(tx, account_token) => self.on_set_account(tx, account_token), @@ -1022,6 +1023,16 @@ where } } + fn on_submit_voucher( + &mut self, + tx: oneshot::Sender<BoxFuture<VoucherSubmission, mullvad_rpc::Error>>, + voucher: String, + ) { + if let Some(account_token) = self.settings.get_account_token() { + let rpc_call = self.accounts_proxy.submit_voucher(account_token, voucher); + Self::oneshot_send(tx, Box::new(rpc_call), "submit_voucher response"); + } + } fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) { Self::oneshot_send(tx, self.relay_selector.get_locations(), "relay locations"); diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index afa6ad165e..56d1e100cb 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -13,7 +13,7 @@ use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; use mullvad_paths; use mullvad_rpc; use mullvad_types::{ - account::{AccountData, AccountToken}, + account::{AccountData, AccountToken, VoucherSubmission}, location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, relay_list::RelayList, @@ -52,6 +52,10 @@ build_rpc_trait! { #[rpc(meta, name = "get_www_auth_token")] fn get_www_auth_token(&self, Self::Metadata) -> BoxFuture<String, Error>; + /// Submit voucher to add time to account + #[rpc(meta, name = "submit_voucher")] + fn submit_voucher(&self, Self::Metadata, String) -> BoxFuture<VoucherSubmission, Error>; + /// Returns available countries. #[rpc(meta, name = "get_relay_locations")] fn get_relay_locations(&self, Self::Metadata) -> BoxFuture<RelayList, Error>; @@ -195,6 +199,11 @@ pub enum ManagementCommand { ), /// Request www auth token for an account GetWwwAuthToken(OneshotSender<BoxFuture<String, mullvad_rpc::Error>>), + /// Submit voucher to add time to the current account. Returns time added in seconds + SubmitVoucher( + OneshotSender<BoxFuture<VoucherSubmission, mullvad_rpc::Error>>, + String, + ), /// Request account history GetAccountHistory(OneshotSender<Vec<AccountToken>>), /// Request account history @@ -441,6 +450,20 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi Box::new(future) } + fn submit_voucher( + &self, + _: Self::Metadata, + voucher: String, + ) -> BoxFuture<VoucherSubmission, Error> { + log::debug!("submit_voucher"); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::SubmitVoucher(tx, voucher)) + .and_then(|_| rx.map_err(|_| Error::internal_error())) + .and_then(|f| f.map_err(|e| Self::map_rpc_error(&e))); + Box::new(future) + } + fn get_relay_locations(&self, _: Self::Metadata) -> BoxFuture<RelayList, Error> { log::debug!("get_relay_locations"); let (tx, rx) = sync::oneshot::channel(); diff --git a/mullvad-ipc-client/src/lib.rs b/mullvad-ipc-client/src/lib.rs index a962355ce8..84eacb5dd7 100644 --- a/mullvad-ipc-client/src/lib.rs +++ b/mullvad-ipc-client/src/lib.rs @@ -4,7 +4,7 @@ use futures::sync::oneshot; use jsonrpc_client_core::{Client, ClientHandle, Future}; use jsonrpc_client_ipc::IpcTransport; use mullvad_types::{ - account::{AccountData, AccountToken}, + account::{AccountData, AccountToken, VoucherSubmission}, location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, RelaySettings, RelaySettingsUpdate}, relay_list::RelayList, @@ -19,7 +19,7 @@ use std::{io, path::Path, thread}; static NO_ARGS: [u8; 0] = []; pub type Result<T> = std::result::Result<T, jsonrpc_client_core::Error>; -pub use jsonrpc_client_core::Error; +pub use jsonrpc_client_core::{Error, ErrorKind}; pub use jsonrpc_client_pubsub::Error as PubSubError; pub fn new_standalone_ipc_client(path: &impl AsRef<Path>) -> io::Result<DaemonRpcClient> { @@ -111,6 +111,10 @@ impl DaemonRpcClient { self.call("get_account_data", &[account]) } + pub fn submit_voucher(&mut self, voucher: String) -> Result<VoucherSubmission> { + self.call("submit_voucher", &[voucher]) + } + pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<()> { self.call("set_allow_lan", &[allow_lan]) } diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index ced3bfc729..d16029c16d 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -11,7 +11,11 @@ use chrono::{offset::Utc, DateTime}; use jsonrpc_client_core::{expand_params, jsonrpc_client}; use jsonrpc_client_http::{header::Host, HttpTransport, HttpTransportBuilder}; -use mullvad_types::{account::AccountToken, relay_list::RelayList, version}; +use mullvad_types::{ + account::{AccountToken, VoucherSubmission}, + relay_list::RelayList, + version, +}; use std::{ collections::BTreeMap, net::{IpAddr, Ipv4Addr}, @@ -107,6 +111,7 @@ jsonrpc_client!(pub struct AccountsProxy { pub fn create_account(&mut self) -> RpcRequest<AccountToken>; pub fn get_expiry(&mut self, account_token: AccountToken) -> RpcRequest<DateTime<Utc>>; pub fn get_www_auth_token(&mut self, account_token: AccountToken) -> RpcRequest<String>; + pub fn submit_voucher(&mut self, account_token: AccountToken, voucher: String) -> RpcRequest<VoucherSubmission>; }); jsonrpc_client!(pub struct ProblemReportProxy { diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs index c8de40ac54..909b6c0070 100644 --- a/mullvad-types/src/account.rs +++ b/mullvad-types/src/account.rs @@ -7,3 +7,41 @@ pub type AccountToken = String; pub struct AccountData { pub expiry: DateTime<Utc>, } + +/// Data-structure that's returned from successfuly invocation of the mullvad API's +/// `submit_voucher(account, voucher)` RPC +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct VoucherSubmission { + /// Amount of time added to the account + pub time_added: u64, + /// Updated expiry time + pub new_expiry: DateTime<Utc>, +} + +/// Mapping of mullvad-api errors +#[derive(err_derive::Error, Debug)] +pub enum VoucherError { + /// Error code -400 + #[error(display = "Bad voucher code")] + BadVoucher, + /// Error code -401 + #[error(display = "Voucher already used")] + VoucherAlreadyUsed, + /// Error code -100 + #[error(display = "Server internal error")] + InternalError, + #[error(display = "Unknown error, _0")] + UnknownError(i64), +} + +impl VoucherError { + /// Create error from RPC error code. + pub fn from_rpc_error_code(err_code: i64) -> VoucherError { + match err_code { + -400 => VoucherError::BadVoucher, + -401 => VoucherError::VoucherAlreadyUsed, + -100 => VoucherError::InternalError, + err => VoucherError::UnknownError(err), + } + } +} |
