bitwarden_vault/cipher/
secure_note.rs

1use bitwarden_api_api::models::CipherSecureNoteModel;
2use bitwarden_core::{
3    key_management::{KeyIds, SymmetricKeyId},
4    require,
5};
6use bitwarden_crypto::{CryptoError, Decryptable, Encryptable, KeyStoreContext};
7use serde::{Deserialize, Serialize};
8use serde_repr::{Deserialize_repr, Serialize_repr};
9#[cfg(feature = "wasm")]
10use tsify_next::Tsify;
11#[cfg(feature = "wasm")]
12use wasm_bindgen::prelude::wasm_bindgen;
13
14use crate::{
15    cipher::cipher::{CipherKind, CopyableCipherFields},
16    Cipher, VaultParseError,
17};
18
19#[allow(missing_docs)]
20#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug)]
21#[repr(u8)]
22#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
23#[cfg_attr(feature = "wasm", wasm_bindgen)]
24pub enum SecureNoteType {
25    Generic = 0,
26}
27
28#[derive(Clone, Serialize, Deserialize, Debug)]
29#[serde(rename_all = "camelCase", deny_unknown_fields)]
30#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
31#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
32pub struct SecureNote {
33    r#type: SecureNoteType,
34}
35
36#[allow(missing_docs)]
37#[derive(Clone, Serialize, Deserialize, Debug)]
38#[serde(rename_all = "camelCase", deny_unknown_fields)]
39#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
40#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
41pub struct SecureNoteView {
42    pub r#type: SecureNoteType,
43}
44
45impl Encryptable<KeyIds, SymmetricKeyId, SecureNote> for SecureNoteView {
46    fn encrypt(
47        &self,
48        _ctx: &mut KeyStoreContext<KeyIds>,
49        _key: SymmetricKeyId,
50    ) -> Result<SecureNote, CryptoError> {
51        Ok(SecureNote {
52            r#type: self.r#type,
53        })
54    }
55}
56
57impl Decryptable<KeyIds, SymmetricKeyId, SecureNoteView> for SecureNote {
58    fn decrypt(
59        &self,
60        _ctx: &mut KeyStoreContext<KeyIds>,
61        _key: SymmetricKeyId,
62    ) -> Result<SecureNoteView, CryptoError> {
63        Ok(SecureNoteView {
64            r#type: self.r#type,
65        })
66    }
67}
68
69impl TryFrom<CipherSecureNoteModel> for SecureNote {
70    type Error = VaultParseError;
71
72    fn try_from(model: CipherSecureNoteModel) -> Result<Self, Self::Error> {
73        Ok(Self {
74            r#type: require!(model.r#type).into(),
75        })
76    }
77}
78
79impl From<bitwarden_api_api::models::SecureNoteType> for SecureNoteType {
80    fn from(model: bitwarden_api_api::models::SecureNoteType) -> Self {
81        match model {
82            bitwarden_api_api::models::SecureNoteType::Generic => SecureNoteType::Generic,
83        }
84    }
85}
86
87impl CipherKind for SecureNote {
88    fn get_copyable_fields(&self, cipher: Option<&Cipher>) -> Vec<CopyableCipherFields> {
89        [cipher
90            .and_then(|c| c.notes.as_ref())
91            .map(|_| CopyableCipherFields::SecureNotes)]
92        .into_iter()
93        .flatten()
94        .collect()
95    }
96
97    fn decrypt_subtitle(
98        &self,
99        _ctx: &mut KeyStoreContext<KeyIds>,
100        _key: SymmetricKeyId,
101    ) -> Result<String, CryptoError> {
102        Ok(String::new())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::{
109        cipher::cipher::{Cipher, CipherKind, CopyableCipherFields},
110        secure_note::SecureNote,
111        CipherRepromptType, CipherType, SecureNoteType,
112    };
113
114    fn create_cipher_for_note(note: SecureNote) -> Cipher {
115        Cipher {
116            id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()),
117            organization_id: None,
118            folder_id: None,
119            collection_ids: vec![],
120            r#type: CipherType::Login,
121            key: None,
122            name: "2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap(),
123            notes: None,
124            login: None,
125            identity: None,
126            card: None,
127            secure_note: Some(note),
128            ssh_key: None,
129            favorite: false,
130            reprompt: CipherRepromptType::None,
131            organization_use_totp: false,
132            edit: true,
133            permissions: None,
134            view_password: true,
135            local_data: None,
136            attachments: None,
137            fields: None,
138            password_history: None,
139            creation_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
140            deleted_date: None,
141            revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
142        }
143    }
144
145    #[test]
146    fn test_get_copyable_fields_secure_note_empty() {
147        let secure_note = SecureNote {
148            r#type: SecureNoteType::Generic,
149        };
150
151        let cipher = create_cipher_for_note(secure_note.clone());
152
153        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
154        assert_eq!(copyable_fields, vec![]);
155    }
156
157    #[test]
158    fn test_get_copyable_fields_secure_note_has_notes() {
159        let secure_note = SecureNote {
160            r#type: SecureNoteType::Generic,
161        };
162
163        let mut cipher = create_cipher_for_note(secure_note.clone());
164        cipher.notes = Some("2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap());
165
166        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
167        assert_eq!(copyable_fields, vec![CopyableCipherFields::SecureNotes]);
168    }
169
170    #[test]
171    fn test_get_copyable_fields_secure_no_cipher() {
172        let secure_note = SecureNote {
173            r#type: SecureNoteType::Generic,
174        };
175
176        let copyable_fields = secure_note.get_copyable_fields(None);
177        assert_eq!(copyable_fields, vec![]);
178    }
179}