summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2019-10-08 17:52:43 +0100
committerEmīls Piņķis <emils@mullvad.net>2019-10-08 17:52:43 +0100
commit5368c6f3dd866e640144cf27166762345f3cd634 (patch)
tree7b3c1d795171dd0020ef86f0e2bef6411eaaab11
parent0b5b3440a740721fb454ce33f717241a6b8d775e (diff)
parente6201eff4b8ef817c8672650ebd9db9837356232 (diff)
downloadmullvadvpn-5368c6f3dd866e640144cf27166762345f3cd634.tar.xz
mullvadvpn-5368c6f3dd866e640144cf27166762345f3cd634.zip
Merge branch 'add-redeem-voucher-rpc'
-rw-r--r--CHANGELOG.md4
-rw-r--r--mullvad-cli/src/cmds/account.rs57
-rw-r--r--mullvad-daemon/src/lib.rs13
-rw-r--r--mullvad-daemon/src/management_interface.rs25
-rw-r--r--mullvad-ipc-client/src/lib.rs8
-rw-r--r--mullvad-rpc/src/lib.rs7
-rw-r--r--mullvad-types/src/account.rs38
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),
+ }
+ }
+}