1use std::{collections::HashMap, sync::Arc, time::Duration};
2
3#[cfg(feature = "async-std")]
4use async_lock::RwLock;
5#[cfg(feature = "tokio")]
6use tokio::sync::RwLock;
7
8use crate::{dbus, file, AsAttributes, Result, Secret};
9
10#[derive(Debug)]
20pub enum Keyring {
21 #[doc(hidden)]
22 File(Arc<file::Keyring>),
23 #[doc(hidden)]
24 DBus(dbus::Collection<'static>),
25}
26
27impl Keyring {
28 #[deprecated = "The method is no longer useful as the user can fix the keyring using oo7-cli"]
34 pub async fn with_broken_item_cleanup() -> Result<Self> {
35 Self::new_inner(true).await
36 }
37
38 pub async fn new() -> Result<Self> {
40 Self::new_inner(false).await
41 }
42
43 async fn new_inner(auto_delete_broken_items: bool) -> Result<Self> {
44 let is_sandboxed = ashpd::is_sandboxed().await;
45 if is_sandboxed {
46 #[cfg(feature = "tracing")]
47 tracing::debug!("Application is sandboxed, using the file backend");
48
49 match file::Keyring::load_default().await {
50 Ok(file) => return Ok(Self::File(Arc::new(file))),
51 Err(super::file::Error::Portal(ashpd::Error::PortalNotFound(_))) => {
53 #[cfg(feature = "tracing")]
54 tracing::debug!(
55 "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
56 );
57 }
58 Err(e) => {
59 if matches!(e, file::Error::IncorrectSecret) && auto_delete_broken_items {
60 let keyring = unsafe { file::Keyring::load_default_unchecked().await? };
61 let deleted_items = keyring.delete_broken_items().await?;
62 debug_assert!(deleted_items > 0);
63 return Ok(Self::File(Arc::new(keyring)));
64 }
65 return Err(crate::Error::File(e));
66 }
67 };
68 } else {
69 #[cfg(feature = "tracing")]
70 tracing::debug!(
71 "Application is not sandboxed, falling back to the Secret Service backend"
72 );
73 }
74 let service = dbus::Service::new().await?;
75 let collection = service.default_collection().await?;
76 Ok(Self::DBus(collection))
77 }
78
79 pub async fn unlock(&self) -> Result<()> {
83 if let Self::DBus(backend) = self {
85 backend.unlock(None).await?;
86 };
87 Ok(())
88 }
89
90 pub async fn lock(&self) -> Result<()> {
94 if let Self::DBus(backend) = self {
96 backend.lock(None).await?;
97 };
98 Ok(())
99 }
100
101 pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<()> {
103 match self {
104 Self::DBus(backend) => {
105 let items = backend.search_items(attributes).await?;
106 for item in items {
107 item.delete(None).await?;
108 }
109 }
110 Self::File(backend) => {
111 backend.delete(attributes).await?;
112 }
113 };
114 Ok(())
115 }
116
117 pub async fn items(&self) -> Result<Vec<Item>> {
119 let items = match self {
120 Self::DBus(backend) => {
121 let items = backend.items().await?;
122 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
123 }
124 Self::File(backend) => {
125 let items = backend.items().await?;
126 items
127 .into_iter()
128 .flatten()
130 .map(|i| Item::for_file(i, Arc::clone(backend)))
131 .collect::<Vec<_>>()
132 }
133 };
134 Ok(items)
135 }
136
137 pub async fn create_item(
139 &self,
140 label: &str,
141 attributes: &impl AsAttributes,
142 secret: impl Into<Secret>,
143 replace: bool,
144 ) -> Result<()> {
145 match self {
146 Self::DBus(backend) => {
147 backend
148 .create_item(label, attributes, secret, replace, None)
149 .await?;
150 }
151 Self::File(backend) => {
152 backend
153 .create_item(label, attributes, secret, replace)
154 .await?;
155 }
156 };
157 Ok(())
158 }
159
160 pub async fn search_items(&self, attributes: &impl AsAttributes) -> Result<Vec<Item>> {
162 let items = match self {
163 Self::DBus(backend) => {
164 let items = backend.search_items(attributes).await?;
165 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
166 }
167 Self::File(backend) => {
168 let items = backend.search_items(attributes).await?;
169 items
170 .into_iter()
171 .map(|i| Item::for_file(i, Arc::clone(backend)))
172 .collect::<Vec<_>>()
173 }
174 };
175 Ok(items)
176 }
177
178 pub fn as_file(&self) -> Arc<file::Keyring> {
180 match self {
181 Self::File(keyring) => keyring.clone(),
182 _ => unreachable!(),
183 }
184 }
185
186 pub fn as_dbus(&self) -> &dbus::Collection {
188 match self {
189 Self::DBus(collection) => collection,
190 _ => unreachable!(),
191 }
192 }
193}
194
195#[derive(Debug)]
197pub enum Item {
198 #[doc(hidden)]
199 File(RwLock<file::Item>, Arc<file::Keyring>),
200 #[doc(hidden)]
201 DBus(dbus::Item<'static>),
202}
203
204impl Item {
205 fn for_file(item: file::Item, backend: Arc<file::Keyring>) -> Self {
206 Self::File(RwLock::new(item), backend)
207 }
208
209 fn for_dbus(item: dbus::Item<'static>) -> Self {
210 Self::DBus(item)
211 }
212
213 pub async fn label(&self) -> Result<String> {
215 let label = match self {
216 Self::File(item, _) => item.read().await.label().to_owned(),
217 Self::DBus(item) => item.label().await?,
218 };
219 Ok(label)
220 }
221
222 pub async fn set_label(&self, label: &str) -> Result<()> {
224 match self {
225 Self::File(item, backend) => {
226 item.write().await.set_label(label);
227
228 let item_guard = item.read().await;
229
230 backend
231 .create_item(
232 item_guard.label(),
233 &item_guard.attributes(),
234 item_guard.secret(),
235 true,
236 )
237 .await?;
238 }
239 Self::DBus(item) => item.set_label(label).await?,
240 };
241 Ok(())
242 }
243
244 pub async fn attributes(&self) -> Result<HashMap<String, String>> {
246 let attributes = match self {
247 Self::File(item, _) => item
248 .read()
249 .await
250 .attributes()
251 .iter()
252 .map(|(k, v)| (k.to_owned(), v.to_string()))
253 .collect::<HashMap<_, _>>(),
254 Self::DBus(item) => item.attributes().await?,
255 };
256 Ok(attributes)
257 }
258
259 pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
261 match self {
262 Self::File(item, backend) => {
263 let index = backend
264 .lookup_item_index(item.read().await.attributes())
265 .await?;
266
267 item.write().await.set_attributes(attributes);
268 let item_guard = item.read().await;
269
270 if let Some(index) = index {
271 backend.replace_item_index(index, &item_guard).await?;
272 } else {
273 backend
274 .create_item(item_guard.label(), attributes, item_guard.secret(), true)
275 .await?;
276 }
277 }
278 Self::DBus(item) => item.set_attributes(attributes).await?,
279 };
280 Ok(())
281 }
282
283 pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<()> {
285 match self {
286 Self::File(item, backend) => {
287 item.write().await.set_secret(secret);
288 let item_guard = item.read().await;
289
290 backend
291 .create_item(
292 item_guard.label(),
293 &item_guard.attributes(),
294 item_guard.secret(),
295 true,
296 )
297 .await?;
298 }
299 Self::DBus(item) => item.set_secret(secret).await?,
300 };
301 Ok(())
302 }
303
304 pub async fn secret(&self) -> Result<Secret> {
306 let secret = match self {
307 Self::File(item, _) => item.read().await.secret(),
308 Self::DBus(item) => item.secret().await?,
309 };
310 Ok(secret)
311 }
312
313 pub async fn is_locked(&self) -> Result<bool> {
318 if let Self::DBus(item) = self {
319 item.is_locked().await.map_err(From::from)
320 } else {
321 Ok(false)
322 }
323 }
324
325 pub async fn lock(&self) -> Result<()> {
329 if let Self::DBus(item) = self {
330 item.lock(None).await?;
331 }
332 Ok(())
333 }
334
335 pub async fn unlock(&self) -> Result<()> {
339 if let Self::DBus(item) = self {
340 item.unlock(None).await?;
341 }
342 Ok(())
343 }
344
345 pub async fn delete(&self) -> Result<()> {
347 match self {
348 Self::File(item, backend) => {
349 let item_guard = item.read().await;
350
351 backend.delete(&item_guard.attributes()).await?;
352 }
353 Self::DBus(item) => {
354 item.delete(None).await?;
355 }
356 };
357 Ok(())
358 }
359
360 pub async fn created(&self) -> Result<Duration> {
362 match self {
363 Self::DBus(item) => Ok(item.created().await?),
364 Self::File(item, _) => Ok(item.read().await.created()),
365 }
366 }
367
368 pub async fn modified(&self) -> Result<Duration> {
370 match self {
371 Self::DBus(item) => Ok(item.modified().await?),
372 Self::File(item, _) => Ok(item.read().await.modified()),
373 }
374 }
375}
376
377#[cfg(test)]
378#[cfg(feature = "tokio")]
379mod tests {
380 use tempfile::tempdir;
381 use tokio::fs;
382
383 use super::*;
384
385 #[tokio::test]
386 async fn portal_set_attributes() -> Result<()> {
387 let data_dir = tempdir().unwrap();
388 let dir = data_dir.path().join("keyrings");
389 fs::create_dir_all(&dir).await.unwrap();
390 let path = dir.join("default.keyring");
391
392 let secret = crate::Secret::text("test");
393 let keyring = Keyring::File(file::Keyring::load(&path, secret).await?.into());
394
395 let items = keyring.items().await?;
396 assert_eq!(items.len(), 0);
397
398 keyring
399 .create_item("my item", &vec![("key", "value")], "my_secret", false)
400 .await?;
401
402 let mut items = keyring.items().await?;
403 assert_eq!(items.len(), 1);
404 let item = items.remove(0);
405 assert_eq!(item.label().await?, "my item");
406 assert_eq!(item.secret().await?, Secret::text("my_secret"));
407 let attrs = item.attributes().await?;
408 assert_eq!(attrs.len(), 2);
409 assert_eq!(attrs.get("key").unwrap(), "value");
410
411 item.set_attributes(&vec![("key", "changed_value"), ("new_key", "new_value")])
412 .await?;
413
414 let mut items = keyring.items().await?;
415 assert_eq!(items.len(), 1);
416 let item = items.remove(0);
417 assert_eq!(item.label().await?, "my item");
418 assert_eq!(item.secret().await?, Secret::text("my_secret"));
419 let attrs = item.attributes().await?;
420 assert_eq!(attrs.len(), 3);
421 assert_eq!(attrs.get("key").unwrap(), "changed_value");
422 assert_eq!(attrs.get("new_key").unwrap(), "new_value");
423
424 Ok(())
425 }
426}