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
32impl<'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 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}