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::{CompositeEncryptable, CryptoError, Decryptable, KeyStoreContext};
7use serde::{Deserialize, Serialize};
8use serde_repr::{Deserialize_repr, Serialize_repr};
9#[cfg(feature = "wasm")]
10use tsify::Tsify;
11#[cfg(feature = "wasm")]
12use wasm_bindgen::prelude::wasm_bindgen;
13
14use crate::{
15    Cipher, VaultParseError,
16    cipher::cipher::{CipherKind, CopyableCipherFields},
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 CompositeEncryptable<KeyIds, SymmetricKeyId, SecureNote> for SecureNoteView {
46    fn encrypt_composite(
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 From<SecureNoteType> for bitwarden_api_api::models::SecureNoteType {
88    fn from(model: SecureNoteType) -> Self {
89        match model {
90            SecureNoteType::Generic => bitwarden_api_api::models::SecureNoteType::Generic,
91        }
92    }
93}
94
95impl From<SecureNote> for CipherSecureNoteModel {
96    fn from(model: SecureNote) -> Self {
97        Self {
98            r#type: Some(model.r#type.into()),
99        }
100    }
101}
102
103impl CipherKind for SecureNote {
104    fn get_copyable_fields(&self, cipher: Option<&Cipher>) -> Vec<CopyableCipherFields> {
105        [cipher
106            .and_then(|c| c.notes.as_ref())
107            .map(|_| CopyableCipherFields::SecureNotes)]
108        .into_iter()
109        .flatten()
110        .collect()
111    }
112
113    fn decrypt_subtitle(
114        &self,
115        _ctx: &mut KeyStoreContext<KeyIds>,
116        _key: SymmetricKeyId,
117    ) -> Result<String, CryptoError> {
118        Ok(String::new())
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use crate::{
125        CipherRepromptType, CipherType, SecureNoteType,
126        cipher::cipher::{Cipher, CipherKind, CopyableCipherFields},
127        secure_note::SecureNote,
128    };
129
130    fn create_cipher_for_note(note: SecureNote) -> Cipher {
131        Cipher {
132            id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()),
133            organization_id: None,
134            folder_id: None,
135            collection_ids: vec![],
136            r#type: CipherType::Login,
137            key: None,
138            name: "2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap(),
139            notes: None,
140            login: None,
141            identity: None,
142            card: None,
143            secure_note: Some(note),
144            ssh_key: None,
145            favorite: false,
146            reprompt: CipherRepromptType::None,
147            organization_use_totp: false,
148            edit: true,
149            permissions: None,
150            view_password: true,
151            local_data: None,
152            attachments: None,
153            fields: None,
154            password_history: None,
155            creation_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
156            deleted_date: None,
157            revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
158            archived_date: None,
159        }
160    }
161
162    #[test]
163    fn test_get_copyable_fields_secure_note_empty() {
164        let secure_note = SecureNote {
165            r#type: SecureNoteType::Generic,
166        };
167
168        let cipher = create_cipher_for_note(secure_note.clone());
169
170        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
171        assert_eq!(copyable_fields, vec![]);
172    }
173
174    #[test]
175    fn test_get_copyable_fields_secure_note_has_notes() {
176        let secure_note = SecureNote {
177            r#type: SecureNoteType::Generic,
178        };
179
180        let mut cipher = create_cipher_for_note(secure_note.clone());
181        cipher.notes = Some("2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap());
182
183        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
184        assert_eq!(copyable_fields, vec![CopyableCipherFields::SecureNotes]);
185    }
186
187    #[test]
188    fn test_get_copyable_fields_secure_no_cipher() {
189        let secure_note = SecureNote {
190            r#type: SecureNoteType::Generic,
191        };
192
193        let copyable_fields = secure_note.get_copyable_fields(None);
194        assert_eq!(copyable_fields, vec![]);
195    }
196}