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}