secret_service/blocking/
mod.rs1use crate::session::Session;
14use crate::ss::SS_COLLECTION_LABEL;
15use crate::util;
16use crate::{proxy::service::ServiceProxyBlocking, util::exec_prompt_blocking};
17use crate::{EncryptionType, Error, SearchItemsResult};
18use std::collections::HashMap;
19use zbus::zvariant::{ObjectPath, OwnedObjectPath, Value};
20
21mod collection;
22pub use collection::Collection;
23mod item;
24pub use item::Item;
25
26pub struct SecretService<'a> {
34 conn: zbus::blocking::Connection,
35 session: Session,
36 service_proxy: ServiceProxyBlocking<'a>,
37}
38
39impl<'a> SecretService<'a> {
40 pub fn connect(encryption: EncryptionType) -> Result<SecretService<'a>, Error> {
44 let conn = zbus::blocking::Connection::session().map_err(util::handle_conn_error)?;
45 Self::connect_with_existing(encryption, conn)
46 }
47
48 pub fn connect_with_existing(
52 encryption: EncryptionType,
53 session_conn: zbus::blocking::Connection,
54 ) -> Result<SecretService<'a>, Error> {
55 let service_proxy =
56 ServiceProxyBlocking::new(&session_conn).map_err(util::handle_conn_error)?;
57
58 let session = Session::new_blocking(&service_proxy, encryption)?;
59
60 Ok(SecretService {
61 conn: session_conn,
62 session,
63 service_proxy,
64 })
65 }
66
67 pub fn get_all_collections(&'a self) -> Result<Vec<Collection<'a>>, Error> {
69 let collections = self.service_proxy.collections()?;
70 collections
71 .into_iter()
72 .map(|object_path| {
73 Collection::new(
74 self.conn.clone(),
75 &self.session,
76 &self.service_proxy,
77 object_path.into(),
78 )
79 })
80 .collect()
81 }
82
83 pub fn get_collection_by_alias(&'a self, alias: &str) -> Result<Collection<'a>, Error> {
89 let object_path = self.service_proxy.read_alias(alias)?;
90
91 if object_path.as_str() == "/" {
92 Err(Error::NoResult)
93 } else {
94 Ok(Collection::new(
95 self.conn.clone(),
96 &self.session,
97 &self.service_proxy,
98 object_path,
99 )?)
100 }
101 }
102
103 pub fn get_default_collection(&'a self) -> Result<Collection<'a>, Error> {
106 self.get_collection_by_alias("default")
107 }
108
109 pub fn get_any_collection(&'a self) -> Result<Collection<'a>, Error> {
114 self.get_default_collection()
117 .or_else(|_| self.get_collection_by_alias("session"))
118 .or_else(|_| {
119 let mut collections = self.get_all_collections()?;
120 if collections.is_empty() {
121 Err(Error::NoResult)
122 } else {
123 Ok(collections.swap_remove(0))
124 }
125 })
126 }
127
128 pub fn create_collection(&'a self, label: &str, alias: &str) -> Result<Collection<'a>, Error> {
130 let mut properties: HashMap<&str, Value> = HashMap::new();
131 properties.insert(SS_COLLECTION_LABEL, label.into());
132
133 let created_collection = self.service_proxy.create_collection(properties, alias)?;
134
135 let collection_path: ObjectPath = {
137 let created_path = created_collection.collection;
139
140 if created_path.as_str() == "/" {
142 let prompt_path = created_collection.prompt;
143
144 let prompt_res = exec_prompt_blocking(self.conn.clone(), &prompt_path)?;
146 prompt_res.try_into()?
147 } else {
148 created_path.into()
150 }
151 };
152
153 Collection::new(
154 self.conn.clone(),
155 &self.session,
156 &self.service_proxy,
157 collection_path.into(),
158 )
159 }
160
161 pub fn search_items(
163 &'a self,
164 attributes: HashMap<&str, &str>,
165 ) -> Result<SearchItemsResult<Item<'a>>, Error> {
166 let items = self.service_proxy.search_items(attributes)?;
167
168 let object_paths_to_items = |items: Vec<_>| {
169 items
170 .into_iter()
171 .map(|item_path| {
172 Item::new(
173 self.conn.clone(),
174 &self.session,
175 &self.service_proxy,
176 item_path,
177 )
178 })
179 .collect::<Result<_, _>>()
180 };
181
182 Ok(SearchItemsResult {
183 unlocked: object_paths_to_items(items.unlocked)?,
184 locked: object_paths_to_items(items.locked)?,
185 })
186 }
187
188 pub fn unlock_all(&'a self, items: &[&Item]) -> Result<(), Error> {
190 let objects = items.iter().map(|i| &*i.item_path).collect();
191 let lock_action_res = self.service_proxy.unlock(objects)?;
192
193 if lock_action_res.object_paths.is_empty() {
194 exec_prompt_blocking(self.conn.clone(), &lock_action_res.prompt)?;
195 }
196
197 Ok(())
198 }
199
200 pub fn get_item_by_path(&'a self, item_path: OwnedObjectPath) -> Result<Item<'a>, Error> {
201 Item::new(
202 self.conn.clone(),
203 &self.session,
204 &self.service_proxy,
205 item_path,
206 )
207 }
208
209 pub fn get_collection_by_path(
210 &'a self,
211 collection_path: OwnedObjectPath,
212 ) -> Result<Collection<'a>, Error> {
213 Collection::new(
214 self.conn.clone(),
215 &self.session,
216 &self.service_proxy,
217 collection_path,
218 )
219 }
220}
221
222#[cfg(test)]
223mod test {
224 use super::*;
225 use std::convert::TryFrom;
226 use zbus::zvariant::ObjectPath;
227
228 #[test]
229 fn should_create_secret_service() {
230 SecretService::connect(EncryptionType::Plain).unwrap();
231 }
232
233 #[test]
234 fn should_get_all_collections() {
235 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
238 let collections = ss.get_all_collections().unwrap();
239 assert!(!collections.is_empty(), "no collections found");
240 }
241
242 #[test]
243 fn should_get_collection_by_alias() {
244 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
245 ss.get_collection_by_alias("session").unwrap();
246 }
247
248 #[test]
249 fn should_return_error_if_collection_doesnt_exist() {
250 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
251
252 match ss.get_collection_by_alias("definitely_definitely_does_not_exist") {
253 Err(Error::NoResult) => {}
254 _ => panic!(),
255 };
256 }
257
258 #[test]
259 fn should_get_default_collection() {
260 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
261 ss.get_default_collection().unwrap();
262 }
263
264 #[test]
265 fn should_get_any_collection() {
266 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
267 let _ = ss.get_any_collection().unwrap();
268 }
269
270 #[test_with::no_env(GITHUB_ACTIONS)]
271 #[test]
272 fn should_create_and_delete_collection() {
273 let ss = SecretService::connect(EncryptionType::Plain).unwrap();
274 let test_collection = ss.create_collection("Test", "").unwrap();
275 assert_eq!(
276 ObjectPath::from(test_collection.collection_path.clone()),
277 ObjectPath::try_from("/org/freedesktop/secrets/collection/Test").unwrap()
278 );
279 test_collection.delete().unwrap();
280 }
281
282 #[test]
283 fn should_search_items() {
284 let ss = SecretService::connect(EncryptionType::Dh).unwrap();
285 let collection = ss.get_default_collection().unwrap();
286
287 let item = collection
289 .create_item(
290 "test",
291 HashMap::from([("test_attribute_in_ss", "test_value")]),
292 b"test_secret",
293 false,
294 "text/plain",
295 )
296 .unwrap();
297
298 ss.search_items(HashMap::new()).unwrap();
300
301 let bad_search = ss.search_items(HashMap::from([("test", "test")])).unwrap();
303 assert_eq!(bad_search.unlocked.len(), 0);
304 assert_eq!(bad_search.locked.len(), 0);
305
306 let search_item = ss
308 .search_items(HashMap::from([("test_attribute_in_ss", "test_value")]))
309 .unwrap();
310
311 assert_eq!(item.item_path, search_item.unlocked[0].item_path);
312 assert_eq!(search_item.locked.len(), 0);
313 item.delete().unwrap();
314 }
315}