Skip to main content

bitwarden_vault/cipher/
secure_note.rs

1use bitwarden_api_api::models::CipherSecureNoteModel;
2use bitwarden_core::{
3    key_management::{KeySlotIds, SymmetricKeySlotId},
4    require,
5};
6use bitwarden_crypto::{CompositeEncryptable, CryptoError, Decryptable, KeyStoreContext};
7use serde::{Deserialize, Deserializer, Serialize};
8use serde_repr::Serialize_repr;
9
10use crate::{
11    Cipher, VaultParseError,
12    cipher::cipher::{CipherKind, CopyableCipherFields},
13};
14
15#[allow(missing_docs)]
16#[derive(Clone, Copy, Serialize_repr, Debug, PartialEq)]
17#[repr(u8)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
19#[bitwarden_ffi::wasm_object]
20pub enum SecureNoteType {
21    Generic = 0,
22}
23
24#[derive(Clone, Serialize, Deserialize, Debug)]
25#[serde(rename_all = "camelCase")]
26#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
27#[bitwarden_ffi::wasm_record]
28pub struct SecureNote {
29    r#type: SecureNoteType,
30}
31
32/// Use the custom deserializer for SecureNoteType
33/// Older notes may have a Type greater than 0. If so set them to Generic
34impl<'de> Deserialize<'de> for SecureNoteType {
35    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36    where
37        D: Deserializer<'de>,
38    {
39        match u8::deserialize(deserializer) {
40            Ok(0) => Ok(SecureNoteType::Generic),
41            Ok(_) => {
42                // Any unknown type (like type 1) defaults to Generic
43                Ok(SecureNoteType::Generic)
44            }
45            Err(_) => Ok(SecureNoteType::Generic),
46        }
47    }
48}
49
50#[allow(missing_docs)]
51#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
54#[bitwarden_ffi::wasm_record]
55pub struct SecureNoteView {
56    pub r#type: SecureNoteType,
57}
58
59impl CompositeEncryptable<KeySlotIds, SymmetricKeySlotId, SecureNote> for SecureNoteView {
60    fn encrypt_composite(
61        &self,
62        _ctx: &mut KeyStoreContext<KeySlotIds>,
63        _key: SymmetricKeySlotId,
64    ) -> Result<SecureNote, CryptoError> {
65        Ok(SecureNote {
66            r#type: self.r#type,
67        })
68    }
69}
70
71impl Decryptable<KeySlotIds, SymmetricKeySlotId, SecureNoteView> for SecureNote {
72    fn decrypt(
73        &self,
74        _ctx: &mut KeyStoreContext<KeySlotIds>,
75        _key: SymmetricKeySlotId,
76    ) -> Result<SecureNoteView, CryptoError> {
77        Ok(SecureNoteView {
78            r#type: self.r#type,
79        })
80    }
81}
82
83impl TryFrom<CipherSecureNoteModel> for SecureNote {
84    type Error = VaultParseError;
85
86    fn try_from(model: CipherSecureNoteModel) -> Result<Self, Self::Error> {
87        Ok(Self {
88            r#type: require!(model.r#type).try_into()?,
89        })
90    }
91}
92
93impl TryFrom<bitwarden_api_api::models::SecureNoteType> for SecureNoteType {
94    type Error = bitwarden_core::MissingFieldError;
95
96    fn try_from(model: bitwarden_api_api::models::SecureNoteType) -> Result<Self, Self::Error> {
97        Ok(match model {
98            bitwarden_api_api::models::SecureNoteType::Generic => SecureNoteType::Generic,
99            bitwarden_api_api::models::SecureNoteType::__Unknown(_) => {
100                return Err(bitwarden_core::MissingFieldError("type"));
101            }
102        })
103    }
104}
105
106impl From<SecureNoteType> for bitwarden_api_api::models::SecureNoteType {
107    fn from(model: SecureNoteType) -> Self {
108        match model {
109            SecureNoteType::Generic => bitwarden_api_api::models::SecureNoteType::Generic,
110        }
111    }
112}
113
114impl From<SecureNote> for CipherSecureNoteModel {
115    fn from(model: SecureNote) -> Self {
116        Self {
117            r#type: Some(model.r#type.into()),
118        }
119    }
120}
121
122impl CipherKind for SecureNote {
123    fn get_copyable_fields(&self, cipher: Option<&Cipher>) -> Vec<CopyableCipherFields> {
124        [cipher
125            .and_then(|c| c.notes.as_ref())
126            .map(|_| CopyableCipherFields::SecureNotes)]
127        .into_iter()
128        .flatten()
129        .collect()
130    }
131
132    fn decrypt_subtitle(
133        &self,
134        _ctx: &mut KeyStoreContext<KeySlotIds>,
135        _key: SymmetricKeySlotId,
136    ) -> Result<String, CryptoError> {
137        Ok(String::new())
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::{
144        CipherRepromptType, CipherType, SecureNoteType,
145        cipher::cipher::{Cipher, CipherKind, CopyableCipherFields},
146        secure_note::SecureNote,
147    };
148
149    fn create_cipher_for_note(note: SecureNote) -> Cipher {
150        Cipher {
151            partial_data: None,
152            id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()),
153            organization_id: None,
154            folder_id: None,
155            collection_ids: vec![],
156            r#type: CipherType::Login,
157            key: None,
158            name: Some("2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap()),
159            notes: None,
160            login: None,
161            identity: None,
162            card: None,
163            secure_note: Some(note),
164            ssh_key: None,
165            bank_account: None,
166            drivers_license: None,
167            passport: None,
168            favorite: false,
169            reprompt: CipherRepromptType::None,
170            organization_use_totp: false,
171            edit: true,
172            permissions: None,
173            view_password: true,
174            local_data: None,
175            attachments: None,
176            fields: None,
177            password_history: None,
178            creation_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
179            deleted_date: None,
180            revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(),
181            archived_date: None,
182            data: None,
183        }
184    }
185
186    #[test]
187    fn test_get_copyable_fields_secure_note_empty() {
188        let secure_note = SecureNote {
189            r#type: SecureNoteType::Generic,
190        };
191
192        let cipher = create_cipher_for_note(secure_note.clone());
193
194        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
195        assert_eq!(copyable_fields, vec![]);
196    }
197
198    #[test]
199    fn test_get_copyable_fields_secure_note_has_notes() {
200        let secure_note = SecureNote {
201            r#type: SecureNoteType::Generic,
202        };
203
204        let mut cipher = create_cipher_for_note(secure_note.clone());
205        cipher.notes = Some("2.iovOJUb186UXu+0AlQggjw==|LeWZhrT0B7rqFtDufOJMlJsftwmMGuaoBxf/Cig4D4A9XHhUqacd8uOYP7M5bd/k|++gmrHIyt8hvvPP9dwFS/CGd+POfzmeXzKOsuyJpDDc=".parse().unwrap());
206
207        let copyable_fields = secure_note.get_copyable_fields(Some(&cipher));
208        assert_eq!(copyable_fields, vec![CopyableCipherFields::SecureNotes]);
209    }
210
211    #[test]
212    fn test_get_copyable_fields_secure_no_cipher() {
213        let secure_note = SecureNote {
214            r#type: SecureNoteType::Generic,
215        };
216
217        let copyable_fields = secure_note.get_copyable_fields(None);
218        assert_eq!(copyable_fields, vec![]);
219    }
220}