From 353dacc4a8d840ce8c3ddb1c19eebda4d821a9c8 Mon Sep 17 00:00:00 2001 From: Ghenadie <118184705+GhenadieVP@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:53:20 +0200 Subject: [PATCH 1/2] Keep HostId in memory (#303) * store host id in memory * fmt * wip * wi[ * wip * wip --- Cargo.lock | 4 +- crates/sargon-uniffi/Cargo.toml | 2 +- .../src/system/sargon_os/sargon_os.rs | 4 +- crates/sargon/Cargo.toml | 2 +- .../sargon/src/system/sargon_os/sargon_os.rs | 50 +++++++++++-------- .../src/system/sargon_os/sargon_os_profile.rs | 5 +- 6 files changed, 36 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4a1d85c9..962e444a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.82" +version = "1.1.83" dependencies = [ "actix-rt", "aes-gcm", @@ -2827,7 +2827,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.82" +version = "1.1.83" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index e1a3c3d74..2d6780862 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.82" +version = "1.1.83" edition = "2021" build = "build.rs" diff --git a/crates/sargon-uniffi/src/system/sargon_os/sargon_os.rs b/crates/sargon-uniffi/src/system/sargon_os/sargon_os.rs index d8e7fe391..4a15696e0 100644 --- a/crates/sargon-uniffi/src/system/sargon_os/sargon_os.rs +++ b/crates/sargon-uniffi/src/system/sargon_os/sargon_os.rs @@ -75,8 +75,8 @@ impl SargonOS { self.wrapped.delete_wallet().await.into_result() } - pub async fn resolve_host_id(&self) -> Result { - self.wrapped.resolve_host_id().await.into_result() + pub fn host_id(&self) -> HostId { + self.wrapped.host_id().into() } pub async fn resolve_host_info(&self) -> HostInfo { diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index 4b67ab8d8..f22b0e8a8 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.82" +version = "1.1.83" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/system/sargon_os/sargon_os.rs b/crates/sargon/src/system/sargon_os/sargon_os.rs index 8a05114d3..a046a6090 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os.rs @@ -14,6 +14,7 @@ pub struct SargonOS { pub(crate) profile_state_holder: ProfileStateHolder, pub(crate) clients: Clients, pub(crate) interactors: Interactors, + pub(crate) host_id: HostId, } /// So that we do not have to go through `self.clients`, @@ -68,12 +69,14 @@ impl SargonOS { profile_state = ProfileState::None; } + let host_id = Self::get_host_id(&clients).await; let os = Arc::new(Self { clients, profile_state_holder: ProfileStateHolder::new( profile_state.clone(), ), interactors, + host_id, }); os.clients .profile_state_change @@ -227,12 +230,11 @@ impl SargonOS { .save_private_hd_factor_source(&hd_factor_source) .await?; - let host_id = self.host_id().await?; let host_info = self.host_info().await; let profile = Profile::from_device_factor_source( hd_factor_source.factor_source, - host_id, + self.host_id, host_info, Some(accounts), ); @@ -281,8 +283,8 @@ impl SargonOS { as Arc } - pub async fn resolve_host_id(&self) -> Result { - self.host_id().await + pub fn host_id(&self) -> HostId { + self.host_id } pub async fn resolve_host_info(&self) -> HostInfo { @@ -297,7 +299,6 @@ impl SargonOS { ) -> Result<(Profile, PrivateHierarchicalDeterministicFactorSource)> { debug!("Creating new Profile and BDFS"); - let host_id = self.host_id().await?; let host_info = self.host_info().await; let is_main = true; @@ -325,7 +326,7 @@ impl SargonOS { debug!("Creating new Profile..."); let profile = Profile::with( - Header::new(DeviceInfo::new_from_info(&host_id, &host_info)), + Header::new(DeviceInfo::new_from_info(&self.host_id, &host_info)), FactorSources::with_bdfs(private_bdfs.factor_source.clone()), AppPreferences::default(), ProfileNetworks::default(), @@ -334,26 +335,34 @@ impl SargonOS { Ok((profile, private_bdfs)) } - pub(crate) async fn host_id(&self) -> Result { - Self::get_host_id(&self.clients).await - } - - pub(crate) async fn get_host_id(clients: &Clients) -> Result { + pub(crate) async fn get_host_id(clients: &Clients) -> HostId { debug!("Get Host ID"); let secure_storage = &clients.secure_storage; + let stored_host_id = secure_storage.load_host_id().await; - match secure_storage.load_host_id().await? { - Some(loaded_host_id) => { + match stored_host_id { + Ok(Some(loaded_host_id)) => { debug!("Found saved host id: {:?}", &loaded_host_id); - Ok(loaded_host_id) + loaded_host_id } - None => { + Ok(None) => { debug!("Found no saved host id, creating new."); let new_host_id = HostId::generate_new(); debug!("Created new host id: {:?}", &new_host_id); - secure_storage.save_host_id(&new_host_id).await?; - debug!("Saved new host id"); - Ok(new_host_id) + + let save_result = + secure_storage.save_host_id(&new_host_id).await; + if let Err(error) = save_result { + debug!("Failed to save new host id {:?}", error); + } else { + debug!("Saved new host id"); + } + new_host_id + } + Err(error) => { + debug!("Failed to load the host id {:?}", error); + + HostId::generate_new() } } } @@ -756,10 +765,7 @@ mod tests { async fn test_resolve_host_id() { let os = SUT::fast_boot().await; - assert_eq!( - os.resolve_host_id().await.unwrap(), - os.host_id().await.unwrap() - ) + assert_eq!(SargonOS::get_host_id(&os.clients).await, os.host_id()) } #[actix_rt::test] diff --git a/crates/sargon/src/system/sargon_os/sargon_os_profile.rs b/crates/sargon/src/system/sargon_os/sargon_os_profile.rs index eed6e6d08..73c60c328 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_profile.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_profile.rs @@ -21,10 +21,9 @@ impl SargonOS { /// header pub async fn claim_profile(&self, profile: &mut Profile) -> Result<()> { debug!("Claiming profile, id: {}", &profile.id()); - let host_id = self.host_id().await?; let host_info = self.host_info().await; let claiming_device_info = - DeviceInfo::new_from_info(&host_id, &host_info); + DeviceInfo::new_from_info(&self.host_id, &host_info); Self::claim_provided_profile(profile, claiming_device_info); info!( @@ -345,7 +344,7 @@ mod tests { .unwrap(); // ASSERT - let host_id = os.host_id().await.unwrap(); + let host_id = os.host_id; let host_info = os.host_info().await; assert_eq!( os.profile().unwrap().header.last_used_on_device, From 56c6e5872ce78fe6c762f206632c01438e34aba9 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Thu, 12 Dec 2024 20:10:47 +0100 Subject: [PATCH 2/2] Automatic Assignment of Factors to Recovery & Confirmation Role (#299) --- Cargo.lock | 4 +- crates/sargon-uniffi/Cargo.toml | 2 +- .../src/core/error/common_error.rs | 3 + .../types/collections/internal_mapping.rs | 51 +- .../src/core/types/requested_quantity.rs | 2 +- .../keys_collector/key_derivation_request.rs | 8 +- .../keys_collector/key_derivation_response.rs | 7 +- .../security_shield_builder.rs | 151 ++- .../src/signing/neglected_factors.rs | 9 +- .../src/signing/sign_response.rs | 5 +- crates/sargon/Cargo.toml | 2 +- crates/sargon/src/core/error/common_error.rs | 3 + .../src/core/types/requested_quantity.rs | 64 +- .../account/query_security_structures.rs | 20 +- .../auto_build_outcome_for_testing.rs | 38 + .../automatic_shield_builder.rs | 919 ++++++++++++++++++ .../factor_selector.rs | 9 + .../automatic_shield_builder/mod.rs | 10 + .../automatic_shield_builder/proto_matrix.rs | 45 + .../automatic_shield_builder/quantity.rs | 17 + .../matrices/builder/matrix_builder.rs | 5 + .../builder/matrix_builder_unit_tests.rs | 84 +- .../profile/mfa/security_structures/mod.rs | 2 + .../roles/abstract_role_builder_or_built.rs | 21 +- .../confirmation_roles_builder_unit_tests.rs | 20 +- .../primary_roles_builder_unit_tests.rs | 62 +- .../recovery_roles_builder_unit_tests.rs | 22 +- .../roles/builder/roles_builder.rs | 15 +- .../security_shield_builder.rs | 38 + .../security_shield_prerequisites_status.rs | 2 +- ...security_structure_of_factor_source_ids.rs | 3 + .../profile/v100/app_preferences/security.rs | 3 - .../v100/factors/factor_source_category.rs | 7 +- .../profile/v100/factors/factor_source_id.rs | 6 + .../authorized_dapp/shared_with_dapp.rs | 4 +- 35 files changed, 1446 insertions(+), 217 deletions(-) create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/auto_build_outcome_for_testing.rs create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/factor_selector.rs create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs create mode 100644 crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/quantity.rs diff --git a/Cargo.lock b/Cargo.lock index 962e444a5..452b558b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.83" +version = "1.1.84" dependencies = [ "actix-rt", "aes-gcm", @@ -2827,7 +2827,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.83" +version = "1.1.84" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index 2d6780862..0d2cd7a2d 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.83" +version = "1.1.84" edition = "2021" build = "build.rs" diff --git a/crates/sargon-uniffi/src/core/error/common_error.rs b/crates/sargon-uniffi/src/core/error/common_error.rs index 7b43af923..c75ab7c86 100644 --- a/crates/sargon-uniffi/src/core/error/common_error.rs +++ b/crates/sargon-uniffi/src/core/error/common_error.rs @@ -827,6 +827,9 @@ pub enum CommonError { #[error("Signing was rejected by the user")] SigningRejected = 10232, + + #[error("Failed to automatically build shield, reason: '{underlying}'")] + AutomaticShieldBuildingFailure { underlying: String } = 10233, } #[uniffi::export] diff --git a/crates/sargon-uniffi/src/core/types/collections/internal_mapping.rs b/crates/sargon-uniffi/src/core/types/collections/internal_mapping.rs index e1ad44a78..e6e0aa4e3 100644 --- a/crates/sargon-uniffi/src/core/types/collections/internal_mapping.rs +++ b/crates/sargon-uniffi/src/core/types/collections/internal_mapping.rs @@ -1,13 +1,15 @@ use crate::prelude::*; -use sargon::IdentifiedVecOf; - -// From InternalType ================================================================================================= +use sargon::{IdentifiedVecOf, IndexSet}; +// ========================== +// === From InternalType ==== +// ========================== pub trait FromInternal { fn into_type(self) -> Type; } +// ===== Vec ====== impl FromInternal, Vec> for Vec where @@ -18,6 +20,7 @@ where } } +// === IdentifiedVec ==== impl FromInternal, Vec> for IdentifiedVecOf @@ -30,30 +33,58 @@ where } } -// Into InternalType ================================================================================================= +// ==== IndexSet ====== +impl + FromInternal, Vec> + for IndexSet +where + Element: From, +{ + fn into_type(self) -> Vec { + self.into_iter().map(Element::from).collect() + } +} + +// ========================== +// === Into InternalType ==== +// ========================== pub trait IntoInternal { fn into_internal(self) -> InternalType; } -impl - IntoInternal, IdentifiedVecOf> +// ===== Vec ======== +impl IntoInternal, Vec> for Vec where - InternalElement: Debug + PartialEq + Eq + Clone + sargon::Identifiable, Element: Into, { - fn into_internal(self) -> IdentifiedVecOf { + fn into_internal(self) -> Vec { self.into_iter().map(Into::into).collect() } } -impl IntoInternal, Vec> +// ==== IndexSet ====== +impl + IntoInternal, IndexSet> for Vec +where + Element: Into, + InternalElement: std::hash::Hash + Eq, +{ + fn into_internal(self) -> IndexSet { + self.into_iter().map(Into::into).collect::>() + } +} + +// === IdentifiedVec ==== +impl + IntoInternal, IdentifiedVecOf> for Vec where + InternalElement: Debug + PartialEq + Eq + Clone + sargon::Identifiable, Element: Into, { - fn into_internal(self) -> Vec { + fn into_internal(self) -> IdentifiedVecOf { self.into_iter().map(Into::into).collect() } } diff --git a/crates/sargon-uniffi/src/core/types/requested_quantity.rs b/crates/sargon-uniffi/src/core/types/requested_quantity.rs index 0fa7c879d..ec9ae8a05 100644 --- a/crates/sargon-uniffi/src/core/types/requested_quantity.rs +++ b/crates/sargon-uniffi/src/core/types/requested_quantity.rs @@ -40,5 +40,5 @@ pub fn requested_quantity_is_fulfilled_by_ids( ) -> bool { requested_quantity .into_internal() - .is_fulfilled_by_ids(number_of_ids as usize) + .is_fulfilled_by_quantity(number_of_ids as usize) } diff --git a/crates/sargon-uniffi/src/keys_collector/key_derivation_request.rs b/crates/sargon-uniffi/src/keys_collector/key_derivation_request.rs index 05f632b13..958ffa711 100644 --- a/crates/sargon-uniffi/src/keys_collector/key_derivation_request.rs +++ b/crates/sargon-uniffi/src/keys_collector/key_derivation_request.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use sargon::IndexMap; -use sargon::{IndexSet, KeyDerivationRequest as InternalKeyDerivationRequest}; +use sargon::KeyDerivationRequest as InternalKeyDerivationRequest; /// A collection of derivation paths, on a per-factor-source basis. #[derive(Clone, PartialEq, Eq, uniffi::Record)] @@ -61,11 +61,7 @@ impl From for InternalKeyDerivationRequest { IndexMap::from_iter(value.per_factor_source.into_iter().map(|f| { ( f.factor_source_id.into_internal(), - IndexSet::from_iter( - f.derivation_paths - .into_iter() - .map(|d| d.into_internal()), - ), + f.derivation_paths.into_internal(), ) })), ) diff --git a/crates/sargon-uniffi/src/keys_collector/key_derivation_response.rs b/crates/sargon-uniffi/src/keys_collector/key_derivation_response.rs index 4cb2cb3b6..6e51c3f5d 100644 --- a/crates/sargon-uniffi/src/keys_collector/key_derivation_response.rs +++ b/crates/sargon-uniffi/src/keys_collector/key_derivation_response.rs @@ -1,6 +1,5 @@ use crate::prelude::*; use sargon::IndexMap; -use sargon::IndexSet; use sargon::KeyDerivationResponse as InternalKeyDerivationResponse; /// A collection of `HierarchicalDeterministicFactorInstance`s, on a @@ -58,11 +57,7 @@ impl From for InternalKeyDerivationResponse { value.per_factor_source.into_iter().map(|item| { ( item.factor_source_id.into_internal(), - IndexSet::from_iter( - item.factor_instances - .into_iter() - .map(|d| d.into_internal()), - ), + item.factor_instances.into_internal(), ) }), )) diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs index 56de271fc..981b08eb6 100644 --- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs +++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs @@ -4,12 +4,14 @@ use std::{ borrow::Borrow, + future::Future, sync::{Arc, RwLock}, }; -use sargon::SecurityShieldBuilder as InternalSecurityShieldBuilder; -use sargon::SelectedFactorSourcesForRoleStatus as InternalSelectedFactorSourcesForRoleStatus; -use sargon::{IndexSet, MatrixBuilder}; +use sargon::{ + SecurityShieldBuilder as InternalSecurityShieldBuilder, + SelectedFactorSourcesForRoleStatus as InternalSelectedFactorSourcesForRoleStatus, +}; use crate::prelude::*; @@ -378,6 +380,23 @@ impl SecurityShieldBuilder { } } +use sargon::FactorSource as InternalFactorSource; + +#[uniffi::export] +impl SecurityShieldBuilder { + pub fn auto_assign_factors_to_recovery_and_confirmation_based_on_primary( + &self, + all_factors: Vec, + ) -> Result<()> { + let binding = self.wrapped.write().expect("No poison"); + let _ = binding + .auto_assign_factors_to_recovery_and_confirmation_based_on_primary( + all_factors.into_internal(), + ); + Ok(()) + } +} + #[uniffi::export] impl SecurityShieldBuilder { pub fn validate(&self) -> Option { @@ -455,6 +474,10 @@ impl FactorSourceID { Self::new(sargon::FactorSourceID::sample_ledger_other()) } + pub fn sample_trusted_contact() -> Self { + Self::new(sargon::FactorSourceID::sample_trusted_contact()) + } + pub fn sample_arculus() -> Self { Self::new(sargon::FactorSourceID::sample_arculus()) } @@ -472,6 +495,48 @@ impl FactorSourceID { } } +impl FactorSource { + pub fn new(inner: impl Borrow) -> Self { + Self::from(inner.borrow().clone()) + } +} + +#[cfg(test)] +impl FactorSource { + pub fn id(&self) -> FactorSourceID { + use sargon::BaseBaseIsFactorSource; + self.clone().into_internal().factor_source_id().into() + } + + pub fn sample_device() -> Self { + Self::new(sargon::FactorSource::sample_device()) + } + pub fn sample_password() -> Self { + Self::new(sargon::FactorSource::sample_password()) + } + pub fn sample_trusted_contact_frank() -> Self { + Self::new(sargon::FactorSource::sample_trusted_contact_frank()) + } + pub fn sample_device_babylon() -> Self { + Self::new(sargon::FactorSource::sample_device_babylon()) + } + pub fn sample_device_babylon_other() -> Self { + Self::new(sargon::FactorSource::sample_device_babylon_other()) + } + pub fn sample_ledger() -> Self { + Self::new(sargon::FactorSource::sample_ledger()) + } + pub fn sample_arculus() -> Self { + Self::new(sargon::FactorSource::sample_arculus()) + } + pub fn sample_arculus_other() -> Self { + Self::new(sargon::FactorSource::sample_arculus_other()) + } + pub fn sample_ledger_other() -> Self { + Self::new(sargon::FactorSource::sample_ledger_other()) + } +} + #[cfg(test)] mod tests { @@ -713,4 +778,84 @@ mod tests { vec![FactorSourceID::sample_device()] ); } + + #[test] + fn auto_assign() { + let sut = SUT::new(); + let all_factors_in_profile = vec![ + FactorSource::sample_password(), + FactorSource::sample_trusted_contact_frank(), + FactorSource::sample_device_babylon(), + FactorSource::sample_device_babylon_other(), + FactorSource::sample_ledger(), + FactorSource::sample_arculus(), + FactorSource::sample_arculus_other(), + FactorSource::sample_ledger_other(), + ]; + let name = "Auto Built"; + let days_to_auto_confirm = 237; + sut.set_name(name.to_owned()); + sut.set_number_of_days_until_auto_confirm(days_to_auto_confirm); + sut.set_threshold(2); + sut.add_factor_source_to_primary_threshold( + FactorSource::sample_device_babylon().id(), + ); + sut.add_factor_source_to_primary_threshold( + FactorSource::sample_ledger().id(), + ); + + sut.auto_assign_factors_to_recovery_and_confirmation_based_on_primary( + all_factors_in_profile.clone(), + ) + .unwrap(); + + let shield = sut.build().unwrap(); + + assert_eq!(shield.metadata.display_name.value, name.to_owned()); + let matrix = shield.matrix_of_factors; + assert_eq!( + matrix.number_of_days_until_auto_confirm, + days_to_auto_confirm + ); + + pretty_assertions::assert_eq!( + matrix.primary_role, + PrimaryRoleWithFactorSourceIDs { + threshold: 2, + threshold_factors: vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + override_factors: Vec::new() + } + ); + + pretty_assertions::assert_eq!( + matrix.recovery_role, + RecoveryRoleWithFactorSourceIDs { + threshold: 0, + threshold_factors: Vec::new(), + override_factors: vec![ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus_other(), + FactorSourceID::sample_ledger_other(), + ] + } + ); + + pretty_assertions::assert_eq!( + matrix.confirmation_role, + ConfirmationRoleWithFactorSourceIDs { + threshold: 0, + threshold_factors: Vec::new(), + override_factors: vec![ + FactorSourceID::sample_password(), + FactorSourceID::sample_arculus(), + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ] + } + ); + } } diff --git a/crates/sargon-uniffi/src/signing/neglected_factors.rs b/crates/sargon-uniffi/src/signing/neglected_factors.rs index fba3501ee..c545e2152 100644 --- a/crates/sargon-uniffi/src/signing/neglected_factors.rs +++ b/crates/sargon-uniffi/src/signing/neglected_factors.rs @@ -31,14 +31,7 @@ impl From for NeglectedFactors { impl From for InternalNeglectedFactors { fn from(value: NeglectedFactors) -> Self { - Self::new( - value.reason.into_internal(), - value - .factors - .into_iter() - .map(|id| id.into_internal()) - .collect::>(), - ) + Self::new(value.reason.into_internal(), value.factors.into_internal()) } } diff --git a/crates/sargon-uniffi/src/signing/sign_response.rs b/crates/sargon-uniffi/src/signing/sign_response.rs index 65f775e69..e629402f5 100644 --- a/crates/sargon-uniffi/src/signing/sign_response.rs +++ b/crates/sargon-uniffi/src/signing/sign_response.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use paste::paste; use sargon::IndexMap; -use sargon::IndexSet; macro_rules! decl_sign_response { ( @@ -47,9 +46,7 @@ macro_rules! decl_sign_response { |item| { ( item.factor_source_id.into_internal(), - sargon::IndexSet::from_iter( - item.hd_signatures.into_iter().map(|s| s.into_internal()), - ), + item.hd_signatures.into_internal() ) }, )), diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index f22b0e8a8..76e3c5bb0 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.83" +version = "1.1.84" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/core/error/common_error.rs b/crates/sargon/src/core/error/common_error.rs index ee08859ff..4528817a7 100644 --- a/crates/sargon/src/core/error/common_error.rs +++ b/crates/sargon/src/core/error/common_error.rs @@ -824,6 +824,9 @@ pub enum CommonError { #[error("Signing was rejected by the user")] SigningRejected = 10232, + + #[error("Failed to automatically build shield, reason: '{underlying}'")] + AutomaticShieldBuildingFailure { underlying: String } = 10233, } impl CommonError { diff --git a/crates/sargon/src/core/types/requested_quantity.rs b/crates/sargon/src/core/types/requested_quantity.rs index 6727e7411..93df2cdb3 100644 --- a/crates/sargon/src/core/types/requested_quantity.rs +++ b/crates/sargon/src/core/types/requested_quantity.rs @@ -7,6 +7,7 @@ use crate::prelude::*; Deserialize, Debug, Clone, + Copy, PartialEq, Eq, Hash, @@ -44,19 +45,25 @@ impl RequestedQuantity { } } - /// Checks `len` can fulfill the [`RequestedQuantity`] (self), `len` is - /// considered to be fulfilling the requested quantity: - /// * if: quantifier == ::Exactly && len == quantity // ✅ fulfills - /// * else if: quantifier == ::AtLeast && len >= quantity // ✅ fulfills - /// * else false // ❌ does NOT fulfill - pub fn is_fulfilled_by_ids(&self, len: usize) -> bool { - let quantity = self.quantity as usize; + pub fn left_until_fulfilled(&self, actual: usize) -> i32 { + let actual = actual as i32; + let quantity = self.quantity as i32; + let diff = quantity - actual; match self.quantifier { - RequestedNumberQuantifier::Exactly => len == quantity, - RequestedNumberQuantifier::AtLeast => len >= quantity, + RequestedNumberQuantifier::Exactly => diff, + RequestedNumberQuantifier::AtLeast => diff.max(0), } } + /// Checks `actual` can fulfill the [`RequestedQuantity`] (self), `actual` is + /// considered to be fulfilling the requested quantity: + /// * if: quantifier == ::Exactly && actual == quantity // ✅ fulfills + /// * else if: quantifier == ::AtLeast && actual >= quantity // ✅ fulfills + /// * else false // ❌ does NOT fulfill + pub fn is_fulfilled_by_quantity(&self, actual: usize) -> bool { + self.left_until_fulfilled(actual) == 0 + } + pub fn exactly(quantity: u16) -> Self { let value = Self { quantifier: RequestedNumberQuantifier::Exactly, @@ -114,30 +121,43 @@ mod tests { #[test] fn at_least_fulfills_true() { - assert!(SUT::at_least(0).is_fulfilled_by_ids(0)); - assert!(SUT::at_least(0).is_fulfilled_by_ids(1)); - assert!(SUT::at_least(1).is_fulfilled_by_ids(1)); - assert!(SUT::at_least(1).is_fulfilled_by_ids(2)); + assert!(SUT::at_least(0).is_fulfilled_by_quantity(0)); + assert!(SUT::at_least(0).is_fulfilled_by_quantity(1)); + assert!(SUT::at_least(1).is_fulfilled_by_quantity(1)); + assert!(SUT::at_least(1).is_fulfilled_by_quantity(2)); } #[test] fn at_least_fulfills_false() { - assert!(!SUT::at_least(1).is_fulfilled_by_ids(0)); - assert!(!SUT::at_least(10).is_fulfilled_by_ids(0)); - assert!(!SUT::at_least(10).is_fulfilled_by_ids(9)); + assert!(!SUT::at_least(1).is_fulfilled_by_quantity(0)); + assert!(!SUT::at_least(10).is_fulfilled_by_quantity(0)); + assert!(!SUT::at_least(10).is_fulfilled_by_quantity(9)); } #[test] fn exactly_fulfills_true() { - assert!(SUT::exactly(1).is_fulfilled_by_ids(1)); - assert!(SUT::exactly(10).is_fulfilled_by_ids(10)); + assert!(SUT::exactly(1).is_fulfilled_by_quantity(1)); + assert!(SUT::exactly(10).is_fulfilled_by_quantity(10)); } #[test] fn exactly_fulfills_false() { - assert!(!SUT::exactly(1).is_fulfilled_by_ids(0)); - assert!(!SUT::exactly(1).is_fulfilled_by_ids(2)); - assert!(!SUT::exactly(10).is_fulfilled_by_ids(9)); - assert!(!SUT::exactly(10).is_fulfilled_by_ids(11)); + assert!(!SUT::exactly(1).is_fulfilled_by_quantity(0)); + assert!(!SUT::exactly(1).is_fulfilled_by_quantity(2)); + assert!(!SUT::exactly(10).is_fulfilled_by_quantity(9)); + assert!(!SUT::exactly(10).is_fulfilled_by_quantity(11)); + } + + #[test] + fn left_until_fulfilled() { + assert_eq!(SUT::exactly(5).left_until_fulfilled(1), 4); + assert_eq!(SUT::exactly(9).left_until_fulfilled(3), 6); + assert_eq!(SUT::exactly(5).left_until_fulfilled(6), -1); + assert_eq!(SUT::exactly(9).left_until_fulfilled(17), -8); + + assert_eq!(SUT::at_least(5).left_until_fulfilled(1), 4); + assert_eq!(SUT::at_least(9).left_until_fulfilled(2), 7); + assert_eq!(SUT::at_least(5).left_until_fulfilled(7), 0); + assert_eq!(SUT::at_least(13).left_until_fulfilled(18), 0); } } diff --git a/crates/sargon/src/profile/logic/account/query_security_structures.rs b/crates/sargon/src/profile/logic/account/query_security_structures.rs index 5b9885408..04b96c3d7 100644 --- a/crates/sargon/src/profile/logic/account/query_security_structures.rs +++ b/crates/sargon/src/profile/logic/account/query_security_structures.rs @@ -48,22 +48,12 @@ impl Profile { pub fn security_shield_prerequisites_status( &self, ) -> SecurityShieldPrerequisitesStatus { - let factor_sources = self.factor_sources.clone(); - let count_excluding_identity = factor_sources + let factor_source_ids = self + .factor_sources .iter() - .filter(|f| f.category() != FactorSourceCategory::Identity) - .count(); - let count_hardware = factor_sources - .iter() - .filter(|f| f.category() == FactorSourceCategory::Hardware) - .count(); - if count_hardware < 1 { - SecurityShieldPrerequisitesStatus::HardwareRequired - } else if count_excluding_identity < 2 { - SecurityShieldPrerequisitesStatus::AnyRequired - } else { - SecurityShieldPrerequisitesStatus::Sufficient - } + .map(|f| f.id()) + .collect::>(); + SecurityShieldBuilder::prerequisites_status(&factor_source_ids) } } diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/auto_build_outcome_for_testing.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/auto_build_outcome_for_testing.rs new file mode 100644 index 000000000..7d0c95188 --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/auto_build_outcome_for_testing.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; + +/// For testing purposes +/// We do not support Custodian FactorSource yet, but I wanted to write the +/// heuristics being future proof, so instead of actually assigning any Custodian +/// (which does not exist), we record the calls to assign Custodian using this +/// small struct, so that we can assert correctness of the heuristics. +/// +/// When we do add Custodian FactorSource, we can remove this struct and just +/// assert the actual assignment of Custodian factors... +#[derive(Default)] +pub struct AutoBuildOutcomeForTesting { + pub(super) calls_to_assign_unsupported_factor: + Vec, +} + +/// For testing purposes +/// We do not support Custodian FactorSource yet, but I wanted to write the +/// heuristics being future proof, so instead of actually assigning any Custodian +/// (which does not exist), we record the calls to assign Custodian using this +/// small struct, so that we can assert correctness of the heuristics. +/// +/// When we do add Custodian FactorSource, we can remove this struct and just +/// assert the actual assignment of Custodian factors... +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct CallsToAssignUnsupportedFactor { + /// The role. + pub(super) role: RoleKind, + + /// FactorSelector + pub(super) unsupported: FactorSelector, + + /// The number of factors in the role when `assign_custodian_and_hardware_to_role_if_less_than_limit_before_each_assignment` was called + pub(super) number_of_factors_for_role: u8, + + /// The value of `limit` parameter passed to `assign_custodian_and_hardware_to_role_if_less_than_limit_before_each_assignment` + pub(super) limit: u8, +} diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs new file mode 100644 index 000000000..76eb2a063 --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs @@ -0,0 +1,919 @@ +use crate::prelude::*; + +use super::{ + proto_matrix::ProtoMatrix, quantity::Quantity, + CallsToAssignUnsupportedFactor, +}; + +use FactorSourceCategory::*; +use RoleKind::*; + +impl FactorSourceCategory { + fn is_supported(&self) -> bool { + match self { + Identity | Hardware | Contact | Information => true, + Custodian => false, + } + } +} + +/// A crate internal helper builder which assigns factors to +/// Recovery and Confirmation roles based on the heuristic +/// laid out in ["Automatic Security Shield Construction" document][doc] +/// +/// [doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields#Automatic-Security-Shield-Construction +pub(crate) struct AutomaticShieldBuilder { + /// Only used for testing purposes, feel free to remove. + stats_for_testing: AutoBuildOutcomeForTesting, + + /// While we assign factors to Recovery and Confirmation roles, we remove + /// them from this set. + remaining_available_factors: IndexSet, + + /// The factors assigned to each role, including the factors originally + /// set for the primary role. + proto_matrix: ProtoMatrix, +} + +impl SecurityShieldBuilder { + /// Assigns the factors to the Recovery and Confirmation roles according to the heuristic + /// laid out in ["Automatic Security Shield Construction" document][doc]. + /// + /// The `all_factors_in_profile` should contain all factors that are available for the user, + /// i.e. from Profile, and SHOULD contain the factors that the user has preselected for the + /// Primary role. + /// + /// # Throws + /// Throws if the primary role is invalid. + /// Throws if the primary override factors are not empty. + /// Throws if the primary factors are not in the profile. + /// Throws if the prerequisites are not met. + /// Throws if the shield is invalid after auto assignment - which should not happen, can be considered programmer error! + /// + /// [doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields#Automatic-Security-Shield-Construction + pub fn auto_assign_factors_to_recovery_and_confirmation_based_on_primary( + &self, + all_factors_in_profile: IndexSet, + ) -> Result +/* Feel free to replace `AutoBuildOutcomeForTesting` return type if you need anything else, I had Unit, so might as well make testing easier by returning this type. */ + { + if let Some(invalid_reason) = self.validate_primary_role() { + return Err(CommonError::AutomaticShieldBuildingFailure { + underlying: format!( + "Primary role is not valid: {:?}", + invalid_reason + ), + }); + } + + if !self.get_primary_override_factors().is_empty() { + // Can we update this auto assign heuristics to allow primary override factors? + // If we would allow it, we would need to remove all those factors in override + // from `all_factors_in_profile`. + return Err(CommonError::AutomaticShieldBuildingFailure { + underlying: "Primary override factors not allowed when preselecting factors for Recovery and Confirmation".to_string(), + }); + } + + let primary_factors = self + .get_primary_threshold_factors() + .into_iter() + .collect::>(); + + if primary_factors + .intersection( + &all_factors_in_profile + .iter() + .map(|f| f.id()) + .collect::>(), + ) + .cloned() + .collect::>() + != primary_factors + { + return Err(CommonError::AutomaticShieldBuildingFailure { + underlying: "Primary factors not in profile".to_string(), + }); + } + + if !Self::prerequisites_status( + &all_factors_in_profile.iter().map(|f| f.id()).collect(), + ) + .is_sufficient() + { + return Err(CommonError::AutomaticShieldBuildingFailure { + underlying: "Prerequisites not met".to_string(), + }); + } + + let mut auto_builder = AutomaticShieldBuilder::new( + all_factors_in_profile, + primary_factors, + ); + + let proto_matrix = auto_builder.assign()?; + + assert_eq!( + proto_matrix.primary.clone().into_iter().collect_vec(), + self.get_primary_threshold_factors(), + "Auto assignment should not have changed the primary factors" + ); + self.set_state(proto_matrix); + + if let Some(invalid_reason) = self.validate() { + Err(CommonError::AutomaticShieldBuildingFailure { + underlying: invalid_reason.to_string(), + }) + } else { + Ok(auto_builder.stats_for_testing) + } + } + + /// Updates the Primary, Recovery and Confirmation roles with the factors of the given `ProtoMatrix`. + fn set_state(&self, proto_matrix: ProtoMatrix) { + self.reset_factors_in_roles(); + self.set_threshold(proto_matrix.primary.len() as u8); + proto_matrix.primary.into_iter().for_each(|f| { + self.add_factor_source_to_primary_threshold(f); + }); + proto_matrix.recovery.into_iter().for_each(|f| { + self.add_factor_source_to_recovery_override(f); + }); + proto_matrix.confirmation.into_iter().for_each(|f| { + self.add_factor_source_to_confirmation_override(f); + }); + } +} + +impl AutomaticShieldBuilder { + fn new( + available_factors: IndexSet, + primary: IndexSet, + ) -> Self { + Self { + stats_for_testing: AutoBuildOutcomeForTesting::default(), + remaining_available_factors: available_factors, + proto_matrix: ProtoMatrix::new(primary), + } + } + + /// Returns `Some(n)` if any factor matching the selector was found where `n` + /// is `<= quantity_to_add` and `None` if no factors matching the selector was. + /// found. Guaranteed to never return `Some(0)`. + fn assign_factors_matching_selector( + &mut self, + to: RoleKind, + selector: FactorSelector, + quantity_to_add: Quantity, + ) -> Option { + let target_role = to; + + let mut factors_to_add = self + .remaining_available_factors + .iter() + .filter(|&f| match selector { + FactorSelector::Category(category) => f.category() == category, + FactorSelector::Kind(kind) => f.factor_source_kind() == kind, + }) + .map(|f| f.id()) + .collect::>(); + + if let Some(quantity) = quantity_to_add.as_fixed() { + factors_to_add = factors_to_add + .into_iter() + .take(quantity) + .collect::>(); + } + + let number_of_factors_added = factors_to_add.len(); + if number_of_factors_added == 0 { + return None; + } + + self.remaining_available_factors + .retain(|f| !factors_to_add.contains(&f.id())); + + self.proto_matrix + .add_factors_for_role(target_role, factors_to_add); + + Some(number_of_factors_added) + } + + fn factors_for_role(&self, role: RoleKind) -> &IndexSet { + self.proto_matrix.factors_for_role(role) + } + + /// Returns `true` if any factor was assigned, `false` otherwise. + fn assign_factors_of_category( + &mut self, + to: RoleKind, + category: FactorSourceCategory, + quantity_to_add: Quantity, + ) -> bool { + match self.assign_factors_matching_selector( + to, + FactorSelector::Category(category), + quantity_to_add, + ) { + Some(0) | None => false, + Some(_) => true, + } + } + + /// Returns `true` if any factor was assigned, `false` otherwise. + fn assign_factors_of_kind( + &mut self, + to: RoleKind, + kind: FactorSourceKind, + quantity_to_add: Quantity, + ) -> bool { + match self.assign_factors_matching_selector( + to, + FactorSelector::Kind(kind), + quantity_to_add, + ) { + Some(0) | None => false, + Some(_) => true, + } + } + + fn count_factors_for_role(&self, role_kind: RoleKind) -> u8 { + self.factors_for_role(role_kind).len() as u8 + } + + /// Returns `true` if any factor was assigned, `false` otherwise. + fn assign_factors_of_category_to_recovery( + &mut self, + category: FactorSourceCategory, + quantity_to_add: Quantity, + ) -> bool { + self.assign_factors_of_category(Recovery, category, quantity_to_add) + } + + /// Returns `true` if any factor was assigned, `false` otherwise. + fn assign_factors_of_category_to_confirmation( + &mut self, + category: FactorSourceCategory, + quantity_to_add: Quantity, + ) -> bool { + self.assign_factors_of_category(Confirmation, category, quantity_to_add) + } + + fn assign_factor_of_category_to_role_while_meaningful_and_less_than_limit( + &mut self, + category: FactorSourceCategory, + limit: u8, + to: RoleKind, + ) { + let role = to; + + loop { + if self.count_factors_for_role(role) >= limit { + // when `limit` reached, we stop. + return; + } + + if !category.is_supported() { + self.stats_for_testing + .calls_to_assign_unsupported_factor + .push(CallsToAssignUnsupportedFactor { + role, + unsupported: FactorSelector::Category(category), + number_of_factors_for_role: self + .count_factors_for_role(role), + limit, + }); + return; + } + + if !self.assign_factors_of_category(role, category, Quantity::One) { + // We did not manage to assign any factor of this category, meaning we + // it is meaningless to try again. + return; + } + } + } + + /// Calls `assign_factor_of_category_to_role_while_meaningful_and_less_than_limit` + /// for both Custodian and Hardware categories. + fn assign_custodian_and_hardware_to_role_while_meaningful_and_less_than_limit( + &mut self, + limit: u8, + to: RoleKind, + ) { + self.assign_factor_of_category_to_role_while_meaningful_and_less_than_limit( + Custodian, + limit, + to, + ); + + self.assign_factor_of_category_to_role_while_meaningful_and_less_than_limit( + Hardware, + limit, + to, + ); + } + + /// Calls `assign_custodian_and_hardware_to_role_while_meaningful_and_less_than_limit` + /// for both Recovery and Confirmation roles. + fn assign_custodian_and_hardware_to_non_primary_roles_while_less_than_limit_for_each_assignment( + &mut self, + limit: u8, + ) { + self.assign_custodian_and_hardware_to_role_while_meaningful_and_less_than_limit( + limit, Recovery, + ); + self.assign_custodian_and_hardware_to_role_while_meaningful_and_less_than_limit( + limit, + Confirmation, + ); + } + + /// Automatic assignment of factors to roles according to [this heuristics][doc]. + /// + /// [doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields#Automatic-Security-Shield-Construction + fn assign(&mut self) -> Result { + // 📒 "If the user only chose 1 factor for PRIMARY, remove that factor from the list (it cannot be used elsewhere - otherwise it can)." + { + if self.count_factors_for_role(Primary) == 1 + && let Some(only_primary_factor) = + self.proto_matrix.primary.iter().next() + { + self.remaining_available_factors + .retain(|f| f.id() != *only_primary_factor); + } + } + + // 📒 "Drop in the somewhat “special-use” factors first" + { + // 📒 "Add all Contact factors in the list to RECOVERY." + self.assign_factors_of_category_to_recovery(Contact, Quantity::All); + + // 📒 "Add all Information factors in the list to CONFIRMATION." + self.assign_factors_of_category_to_confirmation( + Information, + Quantity::All, + ); + } + + // 📒 Assign Custodian/Hardware factors to RECOVERY & CONFIRMATION + // without exceeding limit of 1 factor in each role. + self.assign_custodian_and_hardware_to_non_primary_roles_while_less_than_limit_for_each_assignment(1); + + // 📒 Assign Custodian/Hardware factors to RECOVERY & CONFIRMATION + // without exceeding limit of 2 factor in each role. + self.assign_custodian_and_hardware_to_non_primary_roles_while_less_than_limit_for_each_assignment(2); + + // 📒 "Fill in any remaining other factors to increase reliability of being able to recover" + { + // 📒 "Add any (and all) remaining Hardware or Custodian factors in the list to RECOVERY." + self.assign_factors_of_category_to_recovery( + Hardware, + Quantity::All, + ); + + // 📒 "Set all Biometrics/PIN factors to a role (they must be all in one role because they are unlocked by the same Biometrics/PIN check):" + { + self.assign_factors_of_kind( + if self.count_factors_for_role(Recovery) + > self.count_factors_for_role(Confirmation) + { + // 📒 "If there are more RECOVERY factors than CONFIRMATION factors, add any (and all) Biometrics/PIN factors to CONFIRMATION" + Confirmation + } else { + // 📒 "Else, add any (and all) Biometrics/PIN factors to RECOVERY." + Recovery + }, + FactorSourceKind::Device, + Quantity::All, + ); + } + } + + Ok(self.proto_matrix.clone()) + } +} + +impl SecurityShieldBuilder { + /// Returns the status of the prerequisites for building a Security Shield. + /// + /// According to [definition][doc], a Security Shield can be built if the user has, asides from + /// the Identity factor, "2 or more factors, one of which must be Hardware" + /// + /// [doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields#Factor-Prerequisites + pub fn prerequisites_status( + factor_source_ids: &IndexSet, + ) -> SecurityShieldPrerequisitesStatus { + let count_excluding_identity = factor_source_ids + .iter() + .filter(|f| f.category() != FactorSourceCategory::Identity) + .count(); + let count_hardware = factor_source_ids + .iter() + .filter(|f| f.category() == FactorSourceCategory::Hardware) + .count(); + if count_hardware < 1 { + SecurityShieldPrerequisitesStatus::HardwareRequired + } else if count_excluding_identity < 2 { + SecurityShieldPrerequisitesStatus::AnyRequired + } else { + SecurityShieldPrerequisitesStatus::Sufficient + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use async_std::future::ready; + use indexmap::IndexSet; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AutomaticShieldBuilder; + + impl SUT { + fn test( + all_factors_in_profile: IndexSet, + pick_primary_role_factors: IndexSet, + ) -> Result<( + SecurityStructureOfFactorSourceIDs, + AutoBuildOutcomeForTesting, + )> { + let shield_builder = SecurityShieldBuilder::new(); + shield_builder.set_threshold(pick_primary_role_factors.len() as u8); + pick_primary_role_factors.into_iter().for_each(|f| { + shield_builder.add_factor_source_to_primary_threshold(f); + }); + + let stats_for_testing = shield_builder.auto_assign_factors_to_recovery_and_confirmation_based_on_primary( + all_factors_in_profile, + )?; + + let built = shield_builder.build().map_err(|e| { + CommonError::AutomaticShieldBuildingFailure { + underlying: format!("{:?}", e), + } + })?; + + Ok((built, stats_for_testing)) + } + } + + #[test] + fn empty_factors_is_err() { + let res = SUT::test(IndexSet::new(), IndexSet::new()); + + assert!(matches!( + res, + Err(CommonError::AutomaticShieldBuildingFailure { .. }) + )); + } + + #[test] + fn one_factors_is_not_enough_is_err() { + let res = SUT::test( + IndexSet::from_iter([FactorSource::sample_device()]), + IndexSet::just(FactorSourceID::sample_device()), + ); + + assert!(matches!( + res, + Err(CommonError::AutomaticShieldBuildingFailure { .. }) + )); + } + + #[test] + fn two_factors_is_not_enough_is_err() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_device(), + FactorSource::sample_ledger(), + ]), + IndexSet::just(FactorSourceID::sample_device()), + ); + + assert!(matches!( + res, + Err(CommonError::AutomaticShieldBuildingFailure { .. }) + )); + } + + #[test] + fn two_device_factor_source_and_one_ledger_is_not_sufficient() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_device_babylon(), + FactorSource::sample_device_babylon_other(), + FactorSource::sample_ledger(), + ]), + IndexSet::just(FactorSourceID::sample_device()), + ); + + assert!(matches!( + res, + Err(CommonError::AutomaticShieldBuildingFailure { .. }) + )); + } + + #[test] + fn one_device_factor_source_and_two_ledger_is_ok_when_primary_uses_one_ledger( + ) { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_device_babylon(), + FactorSource::sample_ledger(), + FactorSource::sample_ledger_other(), + ]), + IndexSet::just(FactorSource::sample_ledger().id()), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 1, + limit: 2 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 0, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 1, + [FactorSourceID::sample_ledger()], + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_ledger_other() + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_device() + ],) + ); + } + + #[test] + fn one_device_factor_source_and_two_ledger_is_ok_when_primary_uses_all() { + let factors = IndexSet::from_iter([ + FactorSource::sample_device_babylon(), + FactorSource::sample_ledger(), + FactorSource::sample_ledger_other(), + ]); + + let res = SUT::test( + factors.clone(), + factors.clone().into_iter().map(|f| f.id()).collect(), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 1, + limit: 2 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 1, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 3, + factors.clone().into_iter().map(|f| f.id()), + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_device(), + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_ledger_other(), + ],) + ); + } + + #[test] + fn two_contacts() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_trusted_contact_frank(), + FactorSource::sample_trusted_contact_grace(), + FactorSource::sample_device_babylon(), + FactorSource::sample_ledger(), + FactorSource::sample_ledger_other(), + ]), + IndexSet::just(FactorSource::sample_device_babylon().id()), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 1, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 1, + [FactorSourceID::sample_device()], + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_trusted_contact_other(), + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + ],) + ); + } + + #[test] + fn two_information() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_password(), + FactorSource::sample_password_other(), + FactorSource::sample_device_babylon(), + FactorSource::sample_ledger(), + FactorSource::sample_ledger_other(), + ]), + IndexSet::just(FactorSource::sample_device_babylon().id()), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 0, + limit: 1 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 1, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 1, + [FactorSourceID::sample_device()], + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + ],) + ); + } + + #[test] + fn one_info_one_contact() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_password(), + FactorSource::sample_trusted_contact_frank(), + FactorSource::sample_device_babylon(), + FactorSource::sample_ledger(), + FactorSource::sample_ledger_other(), + ]), + IndexSet::just(FactorSource::sample_device_babylon().id()), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 1, + limit: 2 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 1, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 1, + [FactorSourceID::sample_device()], + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_ledger(), + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_password(), + FactorSourceID::sample_ledger_other(), + ],) + ); + } + + #[test] + fn arculus_and_ledger_mixed_with_one_info_and_one_contact() { + let res = SUT::test( + IndexSet::from_iter([ + FactorSource::sample_password(), + FactorSource::sample_trusted_contact_frank(), + FactorSource::sample_device_babylon(), + FactorSource::sample_device_babylon_other(), + FactorSource::sample_ledger(), + FactorSource::sample_arculus(), + FactorSource::sample_arculus_other(), + FactorSource::sample_ledger_other(), + ]), + IndexSet::from_iter([ + FactorSource::sample_device_babylon().id(), + FactorSource::sample_ledger().id(), + ]), + ); + + let (shield, stats) = res.unwrap(); + + pretty_assertions::assert_eq!( + stats.calls_to_assign_unsupported_factor, + vec![ + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Recovery, + number_of_factors_for_role: 1, + limit: 2 + }, + CallsToAssignUnsupportedFactor { + unsupported: FactorSelector::Category(Custodian), + role: Confirmation, + number_of_factors_for_role: 1, + limit: 2 + }, + ] + ); + + let matrix = shield.matrix_of_factors; + + pretty_assertions::assert_eq!( + matrix.primary(), + &PrimaryRoleWithFactorSourceIds::with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + [] + ) + ); + + pretty_assertions::assert_eq!( + matrix.recovery(), + &RecoveryRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus_other(), + FactorSourceID::sample_ledger_other(), + ],) + ); + + pretty_assertions::assert_eq!( + matrix.confirmation(), + &ConfirmationRoleWithFactorSourceIds::override_only([ + FactorSourceID::sample_password(), + FactorSourceID::sample_arculus(), + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ],) + ); + } +} diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/factor_selector.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/factor_selector.rs new file mode 100644 index 000000000..e5bef83ec --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/factor_selector.rs @@ -0,0 +1,9 @@ +use crate::prelude::*; + +/// A tiny enum to make it possible to filter FactorSources on either +/// FactorSourceCategory or FactorSourceKind. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FactorSelector { + Category(FactorSourceCategory), + Kind(FactorSourceKind), +} diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs new file mode 100644 index 000000000..f50fd048e --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs @@ -0,0 +1,10 @@ +mod auto_build_outcome_for_testing; +#[allow(clippy::module_inception)] +mod automatic_shield_builder; +mod factor_selector; +mod proto_matrix; +mod quantity; + +pub use auto_build_outcome_for_testing::*; +pub(crate) use automatic_shield_builder::*; +pub use factor_selector::*; diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs new file mode 100644 index 000000000..56dde7835 --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; + +use RoleKind::*; + +/// A tiny holder of factors for each Role. +/// Used by the AutomaticShieldBuilder to keep track of which factors are assigned to which role. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct ProtoMatrix { + pub(super) primary: IndexSet, + pub(super) recovery: IndexSet, + pub(super) confirmation: IndexSet, +} + +impl ProtoMatrix { + pub(super) fn new(primary: IndexSet) -> Self { + Self { + primary, + recovery: IndexSet::new(), + confirmation: IndexSet::new(), + } + } + + pub(super) fn factors_for_role( + &self, + role: RoleKind, + ) -> &IndexSet { + match role { + Primary => &self.primary, + Recovery => &self.recovery, + Confirmation => &self.confirmation, + } + } + + pub(super) fn add_factors_for_role( + &mut self, + role: RoleKind, + factors: IndexSet, + ) { + match role { + Primary => self.primary.extend(factors), + Recovery => self.recovery.extend(factors), + Confirmation => self.confirmation.extend(factors), + } + } +} diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/quantity.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/quantity.rs new file mode 100644 index 000000000..3d96fb654 --- /dev/null +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/quantity.rs @@ -0,0 +1,17 @@ +/// A tiny enum to make it possible to tell auto shield construction to +/// either assign ALL FactorSource matching some `FactorSelector` or only +/// some fixed quantity (typically 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum Quantity { + All, + One, +} + +impl Quantity { + pub(super) fn as_fixed(&self) -> Option { + match self { + Quantity::All => None, + Quantity::One => Some(1), + } + } +} diff --git a/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder.rs b/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder.rs index 699127204..2f962bd6f 100644 --- a/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder.rs +++ b/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder.rs @@ -209,6 +209,11 @@ impl MatrixBuilder { self.confirmation_role.reset(); } + pub fn reset_factors_in_roles(&mut self) { + self.reset_recovery_and_confirmation_role_state(); + self.primary_role.reset(); + } + /// Adds the factor source to the primary role override list. pub fn add_factor_source_to_primary_override( &mut self, diff --git a/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder_unit_tests.rs b/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder_unit_tests.rs index f7a6f6089..08fa0ef9c 100644 --- a/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/sargon/src/profile/mfa/security_structures/matrices/builder/matrix_builder_unit_tests.rs @@ -125,15 +125,15 @@ fn set_number_of_days_42() { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles_and_days( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_device(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), 42, @@ -172,15 +172,15 @@ fn set_number_of_days_if_not_set_uses_default() { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles_and_days( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_device(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), SUT::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, @@ -221,7 +221,7 @@ fn single_factor_in_primary_threshold_cannot_be_in_recovery() { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built.primary(), - &RoleWithFactorSourceIds::primary_with_factors( + &PrimaryRoleWithFactorSourceIds::with_factors( 1, [ FactorSourceID::sample_ledger(), @@ -232,14 +232,14 @@ fn single_factor_in_primary_threshold_cannot_be_in_recovery() { ); pretty_assertions::assert_eq!( built.recovery(), - &RoleWithFactorSourceIds::recovery_with_factors([ + &RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger() ]), ); pretty_assertions::assert_eq!( built.confirmation(), - &RoleWithFactorSourceIds::confirmation_with_factors([ + &ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_arculus_other() ]) ) @@ -1392,7 +1392,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_device(), @@ -1400,11 +1400,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device(), FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), ) @@ -1453,7 +1453,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_ledger(), @@ -1461,11 +1461,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device(), FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), ) @@ -1513,7 +1513,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_device(), @@ -1521,11 +1521,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device(), FactorSourceID::sample_ledger() ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), ) @@ -1565,15 +1565,15 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_device(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger() ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), ) @@ -1614,15 +1614,15 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_ledger(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device() ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password() ],), ) @@ -1671,7 +1671,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_device(), @@ -1679,11 +1679,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), FactorSourceID::sample_ledger_other(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device() ],), ) @@ -1732,7 +1732,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_ledger(), @@ -1740,11 +1740,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), FactorSourceID::sample_ledger_other(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device() ],), ) @@ -1785,15 +1785,15 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_ledger(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger_other(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device() ],), ) @@ -1834,15 +1834,15 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 1, [FactorSourceID::sample_device(),], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger_other() ],), ) @@ -1895,7 +1895,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_device(), @@ -1903,11 +1903,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), FactorSourceID::sample_ledger_other(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device(), FactorSourceID::sample_password() ],), @@ -1965,7 +1965,7 @@ mod shield_configs { pretty_assertions::assert_eq!( built, MatrixOfFactorSourceIds::with_roles( - RoleWithFactorSourceIds::primary_with_factors( + PrimaryRoleWithFactorSourceIds::with_factors( 2, [ FactorSourceID::sample_device(), @@ -1973,11 +1973,11 @@ mod shield_configs { ], [], ), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_device(), FactorSourceID::sample_ledger(), ],), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_password(), FactorSourceID::sample_password_other(), FactorSourceID::sample_off_device() diff --git a/crates/sargon/src/profile/mfa/security_structures/mod.rs b/crates/sargon/src/profile/mfa/security_structures/mod.rs index 6bf4ae0ac..b915d8b26 100644 --- a/crates/sargon/src/profile/mfa/security_structures/mod.rs +++ b/crates/sargon/src/profile/mfa/security_structures/mod.rs @@ -1,3 +1,4 @@ +mod automatic_shield_builder; mod has_role_kind; mod matrices; mod roles; @@ -9,6 +10,7 @@ mod security_structure_metadata; mod security_structure_of_factors; mod selected_factor_sources_status; +pub use automatic_shield_builder::*; pub use has_role_kind::*; pub use matrices::*; pub use roles::*; diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/abstract_role_builder_or_built.rs b/crates/sargon/src/profile/mfa/security_structures/roles/abstract_role_builder_or_built.rs index edd52ad74..70008c2d6 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/abstract_role_builder_or_built.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/abstract_role_builder_or_built.rs @@ -44,20 +44,6 @@ pub struct AbstractRoleBuilderOrBuilt { override_factors: Vec, } -impl RoleBuilder -where - Assert<{ ROLE > ROLE_PRIMARY }>: IsTrue, -{ - /// Removes all override factors from this role - pub fn reset(&mut self) { - self.override_factors.clear(); - - // This is not necessary, but why not... - self.threshold_factors.clear(); - self.threshold = 0; - } -} - pub(crate) type AbstractBuiltRoleWithFactor = AbstractRoleBuilderOrBuilt; @@ -67,6 +53,13 @@ pub(crate) type RoleBuilder = impl AbstractRoleBuilderOrBuilt { + /// Removes all factors from this role and set threshold to 0. + pub fn reset(&mut self) { + self.threshold_factors.clear(); + self.threshold = 0; + self.override_factors.clear(); + } + pub fn role(&self) -> RoleKind { RoleKind::from_u8(ROLE).expect("RoleKind should be valid") } diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/builder/confirmation_roles_builder_unit_tests.rs b/crates/sargon/src/profile/mfa/security_structures/roles/builder/confirmation_roles_builder_unit_tests.rs index a7b9bd2cd..aa92c9880 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/builder/confirmation_roles_builder_unit_tests.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/builder/confirmation_roles_builder_unit_tests.rs @@ -87,7 +87,7 @@ mod device_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample()]) + ConfirmationRoleWithFactorSourceIds::override_only([sample()]) ); } @@ -105,7 +105,7 @@ mod device_in_isolation { assert!(built.get_threshold_factors().is_empty()); assert_eq!( built, - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -135,7 +135,7 @@ mod ledger_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ConfirmationRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -151,7 +151,7 @@ mod ledger_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -181,7 +181,7 @@ mod arculus_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ConfirmationRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -197,7 +197,7 @@ mod arculus_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -227,7 +227,7 @@ mod off_device_mnemonic_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ConfirmationRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -243,7 +243,7 @@ mod off_device_mnemonic_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -320,7 +320,7 @@ mod password_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ConfirmationRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -336,7 +336,7 @@ mod password_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([ + ConfirmationRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/builder/primary_roles_builder_unit_tests.rs b/crates/sargon/src/profile/mfa/security_structures/roles/builder/primary_roles_builder_unit_tests.rs index 012ea265c..bd6c9c8ee 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/builder/primary_roles_builder_unit_tests.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/builder/primary_roles_builder_unit_tests.rs @@ -219,7 +219,7 @@ mod threshold_suite { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 1, [sample_other()], [], @@ -243,7 +243,7 @@ mod threshold_suite { sut.add_factor_source_to_threshold(sample_other()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 1, [sample_other()], [], @@ -269,7 +269,7 @@ mod threshold_suite { sut.add_factor_source_to_threshold(sample_other()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 2, [sample(), sample_other()], [], @@ -289,7 +289,7 @@ mod threshold_suite { assert_eq!(sut.set_threshold(2), Ok(())); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 2, [sample(), sample_other()], [], @@ -317,7 +317,7 @@ mod threshold_suite { sut.add_factor_source_to_threshold(sample_third()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 3, [sample(), sample_other(), sample_third()], [], @@ -335,7 +335,7 @@ mod threshold_suite { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 1, [sample_other()], [], @@ -651,11 +651,8 @@ mod ledger { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 1, - [sample()], - [], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(1, [sample()], []); assert_eq!(sut.build().unwrap(), expected); } @@ -689,7 +686,7 @@ mod ledger { sut.set_threshold(2).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 2, [sample(), sample_other()], [], @@ -718,11 +715,8 @@ mod ledger { sut.add_factor_source_to_override(sample()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample()], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(0, [], [sample()]); assert_eq!(sut.build().unwrap(), expected); } @@ -736,7 +730,7 @@ mod ledger { sut.add_factor_source_to_override(sample_other()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 0, [], [sample(), sample_other()], @@ -786,11 +780,8 @@ mod arculus { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 1, - [sample()], - [], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(1, [sample()], []); assert_eq!(sut.build().unwrap(), expected); } @@ -805,7 +796,7 @@ mod arculus { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 1, [sample(), sample_other()], [], @@ -834,11 +825,8 @@ mod arculus { sut.add_factor_source_to_override(sample()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample()], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(0, [], [sample()]); assert_eq!(sut.build().unwrap(), expected); } @@ -852,7 +840,7 @@ mod arculus { sut.add_factor_source_to_override(sample_other()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( + let expected = PrimaryRoleWithFactorSourceIds::with_factors( 0, [], [sample(), sample_other()], @@ -904,11 +892,8 @@ mod device_factor_source { sut.set_threshold(1).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 1, - [sample()], - [], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(1, [sample()], []); assert_eq!(sut.build().unwrap(), expected); } @@ -954,11 +939,8 @@ mod device_factor_source { sut.add_factor_source_to_override(sample()).unwrap(); // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample()], - ); + let expected = + PrimaryRoleWithFactorSourceIds::with_factors(0, [], [sample()]); assert_eq!(sut.build().unwrap(), expected); } } diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/builder/recovery_roles_builder_unit_tests.rs b/crates/sargon/src/profile/mfa/security_structures/roles/builder/recovery_roles_builder_unit_tests.rs index 8a27eec1a..0efdefec0 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/builder/recovery_roles_builder_unit_tests.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/builder/recovery_roles_builder_unit_tests.rs @@ -85,7 +85,7 @@ mod device_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) + RecoveryRoleWithFactorSourceIds::override_only([sample()]) ); } @@ -101,7 +101,7 @@ mod device_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ sample(), sample_other() ],) @@ -153,7 +153,7 @@ mod ledger_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()],) + RecoveryRoleWithFactorSourceIds::override_only([sample()],) ); } @@ -169,7 +169,7 @@ mod ledger_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -199,7 +199,7 @@ mod arculus_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + RecoveryRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -215,7 +215,7 @@ mod arculus_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -245,7 +245,7 @@ mod off_device_mnemonic_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) + RecoveryRoleWithFactorSourceIds::override_only([sample()]) ); } @@ -261,7 +261,7 @@ mod off_device_mnemonic_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -291,7 +291,7 @@ mod trusted_contact_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + RecoveryRoleWithFactorSourceIds::override_only([sample(),]) ); } @@ -307,7 +307,7 @@ mod trusted_contact_in_isolation { // Assert assert_eq!( sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([ + RecoveryRoleWithFactorSourceIds::override_only([ sample(), sample_other() ]) @@ -412,7 +412,7 @@ mod security_questions_in_isolation { // so we can build and `sample` is not present in the built result. assert_eq!( sut.build(), - Ok(RoleWithFactorSourceIds::recovery_with_factors([ + Ok(RecoveryRoleWithFactorSourceIds::override_only([ FactorSourceID::sample_ledger(), FactorSourceID::sample_arculus() ])) diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/builder/roles_builder.rs b/crates/sargon/src/profile/mfa/security_structures/roles/builder/roles_builder.rs index 6a9fde824..468f3a828 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/builder/roles_builder.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/builder/roles_builder.rs @@ -6,20 +6,9 @@ pub type PrimaryRoleBuilder = RoleBuilder<{ ROLE_PRIMARY }>; pub type RecoveryRoleBuilder = RoleBuilder<{ ROLE_RECOVERY }>; pub type ConfirmationRoleBuilder = RoleBuilder<{ ROLE_CONFIRMATION }>; -#[cfg(test)] -impl PrimaryRoleWithFactorSourceIds { - pub(crate) fn primary_with_factors( - threshold: u8, - threshold_factors: impl IntoIterator, - override_factors: impl IntoIterator, - ) -> Self { - Self::with_factors(threshold, threshold_factors, override_factors) - } -} - #[cfg(test)] impl RecoveryRoleWithFactorSourceIds { - pub(crate) fn recovery_with_factors( + pub(crate) fn override_only( override_factors: impl IntoIterator, ) -> Self { Self::with_factors(0, vec![], override_factors) @@ -28,7 +17,7 @@ impl RecoveryRoleWithFactorSourceIds { #[cfg(test)] impl ConfirmationRoleWithFactorSourceIds { - pub(crate) fn confirmation_with_factors( + pub(crate) fn override_only( override_factors: impl IntoIterator, ) -> Self { Self::with_factors(0, vec![], override_factors) diff --git a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs index 1f702ae59..261682243 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs @@ -226,6 +226,12 @@ impl SecurityShieldBuilder { builder.reset_recovery_and_confirmation_role_state(); }) } + + pub(crate) fn reset_factors_in_roles(&self) -> &Self { + self.set(|builder| { + builder.reset_factors_in_roles(); + }) + } } impl SecurityShieldBuilder { @@ -421,6 +427,13 @@ impl SecurityShieldBuilder { }) } + /// Validates **just** the primary role **in isolation**. + pub fn validate_primary_role( + &self, + ) -> Option { + self.validate_role_in_isolation(RoleKind::Primary) + } + /// `None` means valid! pub fn validate_role_in_isolation( &self, @@ -1099,6 +1112,31 @@ mod test_invalid { ); } + #[test] + fn two_different_password_only_not_valid_for_primary() { + let sut = SUT::new(); + + sut.add_factor_source_to_recovery_override( + FactorSourceID::sample_ledger(), + ); + sut.add_factor_source_to_confirmation_override( + FactorSourceID::sample_arculus(), + ); + + sut.set_threshold(2); + sut.add_factor_source_to_primary_threshold( + FactorSourceID::sample_password(), + ); + sut.add_factor_source_to_primary_threshold( + FactorSourceID::sample_password_other(), + ); + + assert_eq!( + sut.validate().unwrap(), + SecurityShieldBuilderInvalidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ); + } + #[test] fn primary_role_with_password_in_override_does_not_get_added() { let sut = SUT::new(); diff --git a/crates/sargon/src/profile/mfa/security_structures/security_shield_prerequisites_status.rs b/crates/sargon/src/profile/mfa/security_structures/security_shield_prerequisites_status.rs index 087827ae6..fff59371f 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_shield_prerequisites_status.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_shield_prerequisites_status.rs @@ -2,7 +2,7 @@ use crate::prelude::*; /// An enum representing the status of the prerequisites for building a Security Shield. /// This is, whether the user has the necessary factor sources to build a Security Shield. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumAsInner)] pub enum SecurityShieldPrerequisitesStatus { /// A Security Shield can be built with the current Factor Sources available. Sufficient, diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 0ae91a796..23096b5c9 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -3,6 +3,9 @@ use crate::prelude::*; pub type SecurityStructureOfFactorSourceIds = AbstractSecurityStructure; +pub type SecurityStructureOfFactorSourceIDs = + SecurityStructureOfFactorSourceIds; + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "camelCase")] pub struct SecurityStructureOfFactorInstances { diff --git a/crates/sargon/src/profile/v100/app_preferences/security.rs b/crates/sargon/src/profile/v100/app_preferences/security.rs index 2dcdbfbff..ea13ccf13 100644 --- a/crates/sargon/src/profile/v100/app_preferences/security.rs +++ b/crates/sargon/src/profile/v100/app_preferences/security.rs @@ -1,8 +1,5 @@ use crate::prelude::*; -pub type SecurityStructureOfFactorSourceIDs = - SecurityStructureOfFactorSourceIds; - decl_identified_vec_of!( /// A collection of [`SecurityStructureOfFactorSourceIDs`] SecurityStructuresOfFactorSourceIDs, diff --git a/crates/sargon/src/profile/v100/factors/factor_source_category.rs b/crates/sargon/src/profile/v100/factors/factor_source_category.rs index 3d9d4f900..20b1d7ca8 100644 --- a/crates/sargon/src/profile/v100/factors/factor_source_category.rs +++ b/crates/sargon/src/profile/v100/factors/factor_source_category.rs @@ -1,7 +1,7 @@ use crate::prelude::*; /// An enum representing the **category** of a `FactorSource`/`FactorSourceKind`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum FactorSourceCategory { /// Something I am. Identity, @@ -12,6 +12,9 @@ pub enum FactorSourceCategory { /// Something I know. Information, - /// Someone I trust. + /// Some person I trust. Contact, + + /// Some institution I trust. + Custodian, } diff --git a/crates/sargon/src/profile/v100/factors/factor_source_id.rs b/crates/sargon/src/profile/v100/factors/factor_source_id.rs index 9f9f5f7b8..2c5b980ef 100644 --- a/crates/sargon/src/profile/v100/factors/factor_source_id.rs +++ b/crates/sargon/src/profile/v100/factors/factor_source_id.rs @@ -34,6 +34,12 @@ pub enum FactorSourceID { }, } +impl FactorSourceID { + pub fn category(&self) -> FactorSourceCategory { + self.get_factor_source_kind().category() + } +} + /// A bit hacky... but used to make it possible for us to validate FactorSourceID /// in RoleWithFactor... impl IsMaybeKeySpaceAware for FactorSourceID { diff --git a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs index 2cf257754..128bc8529 100644 --- a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs +++ b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs @@ -42,7 +42,7 @@ macro_rules! declare_shared_with_dapp { /// /// # Panics /// Panics if `ids` does not fulfill `request`, for more information - /// see [`RequestedQuantity::is_fulfilled_by_ids`] + /// see [`RequestedQuantity::is_fulfilled_by_quantity`] pub fn new( request: RequestedQuantity, ids: impl IntoIterator, @@ -50,7 +50,7 @@ macro_rules! declare_shared_with_dapp { let ids = IdentifiedVecOf::from_iter(ids.into_iter()); let len = ids.len(); assert!( - request.is_fulfilled_by_ids(len), + request.is_fulfilled_by_quantity(len), "ids does not fulfill request, got: #{}, but requested: {}", len, request