Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion crates/bitwarden-vault/src/cipher/bank_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use tsify::Tsify;

use super::cipher::CipherKind;
use super::cipher::{CipherKind, StrictDecrypt};
use crate::{Cipher, VaultParseError, cipher::cipher::CopyableCipherFields};

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -46,6 +46,18 @@ pub struct BankAccountView {
pub bank_contact_phone: Option<String>,
}

/// Minimal BankAccountView only including the needed details for list views
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct BankAccountListView {
/// The account number of the bank account.
pub account_number: Option<String>,
/// The type of the bank account, e.g. Checking, Savings, etc.
pub account_type: Option<String>,
}

impl CompositeEncryptable<KeySlotIds, SymmetricKeySlotId, BankAccount> for BankAccountView {
fn encrypt_composite(
&self,
Expand Down Expand Up @@ -88,6 +100,34 @@ impl Decryptable<KeySlotIds, SymmetricKeySlotId, BankAccountView> for BankAccoun
}
}

impl Decryptable<KeySlotIds, SymmetricKeySlotId, BankAccountListView> for BankAccount {
fn decrypt(
&self,
ctx: &mut KeyStoreContext<KeySlotIds>,
key: SymmetricKeySlotId,
) -> Result<BankAccountListView, CryptoError> {
Ok(BankAccountListView {
account_number: self.account_number.decrypt(ctx, key).ok().flatten(),
account_type: self.account_type.decrypt(ctx, key).ok().flatten(),
})
}
}

impl Decryptable<KeySlotIds, SymmetricKeySlotId, BankAccountListView>
for StrictDecrypt<&BankAccount>
{
fn decrypt(
&self,
ctx: &mut KeyStoreContext<KeySlotIds>,
key: SymmetricKeySlotId,
) -> Result<BankAccountListView, CryptoError> {
Ok(BankAccountListView {
account_number: self.0.account_number.decrypt(ctx, key)?,
account_type: self.0.account_type.decrypt(ctx, key)?,
})
}
}

impl CipherKind for BankAccount {
fn decrypt_subtitle(
&self,
Expand Down Expand Up @@ -230,6 +270,29 @@ mod tests {
assert_eq!(decrypted, test_bank_account_view());
}

#[test]
fn test_bank_account_list_view() {
let key =
SymmetricCryptoKey::try_from(TEST_VECTOR_BANK_KEY.to_string()).expect("valid test key");
let key_store = create_test_crypto_with_user_key(key);
let key_slot = SymmetricKeySlotId::User;
let mut ctx = key_store.context();

let encrypted: BankAccount =
serde_json::from_str(TEST_VECTOR_BANK_JSON).expect("valid test vector JSON");
let list_view: BankAccountListView = encrypted
.decrypt(&mut ctx, key_slot)
.expect("BankAccount should decrypt to a list view");

assert_eq!(
list_view,
BankAccountListView {
account_number: Some("1234567890".to_string()),
account_type: Some("Checking".to_string()),
}
);
}

#[test]
fn test_subtitle_bank_account() {
let key = SymmetricCryptoKey::try_from("hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string()).unwrap();
Expand Down
49 changes: 43 additions & 6 deletions crates/bitwarden-vault/src/cipher/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use wasm_bindgen::prelude::wasm_bindgen;

use super::{
attachment, bank_account,
bank_account::BankAccountListView,
blob::{decrypt_blob_cipher, encrypt_blob_cipher, try_parse_blob},
card,
card::CardListView,
Expand Down Expand Up @@ -484,7 +485,7 @@ pub enum CipherListViewType {
Card(CardListView),
Identity,
SshKey,
BankAccount,
BankAccount(BankAccountListView),
Passport,
DriversLicense,
}
Expand Down Expand Up @@ -1119,7 +1120,16 @@ impl CipherView {
}
CipherType::Identity => CipherListViewType::Identity,
CipherType::SshKey => CipherListViewType::SshKey,
CipherType::BankAccount => CipherListViewType::BankAccount,
CipherType::BankAccount => {
let bank_account = self
.bank_account
.as_ref()
.ok_or(CryptoError::MissingField("bank_account"))?;
CipherListViewType::BankAccount(BankAccountListView {
account_number: bank_account.account_number.clone(),
account_type: bank_account.account_type.clone(),
})
}
CipherType::DriversLicense => CipherListViewType::DriversLicense,
CipherType::Passport => CipherListViewType::Passport,
};
Expand Down Expand Up @@ -1208,13 +1218,20 @@ impl CipherView {
drivers_license::build_subtitle_drivers_license(
d.first_name.clone(),
d.last_name.clone(),
d.issuing_state.clone(),
)
})
.unwrap_or_default(),
CipherType::Passport => self
.passport
.as_ref()
.map(|p| passport::build_subtitle_passport(p.given_name.clone(), p.surname.clone()))
.map(|p| {
passport::build_subtitle_passport(
p.given_name.clone(),
p.surname.clone(),
p.issuing_country.clone(),
)
})
.unwrap_or_default(),
}
}
Expand Down Expand Up @@ -1414,7 +1431,13 @@ pub(crate) fn lenient_decrypt_cipher_list_view(
}
CipherType::Identity => CipherListViewType::Identity,
CipherType::SshKey => CipherListViewType::SshKey,
CipherType::BankAccount => CipherListViewType::BankAccount,
CipherType::BankAccount => {
let bank_account = cipher
.bank_account
.as_ref()
.ok_or(CryptoError::MissingField("bank_account"))?;
CipherListViewType::BankAccount(bank_account.decrypt(ctx, ciphers_key)?)
}
CipherType::Passport => CipherListViewType::Passport,
CipherType::DriversLicense => CipherListViewType::DriversLicense,
},
Expand Down Expand Up @@ -1686,7 +1709,15 @@ fn strict_decrypt_cipher_list_view(
}
CipherType::Identity => CipherListViewType::Identity,
CipherType::SshKey => CipherListViewType::SshKey,
CipherType::BankAccount => CipherListViewType::BankAccount,
CipherType::BankAccount => {
let bank_account = cipher
.bank_account
.as_ref()
.ok_or(CryptoError::MissingField("bank_account"))?;
CipherListViewType::BankAccount(
StrictDecrypt(bank_account).decrypt(ctx, ciphers_key)?,
)
}
CipherType::Passport => CipherListViewType::Passport,
CipherType::DriversLicense => CipherListViewType::DriversLicense,
},
Expand Down Expand Up @@ -3844,7 +3875,13 @@ mod tests {
let list_view = decrypt_blob_list_view(&key_store, view);
assert_eq!(list_view.name, "My Bank Account");
assert_eq!(list_view.subtitle, "Some Bank");
assert!(matches!(list_view.r#type, CipherListViewType::BankAccount));
assert_eq!(
list_view.r#type,
CipherListViewType::BankAccount(BankAccountListView {
account_number: Some("123456".to_string()),
account_type: None,
})
);
assert_eq!(
list_view.copyable_fields,
vec![
Expand Down
38 changes: 31 additions & 7 deletions crates/bitwarden-vault/src/cipher/drivers_license.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,16 @@ impl CipherKind for DriversLicense {
.as_ref()
.map(|l| l.decrypt(ctx, key))
.transpose()?;
Ok(build_subtitle_drivers_license(first_name, last_name))
let issuing_state: Option<String> = self
.issuing_state
.as_ref()
.map(|l| l.decrypt(ctx, key))
.transpose()?;
Ok(build_subtitle_drivers_license(
first_name,
last_name,
issuing_state,
))
}

fn get_copyable_fields(&self, _: Option<&Cipher>) -> Vec<CopyableCipherFields> {
Expand Down Expand Up @@ -136,13 +145,28 @@ impl CipherKind for DriversLicense {
pub(super) fn build_subtitle_drivers_license(
first_name: Option<String>,
last_name: Option<String>,
issuing_state: Option<String>,
) -> String {
[first_name, last_name]
.into_iter()
.flatten()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
let mut subtitle = String::new();

if let Some(first_name) = first_name {
subtitle.push_str(&first_name);
}
if let Some(last_name) = last_name {
if subtitle.len() > 0 && last_name.len() > 0 {
subtitle.push_str(" ");
}
subtitle.push_str(&last_name);
}

if let Some(issuing_state) = issuing_state {
if subtitle.len() > 0 && issuing_state.len() > 0 {
subtitle.push_str(", ");
}
subtitle.push_str(&issuing_state);
}
Comment thread
jengstrom-bw marked this conversation as resolved.

subtitle
}

impl TryFrom<CipherDriversLicenseModel> for DriversLicense {
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-vault/src/cipher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use attachment_client::{
CipherRenewFileUploadUrlError, CipherUpgradeAttachmentError, CreateAttachmentRequest,
CreatedAttachment, DecryptFileError, DeleteAttachmentAdminError, EncryptFileError,
};
pub use bank_account::BankAccountView;
pub use bank_account::{BankAccountListView, BankAccountView};
pub use blob::{BlobEncryptionError, SealedCipherBlobError};
pub use card::{CardBrand, CardListView, CardView};
pub use cipher::{
Expand Down
38 changes: 31 additions & 7 deletions crates/bitwarden-vault/src/cipher/passport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,16 @@ impl CipherKind for Passport {
.as_ref()
.map(|s| s.decrypt(ctx, key))
.transpose()?;
Ok(build_subtitle_passport(given_name, surname))
let issuing_country: Option<String> = self
.issuing_country
.as_ref()
.map(|s| s.decrypt(ctx, key))
.transpose()?;
Ok(build_subtitle_passport(
given_name,
surname,
issuing_country,
))
}

fn get_copyable_fields(&self, _: Option<&Cipher>) -> Vec<CopyableCipherFields> {
Expand Down Expand Up @@ -150,13 +159,28 @@ impl CipherKind for Passport {
pub(super) fn build_subtitle_passport(
given_name: Option<String>,
surname: Option<String>,
issuing_country: Option<String>,
) -> String {
[given_name, surname]
.into_iter()
.flatten()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
let mut subtitle = String::new();

if let Some(given_name) = given_name {
subtitle.push_str(&given_name);
}
if let Some(surname) = surname {
if subtitle.len() > 0 && surname.len() > 0 {
subtitle.push_str(" ");
}
subtitle.push_str(&surname);
}

if let Some(issuing_country) = issuing_country {
if subtitle.len() > 0 && issuing_country.len() > 0 {
subtitle.push_str(", ");
}
subtitle.push_str(&issuing_country);
}

subtitle
}

impl TryFrom<CipherPassportModel> for Passport {
Expand Down
Loading