1use super::item::Item;
2use crate::error::Error;
3use crate::proxy::collection::CollectionProxyBlocking;
4use crate::proxy::service::ServiceProxyBlocking;
5use crate::session::Session;
6use crate::ss::{SS_DBUS_NAME, SS_ITEM_ATTRIBUTES, SS_ITEM_LABEL};
7use crate::util::{exec_prompt_blocking, format_secret, lock_or_unlock_blocking, LockAction};
8
9use std::collections::HashMap;
10use zbus::{
11 proxy::CacheProperties,
12 zvariant::{Dict, ObjectPath, OwnedObjectPath, Value},
13};
14
15pub struct Collection<'a> {
19 conn: zbus::blocking::Connection,
20 session: &'a Session,
21 pub collection_path: OwnedObjectPath,
22 collection_proxy: CollectionProxyBlocking<'a>,
23 service_proxy: &'a ServiceProxyBlocking<'a>,
24}
25
26impl<'a> Collection<'a> {
27 pub(crate) fn new(
28 conn: zbus::blocking::Connection,
29 session: &'a Session,
30 service_proxy: &'a ServiceProxyBlocking,
31 collection_path: OwnedObjectPath,
32 ) -> Result<Self, Error> {
33 let collection_proxy = CollectionProxyBlocking::builder(&conn)
34 .destination(SS_DBUS_NAME)?
35 .path(collection_path.clone())?
36 .cache_properties(CacheProperties::No)
37 .build()?;
38 Ok(Collection {
39 conn,
40 session,
41 collection_path,
42 collection_proxy,
43 service_proxy,
44 })
45 }
46
47 pub fn is_locked(&self) -> Result<bool, Error> {
48 Ok(self.collection_proxy.locked()?)
49 }
50
51 pub fn ensure_unlocked(&self) -> Result<(), Error> {
52 if self.is_locked()? {
53 Err(Error::Locked)
54 } else {
55 Ok(())
56 }
57 }
58
59 pub fn unlock(&self) -> Result<(), Error> {
60 lock_or_unlock_blocking(
61 self.conn.clone(),
62 self.service_proxy,
63 &self.collection_path,
64 LockAction::Unlock,
65 )
66 }
67
68 pub fn lock(&self) -> Result<(), Error> {
69 lock_or_unlock_blocking(
70 self.conn.clone(),
71 self.service_proxy,
72 &self.collection_path,
73 LockAction::Lock,
74 )
75 }
76
77 pub fn delete(&self) -> Result<(), Error> {
79 self.ensure_unlocked()?;
81 let prompt_path = self.collection_proxy.delete()?;
82
83 if prompt_path.as_str() != "/" {
85 exec_prompt_blocking(self.conn.clone(), &prompt_path)?;
86 }
87
88 Ok(())
89 }
90
91 pub fn get_all_items(&'a self) -> Result<Vec<Item<'a>>, Error> {
92 let items = self.collection_proxy.items()?;
93
94 let res = items
96 .into_iter()
97 .map(|item_path| {
98 Item::new(
99 self.conn.clone(),
100 self.session,
101 self.service_proxy,
102 item_path.into(),
103 )
104 })
105 .collect::<Result<_, _>>()?;
106
107 Ok(res)
108 }
109
110 pub fn search_items(&'a self, attributes: HashMap<&str, &str>) -> Result<Vec<Item<'a>>, Error> {
111 let items = self.collection_proxy.search_items(attributes)?;
112
113 let res = items
115 .into_iter()
116 .map(|item_path| {
117 Item::new(
118 self.conn.clone(),
119 self.session,
120 self.service_proxy,
121 item_path,
122 )
123 })
124 .collect::<Result<_, _>>()?;
125
126 Ok(res)
127 }
128
129 pub fn get_label(&self) -> Result<String, Error> {
130 Ok(self.collection_proxy.label()?)
131 }
132
133 pub fn set_label(&self, new_label: &str) -> Result<(), Error> {
134 Ok(self.collection_proxy.set_label(new_label)?)
135 }
136
137 pub fn create_item(
138 &'a self,
139 label: &str,
140 attributes: HashMap<&str, &str>,
141 secret: &[u8],
142 replace: bool,
143 content_type: &str,
144 ) -> Result<Item<'a>, Error> {
145 let secret_struct = format_secret(self.session, secret, content_type)?;
146
147 let mut properties: HashMap<&str, Value> = HashMap::new();
148 let attributes: Dict = attributes.into();
149
150 properties.insert(SS_ITEM_LABEL, label.into());
151 properties.insert(SS_ITEM_ATTRIBUTES, attributes.into());
152
153 let created_item = self
154 .collection_proxy
155 .create_item(properties, secret_struct, replace)?;
156
157 let item_path: ObjectPath = {
159 let created_path = created_item.item;
161
162 if created_path.as_str() == "/" {
164 let prompt_path = created_item.prompt;
165
166 let prompt_res = exec_prompt_blocking(self.conn.clone(), &prompt_path)?;
168 prompt_res.try_into()?
169 } else {
170 created_path.into()
172 }
173 };
174
175 Item::new(
176 self.conn.clone(),
177 self.session,
178 self.service_proxy,
179 item_path.into(),
180 )
181 }
182}
183
184#[cfg(test)]
185mod test {
186 use crate::blocking::*;
187
188 #[test]
189 fn should_create_collection_struct() {
190 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
191 let _ = ss.get_default_collection().unwrap();
192 }
194
195 #[test]
196 fn should_check_if_collection_locked() {
197 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
198 let collection = ss.get_default_collection().unwrap();
199 let _ = collection.is_locked().unwrap();
200 }
201
202 #[test]
203 #[ignore] fn should_lock_and_unlock() {
205 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
206 let collection = ss.get_default_collection().unwrap();
207 let locked = collection.is_locked().unwrap();
208 if locked {
209 collection.unlock().unwrap();
210 collection.ensure_unlocked().unwrap();
211 assert!(!collection.is_locked().unwrap());
212 collection.lock().unwrap();
213 assert!(collection.is_locked().unwrap());
214 } else {
215 collection.lock().unwrap();
216 assert!(collection.is_locked().unwrap());
217 collection.unlock().unwrap();
218 collection.ensure_unlocked().unwrap();
219 assert!(!collection.is_locked().unwrap());
220 }
221 }
222
223 #[test]
224 #[ignore]
225 fn should_delete_collection() {
226 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
227 let collections = ss.get_all_collections().unwrap();
228 let count_before = collections.len();
229 for collection in collections {
230 let collection_path = &*collection.collection_path;
231 if collection_path.contains("Test") {
232 collection.unlock().unwrap();
233 collection.delete().unwrap();
234 }
235 }
236 let collections = ss.get_all_collections().unwrap();
238 assert!(
239 collections.len() < count_before,
240 "collections before delete {count_before}",
241 )
242 }
243
244 #[test]
245 fn should_get_all_items() {
246 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
247 let collection = ss.get_default_collection().unwrap();
248 collection.get_all_items().unwrap();
249 }
250
251 #[test]
252 fn should_search_items() {
253 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
254 let collection = ss.get_default_collection().unwrap();
255
256 let item = collection
258 .create_item(
259 "test",
260 HashMap::from([("test_attributes_in_collection", "test")]),
261 b"test_secret",
262 false,
263 "text/plain",
264 )
265 .unwrap();
266
267 collection.search_items(HashMap::new()).unwrap();
269
270 let bad_search = collection
272 .search_items(HashMap::from([("test_bad", "test")]))
273 .unwrap();
274 assert_eq!(bad_search.len(), 0);
275
276 let search_item = collection
278 .search_items(HashMap::from([("test_attributes_in_collection", "test")]))
279 .unwrap();
280
281 assert_eq!(item.item_path, search_item[0].item_path);
282 item.delete().unwrap();
283 }
284
285 #[test]
286 #[ignore]
287 fn should_get_and_set_collection_label() {
288 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
289 let collection = ss.get_default_collection().unwrap();
290 let label = collection.get_label().unwrap();
291 assert_eq!(label, "Login");
292
293 collection.unlock().unwrap();
295 collection.set_label("Test").unwrap();
296 let label = collection.get_label().unwrap();
297 assert_eq!(label, "Test");
298
299 collection.unlock().unwrap();
301 collection.set_label("Login").unwrap();
302 let label = collection.get_label().unwrap();
303 assert_eq!(label, "Login");
304
305 collection.lock().unwrap();
306 }
307
308 #[test]
309 fn should_get_collection_by_path() {
310 let path: OwnedObjectPath;
311 let label: String;
312 {
314 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
315 let collection = ss.get_default_collection().unwrap();
316 label = collection.get_label().unwrap();
317 path = collection.collection_path.clone();
318 }
319 {
321 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
322 let collection_prime = ss.get_collection_by_path(path).unwrap();
323 let label_prime = collection_prime.get_label().unwrap();
324 assert_eq!(label, label_prime);
325 }
326 }
327}