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::{AsAttributes, Result, Secret, dbus, file};
9
10#[derive(Debug)]
20pub enum Keyring {
21 #[doc(hidden)]
22 File(Arc<RwLock<Option<file::Keyring>>>, Secret),
23 #[doc(hidden)]
24 DBus(dbus::Collection),
25}
26
27impl Keyring {
28 pub async fn new() -> Result<Self> {
35 if ashpd::is_sandboxed() {
36 match Self::sandboxed().await {
37 Ok(keyring) => Ok(keyring),
38 Err(crate::Error::File(file::Error::Portal(ashpd::Error::PortalNotFound(_)))) => {
40 #[cfg(feature = "tracing")]
41 tracing::debug!(
42 "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
43 );
44 Self::host().await
45 }
46 Err(e) => Err(e),
47 }
48 } else {
49 Self::host().await
50 }
51 }
52
53 pub async fn sandboxed() -> Result<Self> {
55 #[cfg(feature = "tracing")]
56 tracing::debug!("Using file backend (sandboxed mode)");
57
58 let secret = Secret::sandboxed().await?;
59 let path = crate::file::api::Keyring::default_path()?;
60 Self::sandboxed_with_path(&path, secret).await
61 }
62
63 pub async fn sandboxed_with_path(
70 path: impl AsRef<std::path::Path>,
71 secret: Secret,
72 ) -> Result<Self> {
73 #[cfg(feature = "tracing")]
74 tracing::debug!("Using file backend with custom path");
75
76 let file = file::UnlockedKeyring::load(path, secret.clone()).await?;
77 Ok(Self::File(
78 Arc::new(RwLock::new(Some(file::Keyring::Unlocked(file)))),
79 secret,
80 ))
81 }
82
83 pub async fn host() -> Result<Self> {
85 #[cfg(feature = "tracing")]
86 tracing::debug!("Using D-Bus Secret Service (host mode)");
87
88 let service = dbus::Service::new().await?;
89 let collection = service.default_collection().await?;
90 Ok(Self::DBus(collection))
91 }
92
93 pub async fn host_with_connection(connection: zbus::Connection) -> Result<Self> {
95 #[cfg(feature = "tracing")]
96 tracing::debug!("Using D-Bus Secret Service with custom connection (test mode)");
97
98 let service = dbus::Service::new_with_connection(&connection).await?;
99 let collection = service.default_collection().await?;
100 Ok(Self::DBus(collection))
101 }
102
103 pub async fn unlock(&self) -> Result<()> {
105 match self {
106 Self::DBus(backend) => backend.unlock(None).await?,
107 Self::File(keyring, secret) => {
108 let mut kg = keyring.write().await;
109 let kg_value = kg.take();
110 if let Some(file::Keyring::Locked(locked)) = kg_value {
111 #[cfg(feature = "tracing")]
112 tracing::debug!("Unlocking file backend keyring");
113
114 let unlocked = locked
115 .unlock(secret.clone())
116 .await
117 .map_err(crate::Error::File)?;
118 *kg = Some(file::Keyring::Unlocked(unlocked));
119 } else {
120 *kg = kg_value;
121 }
122 }
123 };
124 Ok(())
125 }
126
127 pub async fn lock(&self) -> Result<()> {
129 match self {
130 Self::DBus(backend) => backend.lock(None).await?,
131 Self::File(keyring, _) => {
132 let mut kg = keyring.write().await;
133 let kg_value = kg.take();
134 if let Some(file::Keyring::Unlocked(unlocked)) = kg_value {
135 #[cfg(feature = "tracing")]
136 tracing::debug!("Locking file backend keyring");
137
138 let locked = unlocked.lock();
139 *kg = Some(file::Keyring::Locked(locked));
140 } else {
141 *kg = kg_value;
142 }
143 }
144 };
145 Ok(())
146 }
147
148 pub async fn is_locked(&self) -> Result<bool> {
150 match self {
151 Self::DBus(collection) => collection.is_locked().await.map_err(From::from),
152 Self::File(keyring, _) => {
153 let keyring_guard = keyring.read().await;
154 Ok(keyring_guard
155 .as_ref()
156 .expect("Keyring must exist")
157 .is_locked())
158 }
159 }
160 }
161
162 pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<()> {
164 match self {
165 Self::DBus(backend) => {
166 let items = backend.search_items(attributes).await?;
167 for item in items {
168 item.delete(None).await?;
169 }
170 }
171 Self::File(keyring, _) => {
172 let kg = keyring.read().await;
173 match kg.as_ref() {
174 Some(file::Keyring::Unlocked(backend)) => {
175 backend
176 .delete(attributes)
177 .await
178 .map_err(crate::Error::File)?;
179 }
180 Some(file::Keyring::Locked(_)) => {
181 return Err(crate::file::Error::Locked.into());
182 }
183 _ => unreachable!("A keyring must exist"),
184 }
185 }
186 };
187 Ok(())
188 }
189
190 pub async fn items(&self) -> Result<Vec<Item>> {
192 let items = match self {
193 Self::DBus(backend) => {
194 let items = backend.items().await?;
195 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
196 }
197 Self::File(keyring, _) => {
198 let kg = keyring.read().await;
199 match kg.as_ref() {
200 Some(file::Keyring::Unlocked(backend)) => {
201 let items = backend.items().await.map_err(crate::Error::File)?;
202 items
203 .into_iter()
204 .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
205 .collect::<Vec<_>>()
206 }
207 Some(file::Keyring::Locked(_)) => {
208 return Err(crate::file::Error::Locked.into());
209 }
210 _ => unreachable!("A keyring must exist"),
211 }
212 }
213 };
214 Ok(items)
215 }
216
217 pub async fn create_item(
219 &self,
220 label: &str,
221 attributes: &impl AsAttributes,
222 secret: impl Into<Secret>,
223 replace: bool,
224 ) -> Result<()> {
225 match self {
226 Self::DBus(backend) => {
227 backend
228 .create_item(label, attributes, secret, replace, None)
229 .await?;
230 }
231 Self::File(keyring, _) => {
232 let kg = keyring.read().await;
233 match kg.as_ref() {
234 Some(file::Keyring::Unlocked(backend)) => {
235 backend
236 .create_item(label, attributes, secret, replace)
237 .await
238 .map_err(crate::Error::File)?;
239 }
240 Some(file::Keyring::Locked(_)) => {
241 return Err(crate::file::Error::Locked.into());
242 }
243 _ => unreachable!("A keyring must exist"),
244 }
245 }
246 };
247 Ok(())
248 }
249
250 pub async fn search_items(&self, attributes: &impl AsAttributes) -> Result<Vec<Item>> {
252 let items = match self {
253 Self::DBus(backend) => {
254 let items = backend.search_items(attributes).await?;
255 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
256 }
257 Self::File(keyring, _) => {
258 let kg = keyring.read().await;
259 match kg.as_ref() {
260 Some(file::Keyring::Unlocked(backend)) => {
261 let items = backend
262 .search_items(attributes)
263 .await
264 .map_err(crate::Error::File)?;
265 items
266 .into_iter()
267 .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
268 .collect::<Vec<_>>()
269 }
270 Some(file::Keyring::Locked(_)) => {
271 return Err(crate::file::Error::Locked.into());
272 }
273 _ => unreachable!("A keyring must exist"),
274 }
275 }
276 };
277 Ok(items)
278 }
279}
280
281#[derive(Debug)]
283pub enum Item {
284 #[doc(hidden)]
285 File(
286 RwLock<Option<file::Item>>,
287 Arc<RwLock<Option<file::Keyring>>>,
288 ),
289 #[doc(hidden)]
290 DBus(dbus::Item),
291}
292
293impl Item {
294 fn for_file(item: file::Item, backend: Arc<RwLock<Option<file::Keyring>>>) -> Self {
295 Self::File(RwLock::new(Some(item)), backend)
296 }
297
298 fn for_dbus(item: dbus::Item) -> Self {
299 Self::DBus(item)
300 }
301
302 pub async fn label(&self) -> Result<String> {
304 let label = match self {
305 Self::File(item, _) => {
306 let item_guard = item.read().await;
307 let file_item = item_guard.as_ref().expect("Item must exist");
308 match file_item {
309 file::Item::Unlocked(unlocked) => unlocked.label().to_owned(),
310 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
311 }
312 }
313 Self::DBus(item) => item.label().await?,
314 };
315 Ok(label)
316 }
317
318 pub async fn set_label(&self, label: &str) -> Result<()> {
320 match self {
321 Self::File(item, keyring) => {
322 let mut item_guard = item.write().await;
323 let file_item = item_guard.as_mut().expect("Item must exist");
324
325 match file_item {
326 file::Item::Unlocked(unlocked) => {
327 unlocked.set_label(label);
328
329 let kg = keyring.read().await;
330 match kg.as_ref() {
331 Some(file::Keyring::Unlocked(backend)) => {
332 backend
333 .create_item(
334 unlocked.label(),
335 &unlocked.attributes(),
336 unlocked.secret(),
337 true,
338 )
339 .await
340 .map_err(crate::Error::File)?;
341 }
342 Some(file::Keyring::Locked(_)) => {
343 return Err(crate::file::Error::Locked.into());
344 }
345 None => unreachable!("A keyring must exist"),
346 }
347 }
348 file::Item::Locked(_) => {
349 return Err(crate::file::Error::Locked.into());
350 }
351 }
352 }
353 Self::DBus(item) => item.set_label(label).await?,
354 };
355 Ok(())
356 }
357
358 pub async fn attributes(&self) -> Result<HashMap<String, String>> {
360 let attributes = match self {
361 Self::File(item, _) => {
362 let item_guard = item.read().await;
363 let file_item = item_guard.as_ref().expect("Item must exist");
364 match file_item {
365 file::Item::Unlocked(unlocked) => unlocked
366 .attributes()
367 .iter()
368 .map(|(k, v)| (k.to_owned(), v.to_string()))
369 .collect::<HashMap<_, _>>(),
370 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
371 }
372 }
373 Self::DBus(item) => item.attributes().await?,
374 };
375 Ok(attributes)
376 }
377
378 #[cfg(feature = "schema")]
397 #[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
398 pub async fn attributes_as<T>(&self) -> Result<T>
399 where
400 T: for<'a> std::convert::TryFrom<&'a HashMap<String, String>, Error = crate::SchemaError>,
401 {
402 match self {
403 Self::File(..) => T::try_from(&self.attributes().await?)
404 .map_err(crate::file::Error::Schema)
405 .map_err(Into::into),
406 Self::DBus(_) => T::try_from(&self.attributes().await?)
407 .map_err(crate::dbus::Error::Schema)
408 .map_err(Into::into),
409 }
410 }
411
412 pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
414 match self {
415 Self::File(item, keyring) => {
416 let kg = keyring.read().await;
417
418 match kg.as_ref() {
419 Some(file::Keyring::Unlocked(backend)) => {
420 let mut item_guard = item.write().await;
421 let file_item = item_guard.as_mut().expect("Item must exist");
422
423 match file_item {
424 file::Item::Unlocked(unlocked) => {
425 let index = backend
426 .lookup_item_index(&unlocked.attributes())
427 .await
428 .map_err(crate::Error::File)?;
429
430 unlocked.set_attributes(attributes);
431
432 if let Some(index) = index {
433 backend
434 .replace_item_index(index, unlocked)
435 .await
436 .map_err(crate::Error::File)?;
437 } else {
438 backend
439 .create_item(
440 unlocked.label(),
441 attributes,
442 unlocked.secret(),
443 true,
444 )
445 .await
446 .map_err(crate::Error::File)?;
447 }
448 }
449 file::Item::Locked(_) => {
450 return Err(crate::file::Error::Locked.into());
451 }
452 }
453 }
454 Some(file::Keyring::Locked(_)) => {
455 return Err(crate::file::Error::Locked.into());
456 }
457 None => unreachable!("A keyring must exist"),
458 }
459 }
460 Self::DBus(item) => item.set_attributes(attributes).await?,
461 };
462 Ok(())
463 }
464
465 pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<()> {
467 match self {
468 Self::File(item, keyring) => {
469 let mut item_guard = item.write().await;
470 let file_item = item_guard.as_mut().expect("Item must exist");
471
472 match file_item {
473 file::Item::Unlocked(unlocked) => {
474 unlocked.set_secret(secret);
475
476 let kg = keyring.read().await;
477 match kg.as_ref() {
478 Some(file::Keyring::Unlocked(backend)) => {
479 backend
480 .create_item(
481 unlocked.label(),
482 &unlocked.attributes(),
483 unlocked.secret(),
484 true,
485 )
486 .await
487 .map_err(crate::Error::File)?;
488 }
489 Some(file::Keyring::Locked(_)) => {
490 return Err(crate::file::Error::Locked.into());
491 }
492 None => unreachable!("A keyring must exist"),
493 }
494 }
495 file::Item::Locked(_) => {
496 return Err(crate::file::Error::Locked.into());
497 }
498 }
499 }
500 Self::DBus(item) => item.set_secret(secret).await?,
501 };
502 Ok(())
503 }
504
505 pub async fn secret(&self) -> Result<Secret> {
507 let secret = match self {
508 Self::File(item, _) => {
509 let item_guard = item.read().await;
510 let file_item = item_guard.as_ref().expect("Item must exist");
511 match file_item {
512 file::Item::Unlocked(unlocked) => unlocked.secret(),
513 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
514 }
515 }
516 Self::DBus(item) => item.secret().await?,
517 };
518 Ok(secret)
519 }
520
521 pub async fn is_locked(&self) -> Result<bool> {
523 match self {
524 Self::DBus(item) => item.is_locked().await.map_err(From::from),
525 Self::File(item, _) => {
526 let item_guard = item.read().await;
527 let file_item = item_guard.as_ref().expect("Item must exist");
528 Ok(file_item.is_locked())
529 }
530 }
531 }
532
533 pub async fn lock(&self) -> Result<()> {
535 match self {
536 Self::DBus(item) => item.lock(None).await?,
537 Self::File(item, keyring) => {
538 let mut item_guard = item.write().await;
539 let item_value = item_guard.take();
540 if let Some(file::Item::Unlocked(unlocked)) = item_value {
541 let kg = keyring.read().await;
542 match kg.as_ref() {
543 Some(file::Keyring::Unlocked(backend)) => {
544 let locked = backend
545 .lock_item(unlocked)
546 .await
547 .map_err(crate::Error::File)?;
548 *item_guard = Some(file::Item::Locked(locked));
549 }
550 Some(file::Keyring::Locked(_)) => {
551 *item_guard = Some(file::Item::Unlocked(unlocked));
552 return Err(crate::file::Error::Locked.into());
553 }
554 None => unreachable!("A keyring must exist"),
555 }
556 } else {
557 *item_guard = item_value;
558 }
559 }
560 }
561 Ok(())
562 }
563
564 pub async fn unlock(&self) -> Result<()> {
566 match self {
567 Self::DBus(item) => item.unlock(None).await?,
568 Self::File(item, keyring) => {
569 let mut item_guard = item.write().await;
570 let item_value = item_guard.take();
571 if let Some(file::Item::Locked(locked)) = item_value {
572 let kg = keyring.read().await;
573 match kg.as_ref() {
574 Some(file::Keyring::Unlocked(backend)) => {
575 let unlocked = backend
576 .unlock_item(locked)
577 .await
578 .map_err(crate::Error::File)?;
579 *item_guard = Some(file::Item::Unlocked(unlocked));
580 }
581 Some(file::Keyring::Locked(_)) => {
582 *item_guard = Some(file::Item::Locked(locked));
583 return Err(crate::file::Error::Locked.into());
584 }
585 None => unreachable!("A keyring must exist"),
586 }
587 } else {
588 *item_guard = item_value;
589 }
590 }
591 }
592 Ok(())
593 }
594
595 pub async fn delete(&self) -> Result<()> {
597 match self {
598 Self::File(item, keyring) => {
599 let item_guard = item.read().await;
600 let file_item = item_guard.as_ref().expect("Item must exist");
601
602 match file_item {
603 file::Item::Unlocked(unlocked) => {
604 let kg = keyring.read().await;
605 match kg.as_ref() {
606 Some(file::Keyring::Unlocked(backend)) => {
607 backend
608 .delete(&unlocked.attributes())
609 .await
610 .map_err(crate::Error::File)?;
611 }
612 Some(file::Keyring::Locked(_)) => {
613 return Err(crate::file::Error::Locked.into());
614 }
615 None => unreachable!("A keyring must exist"),
616 }
617 }
618 file::Item::Locked(_) => {
619 return Err(crate::file::Error::Locked.into());
620 }
621 }
622 }
623 Self::DBus(item) => {
624 item.delete(None).await?;
625 }
626 };
627 Ok(())
628 }
629
630 pub async fn created(&self) -> Result<Duration> {
632 match self {
633 Self::DBus(item) => Ok(item.created().await?),
634 Self::File(item, _) => {
635 let item_guard = item.read().await;
636 let file_item = item_guard.as_ref().expect("Item must exist");
637 match file_item {
638 file::Item::Unlocked(unlocked) => Ok(unlocked.created()),
639 file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
640 }
641 }
642 }
643 }
644
645 pub async fn modified(&self) -> Result<Duration> {
647 match self {
648 Self::DBus(item) => Ok(item.modified().await?),
649 Self::File(item, _) => {
650 let item_guard = item.read().await;
651 let file_item = item_guard.as_ref().expect("Item must exist");
652 match file_item {
653 file::Item::Unlocked(unlocked) => Ok(unlocked.modified()),
654 file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
655 }
656 }
657 }
658 }
659}