diff options
| -rw-r--r-- | mullvad-daemon/src/migrations/v7.rs | 313 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 6 | ||||
| -rw-r--r-- | mullvad-management-interface/src/client.rs | 17 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/access_method.rs | 52 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/settings.rs | 2 | ||||
| -rw-r--r-- | mullvad-types/src/access_method.rs | 179 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 2 |
7 files changed, 460 insertions, 111 deletions
diff --git a/mullvad-daemon/src/migrations/v7.rs b/mullvad-daemon/src/migrations/v7.rs index efd3193624..4614cedce9 100644 --- a/mullvad-daemon/src/migrations/v7.rs +++ b/mullvad-daemon/src/migrations/v7.rs @@ -75,6 +75,10 @@ pub struct ShadowsocksProxySettings { /// that instead of having a Socks5 and Shadowsocks variant instead has a Socks5Local, Socks5Remote /// and Shadowsocks variant. /// +/// The predefined access methods "Direct" and "Mullvad Bridges" are now stored as distinct keys in +/// the api_access_methods settings, separating them from user-defined access methods in the settings +/// datastructure. +/// /// We also take the oppertunity to rename a couple of fields that relate to proxy types. /// We rename /// - shadowsocks.peer to shadowsocks.endpoint @@ -141,6 +145,62 @@ fn migrate_api_access_settings(settings: &mut serde_json::Value) -> Result<()> { } } + // Step 1. Rename { "api_access_methods": { "access_method_settings": .. } } to { "api_access_methods": { "custom": .. } }. + // Step 2. Collect all of the built-in methods from { "api_access_methods": { "custom": [ .. ] } }. + // Step 3. Remove all of the built-in methods from { "api_access_methods": { "custom": [ .. ] } }. + // Step 4. Add the collected built-in methods from step 2 to { "api_access_methods": { .. } } under some appropriate key. + if let Some(access_method_settings) = settings + .get_mut("api_access_methods") + .and_then(serde_json::value::Value::as_object_mut) + { + // Step 1. + rename_map_field(access_method_settings, "access_method_settings", "custom")?; + + if let Some(access_method_settings_list) = access_method_settings + .get_mut("custom") + .and_then(serde_json::value::Value::as_array_mut) + { + // Step 2. + let built_ins: Vec<_> = access_method_settings_list + .iter() + .filter(|value| { + value + .get("access_method") + .and_then(|value| value.get("built_in")) + .is_some() + }) + .cloned() + .collect(); + + // Step 3. + for built_in in built_ins.iter() { + access_method_settings_list + .retain(|access_method| access_method.get("id") != built_in.get("id")); + } + + // Step 4. + // Note that the only supported built-in access methods at this time + // are "Direct" and "Mullvad Bridges", so we may discard anything + // else. + let built_ins: Vec<_> = built_ins + .into_iter() + .filter_map(|built_in| { + match built_in + .get("access_method") + .and_then(|value| value.get("built_in")) + .and_then(|value| value.as_str()) + { + Some("direct") => Some(("direct".to_string(), built_in)), + Some("bridge") => Some(("mullvad_bridges".to_string(), built_in)), + Some(_) | None => None, + } + }) + .collect(); + + access_method_settings.extend(built_ins); + } + } + Ok(()) } @@ -228,15 +288,24 @@ fn extract_str(opt: Option<&serde_json::Value>) -> Result<&str> { .ok_or(Error::InvalidSettingsContent) } -fn rename_field(object: &mut serde_json::Value, old_name: &str, new_name: &str) -> Result<()> { - object[new_name] = object +fn rename_field(value: &mut serde_json::Value, old_name: &str, new_name: &str) -> Result<()> { + value + .as_object_mut() + .ok_or(Error::InvalidSettingsContent) + .and_then(|object| rename_map_field(object, old_name, new_name)) +} + +fn rename_map_field( + object: &mut serde_json::Map<String, serde_json::Value>, + old_name: &str, + new_name: &str, +) -> Result<()> { + let old_value = object .get(old_name) .ok_or(Error::InvalidSettingsContent)? .clone(); - object - .as_object_mut() - .ok_or(Error::InvalidSettingsContent)? - .remove(old_name); + let _ = object.insert(new_name.to_string(), old_value); + object.remove(old_name); Ok(()) } @@ -475,23 +544,23 @@ mod test { } }, "api_access_methods": { - "access_method_settings": [ - { - "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89", - "name": "Direct", - "enabled": true, - "access_method": { - "built_in": "direct" - } - }, - { - "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4", - "name": "Mullvad Bridges", - "enabled": true, - "access_method": { - "built_in": "bridge" - } - }, + "direct": { + "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "mullvad_bridges": { + "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [ { "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b", "name": "localsox", @@ -851,7 +920,7 @@ mod test { r#" { "api_access_methods": { - "access_method_settings": [ + "custom": [ { "id": "5eb9b2ee-f764-47c8-8111-ee95910d0099", "name": "mysocks", @@ -880,7 +949,6 @@ mod test { #[test] fn test_api_access_methods_custom_socks5_remote() { - println!("wew"); let mut pre: serde_json::Value = serde_json::from_str( r#" { @@ -910,7 +978,7 @@ mod test { r#" { "api_access_methods": { - "access_method_settings": [ + "custom": [ { "id": "8e377232-8a53-4414-8b8f-f487227aaedb", "name": "remotesox", @@ -965,7 +1033,7 @@ mod test { r#" { "api_access_methods": { - "access_method_settings": [ + "custom": [ { "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2", "name": "remotess", @@ -989,4 +1057,195 @@ mod test { migrate_api_access_settings(&mut pre).unwrap(); assert_eq!(pre, post); } + + #[test] + fn test_api_access_methods_extract_direct() { + let mut pre: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "access_method_settings": [ + { + "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + } + ] + } +} +"#, + ) + .unwrap(); + + let post: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "direct": { + "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "custom": [] + } +} +"#, + ) + .unwrap(); + + migrate_api_access_settings(&mut pre).unwrap(); + assert_eq!(pre, post); + } + + #[test] + fn test_api_access_methods_extract_mullvad_bridges() { + let mut pre: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "access_method_settings": [ + { + "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + } + ] + } +} +"#, + ) + .unwrap(); + + let post: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "mullvad_bridges": { + "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [] + } +} +"#, + ) + .unwrap(); + + migrate_api_access_settings(&mut pre).unwrap(); + assert_eq!(pre, post); + } + + #[test] + fn test_api_access_methods_do_not_extract_custom_methods() { + let mut pre: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "access_method_settings": [ + { + "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b", + "name": "localsox", + "enabled": false, + "access_method": { + "custom": { + "socks5": { + "local": { + "remote_endpoint": { + "address": "1.3.3.7:1080", + "protocol": "tcp" + }, + "local_port": 1079 + } + } + } + } + } + ] + } +} +"#, + ) + .unwrap(); + + let post: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "custom": [ + { + "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b", + "name": "localsox", + "enabled": false, + "access_method": { + "custom": { + "socks5_local": { + "remote_endpoint": { + "address": "1.3.3.7:1080", + "protocol": "tcp" + }, + "local_port": 1079 + } + } + } + } + ] + } +} +"#, + ) + .unwrap(); + + migrate_api_access_settings(&mut pre).unwrap(); + assert_eq!(pre, post); + } + + #[test] + fn test_api_access_methods_extract_corrupt_built_in() { + let mut pre: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "access_method_settings": [ + { + "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "some_other_alternative" + } + } + ] + } +} +"#, + ) + .unwrap(); + + let post: serde_json::Value = serde_json::from_str( + r#" +{ + "api_access_methods": { + "custom": [] + } +} +"#, + ) + .unwrap(); + + migrate_api_access_settings(&mut pre).unwrap(); + assert_eq!(pre, post); + } } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 21de18df8e..7fbdb6eba6 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -380,7 +380,11 @@ message NewAccessMethodSetting { AccessMethod access_method = 3; } -message ApiAccessMethodSettings { repeated AccessMethodSetting access_method_settings = 1; } +message ApiAccessMethodSettings { + AccessMethodSetting direct = 1; + AccessMethodSetting mullvad_bridges = 2; + repeated AccessMethodSetting custom = 3; +} message Settings { RelaySettings relay_settings = 1; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 52dbd72b47..f30b613171 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -176,19 +176,20 @@ impl MullvadProxyClient { } pub async fn get_api_access_methods(&mut self) -> Result<Vec<AccessMethodSetting>> { - self.0 + let access_method_settings = self + .0 .get_settings(()) .await .map_err(Error::Rpc)? .into_inner() .api_access_methods - .ok_or(Error::ApiAccessMethodSettingsNotFound)? - .access_method_settings - .into_iter() - .map(|api_access_method| { - AccessMethodSetting::try_from(api_access_method).map_err(Error::InvalidResponse) - }) - .collect() + .ok_or(Error::ApiAccessMethodSettingsNotFound) + .and_then(|access_method_settings| { + access_method::Settings::try_from(access_method_settings) + .map_err(Error::InvalidResponse) + })?; + + Ok(access_method_settings.iter().cloned().collect()) } pub async fn get_api_access_method( diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs index ec5681d74d..d9758a571c 100644 --- a/mullvad-management-interface/src/types/conversions/access_method.rs +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -5,35 +5,49 @@ mod settings { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::access_method; - impl From<&access_method::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: &access_method::Settings) -> Self { + impl From<access_method::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: access_method::Settings) -> Self { Self { - access_method_settings: settings - .access_method_settings - .iter() - .map(|method| method.clone().into()) + direct: Some(settings.direct().clone().into()), + mullvad_bridges: Some(settings.mullvad_bridges().clone().into()), + custom: settings + .iter_custom() + .cloned() + .map(|method| method.into()) .collect(), } } } - impl From<access_method::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: access_method::Settings) -> Self { - proto::ApiAccessMethodSettings::from(&settings) - } - } - impl TryFrom<proto::ApiAccessMethodSettings> for access_method::Settings { type Error = FromProtobufTypeError; fn try_from(settings: proto::ApiAccessMethodSettings) -> Result<Self, Self::Error> { - Ok(Self { - access_method_settings: settings - .access_method_settings - .iter() - .map(access_method::AccessMethodSetting::try_from) - .collect::<Result<Vec<access_method::AccessMethodSetting>, _>>()?, - }) + let direct = settings + .direct + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Direct Access Method from protobuf", + )) + .and_then(access_method::AccessMethodSetting::try_from)?; + + let mullvad_bridges = settings + .mullvad_bridges + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Mullvad Bridges Access Method from protobuf", + )) + .and_then(access_method::AccessMethodSetting::try_from)?; + + let custom = settings + .custom + .iter() + .map(access_method::AccessMethodSetting::try_from) + .collect::<Result<Vec<_>, _>>()?; + + Ok(access_method::Settings::new( + direct, + mullvad_bridges, + custom, + )) } } } diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index a7c4bcd78c..a4d6313158 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -43,7 +43,7 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings { settings.custom_lists.clone(), )), api_access_methods: Some(proto::ApiAccessMethodSettings::from( - &settings.api_access_methods, + settings.api_access_methods.clone(), )), relay_overrides: settings .relay_overrides diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index 73ab671c9c..80ba7ab69c 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -1,102 +1,162 @@ -use std::str::FromStr; - use serde::{Deserialize, Serialize}; use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote}; -/// Dttings for API access methods. +/// Settings for API access methods. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { - pub access_method_settings: Vec<AccessMethodSetting>, + direct: AccessMethodSetting, + mullvad_bridges: AccessMethodSetting, + /// Custom API access methods. + custom: Vec<AccessMethodSetting>, } impl Settings { + pub fn new( + direct: AccessMethodSetting, + mullvad_bridges: AccessMethodSetting, + custom: Vec<AccessMethodSetting>, + ) -> Settings { + Settings { + direct, + mullvad_bridges, + custom, + } + } + /// Append an [`AccessMethod`] to the end of `api_access_methods`. pub fn append(&mut self, api_access_method: AccessMethodSetting) { - self.access_method_settings.push(api_access_method) + self.custom.push(api_access_method) } /// Remove an [`ApiAccessMethod`] from `api_access_methods`. - pub fn remove(&mut self, api_access_method: &Id) { - self.retain(|method| method.get_id() != *api_access_method) + /// + /// This function will return an error if a built-in API access is about to + /// be removed. + pub fn remove(&mut self, api_access_method: &Id) -> Result<(), Error> { + let maybe_setting = self + .custom + .iter() + .find(|setting| setting.get_id() == *api_access_method); + + match maybe_setting { + Some(x) => match x.access_method { + AccessMethod::BuiltIn(ref built_in) => Err(Error::RemoveBuiltin { + attempted: built_in.clone(), + }), + AccessMethod::Custom(_) => { + self.custom + .retain(|method| method.get_id() != *api_access_method); + self.ensure_consistent_state(); + Ok(()) + } + }, + None => Ok(()), + } } - /// Search for any [`AccessMethod`] in `api_access_methods` which matches `predicate`. - pub fn find<P>(&self, predicate: P) -> Option<&AccessMethodSetting> - where - P: Fn(&AccessMethodSetting) -> bool, - { - self.access_method_settings - .iter() - .find(|api_access_method| predicate(api_access_method)) + /// Update an existing [`AccessMethodSetting`] chosen by `predicate`, in a + /// closure `f`, saving the result to `self`. + /// + /// Returns a bool to indicate whether some [`AccessMethodSetting`] was + /// updated. + pub fn update( + &mut self, + predicate: impl Fn(&AccessMethodSetting) -> bool, + f: impl FnOnce(&AccessMethodSetting) -> AccessMethodSetting, + ) -> bool { + let mut updated = false; + if let Some(access_method) = self.iter_mut().find(|setting| predicate(setting)) { + *access_method = f(access_method); + updated = true; + } + self.ensure_consistent_state(); + + updated } - /// Search for any [`AccessMethod`] in `api_access_methods`. - pub fn find_mut<P>(&mut self, predicate: P) -> Option<&mut AccessMethodSetting> - where - P: Fn(&AccessMethodSetting) -> bool, - { - self.access_method_settings - .iter_mut() - .find(|api_access_method| predicate(api_access_method)) + /// Check that `self` contains atleast one enabled access methods. If not, + /// the `Direct` access method is re-enabled. + fn ensure_consistent_state(&mut self) { + if self.collect_enabled().is_empty() { + self.direct.enable(); + } } - /// Search for a particular [`AccessMethod`] in `api_access_methods`. - pub fn find_by_id(&self, element: &Id) -> Option<&AccessMethodSetting> { - self.find(|api_access_method| *element == api_access_method.get_id()) + // TODO(markus): This can surely be removed. + /// Retrieve all [`AccessMethodSetting`]s which are enabled. + pub fn collect_enabled(&self) -> Vec<AccessMethodSetting> { + self.iter() + .filter(|access_method| access_method.enabled) + .cloned() + .collect() } - /// Search for a particular [`AccessMethod`] in `api_access_methods`. - pub fn find_by_id_mut(&mut self, element: &Id) -> Option<&mut AccessMethodSetting> { - self.find_mut(|api_access_method| *element == api_access_method.get_id()) + /// Iterate over references of built-in & custom access methods. + pub fn iter(&self) -> impl Iterator<Item = &AccessMethodSetting> { + use std::iter::once; + once(&self.direct) + .chain(once(&self.mullvad_bridges)) + .chain(&self.custom) } - /// Equivalent to [`Vec::retain`]. - pub fn retain<F>(&mut self, f: F) - where - F: FnMut(&AccessMethodSetting) -> bool, - { - self.access_method_settings.retain(f) + /// Iterate over mutable references of built-in & custom access methods. + fn iter_mut(&mut self) -> impl Iterator<Item = &mut AccessMethodSetting> { + use std::iter::once; + once(&mut self.direct) + .chain(once(&mut self.mullvad_bridges)) + .chain(&mut self.custom) } - /// Clone the content of `api_access_methods`. - pub fn cloned(&self) -> Vec<AccessMethodSetting> { - self.access_method_settings.clone() + /// Iterate over references of custom access methods. + pub fn iter_custom(&self) -> impl Iterator<Item = &AccessMethodSetting> { + self.custom.iter() } - /// Get a reference to the `Direct` access method instance of this [`Settings`]. - pub fn get_direct(&mut self) -> Option<&mut AccessMethodSetting> { - self.find_mut(|access_method| { - access_method.access_method == BuiltInAccessMethod::Direct.into() - }) + /// Return the total number of access methods. + /// This counts both enabled and disabled [`AccessMethodSetting`]s. + pub fn cardinality(&self) -> usize { + 1 + // 'Direct' + 1 + // 'Mullvad bridges' + self.custom.len() } - pub fn direct() -> AccessMethodSetting { + pub fn direct(&self) -> &AccessMethodSetting { + &self.direct + } + + pub fn mullvad_bridges(&self) -> &AccessMethodSetting { + &self.mullvad_bridges + } + + // TODO(markus): This can probably be made private + pub fn create_direct() -> AccessMethodSetting { let method = BuiltInAccessMethod::Direct; AccessMethodSetting::new(method.canonical_name(), true, AccessMethod::from(method)) } - pub fn mullvad_bridges() -> AccessMethodSetting { + fn create_mullvad_bridges() -> AccessMethodSetting { let method = BuiltInAccessMethod::Bridge; AccessMethodSetting::new(method.canonical_name(), true, AccessMethod::from(method)) } - - /// Retrieve all [`AccessMethodSetting`]s which are enabled. - pub fn collect_enabled(&self) -> Vec<AccessMethodSetting> { - self.cloned() - .into_iter() - .filter(|access_method| access_method.enabled) - .collect() - } } impl Default for Settings { fn default() -> Self { Self { - access_method_settings: vec![Settings::direct(), Settings::mullvad_bridges()], + direct: Settings::create_direct(), + mullvad_bridges: Settings::create_mullvad_bridges(), + custom: vec![], } } } +#[derive(err_derive::Error, Debug)] +pub enum Error { + /// Built-in access methods can not be removed + #[error(display = "Cannot remove built-in access method {}", attempted)] + RemoveBuiltin { attempted: BuiltInAccessMethod }, +} + /// API Access Method datastructure /// /// Mirrors the protobuf definition @@ -120,6 +180,7 @@ impl Id { /// Tries to parse a UUID from a raw String. If it is successful, an /// [`Id`] is instantiated. pub fn from_string(id: String) -> Option<Self> { + use std::str::FromStr; uuid::Uuid::from_str(&id).ok().map(Self) } } @@ -178,6 +239,10 @@ impl AccessMethodSetting { self.enabled } + pub fn disabled(&self) -> bool { + !self.enabled + } + pub fn as_custom(&self) -> Option<&CustomProxy> { self.access_method.as_custom() } @@ -223,6 +288,12 @@ impl BuiltInAccessMethod { } } +impl std::fmt::Display for BuiltInAccessMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.canonical_name()) + } +} + impl From<BuiltInAccessMethod> for AccessMethod { fn from(value: BuiltInAccessMethod) -> Self { AccessMethod::BuiltIn(value) diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index e6886c9e3c..af8c11e8d7 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -79,7 +79,7 @@ pub struct Settings { /// All of the custom relay lists #[cfg_attr(target_os = "android", jnix(skip))] pub custom_lists: CustomListsSettings, - /// API access methods. + /// API access methods #[cfg_attr(target_os = "android", jnix(skip))] pub api_access_methods: access_method::Settings, /// If the daemon should allow communication with private (LAN) networks. |
