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}