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, Some(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 new_unchecked() -> Result<Self> {
89 if ashpd::is_sandboxed() {
90 match Self::sandboxed_unchecked().await {
91 Ok(keyring) => Ok(keyring),
92 Err(crate::Error::File(file::Error::Portal(ashpd::Error::PortalNotFound(_)))) => {
93 #[cfg(feature = "tracing")]
94 tracing::debug!(
95 "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
96 );
97 Self::host().await
98 }
99 Err(e) => Err(e),
100 }
101 } else {
102 Self::host().await
103 }
104 }
105
106 pub async fn sandboxed_unchecked() -> Result<Self> {
112 #[cfg(feature = "tracing")]
113 tracing::debug!("Using file backend (sandboxed unchecked mode)");
114
115 let secret = Secret::sandboxed().await?;
116 let path = crate::file::api::Keyring::default_path()?;
117 Self::sandboxed_with_path_unchecked(&path, secret).await
118 }
119
120 #[allow(unsafe_code)]
129 pub async fn sandboxed_with_path_unchecked(
130 path: impl AsRef<std::path::Path>,
131 secret: Secret,
132 ) -> Result<Self> {
133 #[cfg(feature = "tracing")]
134 tracing::debug!("Using file backend with custom path (unchecked mode)");
135
136 let file = unsafe { file::UnlockedKeyring::load_unchecked(path, secret.clone()).await? };
139 Ok(Self::File(
140 Arc::new(RwLock::new(Some(file::Keyring::Unlocked(file)))),
141 secret,
142 ))
143 }
144
145 pub async fn host() -> Result<Self> {
147 #[cfg(feature = "tracing")]
148 tracing::debug!("Using D-Bus Secret Service (host mode)");
149
150 let service = dbus::Service::new().await?;
151 let collection = service.default_collection().await?;
152 Ok(Self::DBus(collection))
153 }
154
155 pub async fn host_with_connection(connection: zbus::Connection) -> Result<Self> {
157 #[cfg(feature = "tracing")]
158 tracing::debug!("Using D-Bus Secret Service with custom connection (test mode)");
159
160 let service = dbus::Service::new_with_connection(&connection).await?;
161 let collection = service.default_collection().await?;
162 Ok(Self::DBus(collection))
163 }
164
165 pub async fn unlock(&self) -> Result<()> {
167 match self {
168 Self::DBus(backend) => backend.unlock(None).await?,
169 Self::File(keyring, secret) => {
170 let mut kg = keyring.write().await;
171 let kg_value = kg.take();
172 if let Some(file::Keyring::Locked(locked)) = kg_value {
173 #[cfg(feature = "tracing")]
174 tracing::debug!("Unlocking file backend keyring");
175
176 let unlocked = locked
177 .unlock(secret.clone())
178 .await
179 .map_err(crate::Error::File)?;
180 *kg = Some(file::Keyring::Unlocked(unlocked));
181 } else {
182 *kg = kg_value;
183 }
184 }
185 };
186 Ok(())
187 }
188
189 pub async fn lock(&self) -> Result<()> {
191 match self {
192 Self::DBus(backend) => backend.lock(None).await?,
193 Self::File(keyring, _) => {
194 let mut kg = keyring.write().await;
195 let kg_value = kg.take();
196 if let Some(file::Keyring::Unlocked(unlocked)) = kg_value {
197 #[cfg(feature = "tracing")]
198 tracing::debug!("Locking file backend keyring");
199
200 let locked = unlocked.lock();
201 *kg = Some(file::Keyring::Locked(locked));
202 } else {
203 *kg = kg_value;
204 }
205 }
206 };
207 Ok(())
208 }
209
210 pub async fn is_locked(&self) -> Result<bool> {
212 match self {
213 Self::DBus(collection) => collection.is_locked().await.map_err(From::from),
214 Self::File(keyring, _) => {
215 let keyring_guard = keyring.read().await;
216 Ok(keyring_guard
217 .as_ref()
218 .expect("Keyring must exist")
219 .is_locked())
220 }
221 }
222 }
223
224 pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<()> {
226 match self {
227 Self::DBus(backend) => {
228 let items = backend.search_items(attributes).await?;
229 for item in items {
230 item.delete(None).await?;
231 }
232 }
233 Self::File(keyring, _) => {
234 let kg = keyring.read().await;
235 match kg.as_ref() {
236 Some(file::Keyring::Unlocked(backend)) => {
237 backend
238 .delete(attributes)
239 .await
240 .map_err(crate::Error::File)?;
241 }
242 Some(file::Keyring::Locked(_)) => {
243 return Err(crate::file::Error::Locked.into());
244 }
245 _ => unreachable!("A keyring must exist"),
246 }
247 }
248 };
249 Ok(())
250 }
251
252 pub async fn items(&self) -> Result<Vec<Item>> {
254 let items = match self {
255 Self::DBus(backend) => {
256 let items = backend.items().await?;
257 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
258 }
259 Self::File(keyring, _) => {
260 let kg = keyring.read().await;
261 match kg.as_ref() {
262 Some(file::Keyring::Unlocked(backend)) => {
263 let items = backend.items().await.map_err(crate::Error::File)?;
264 items
265 .into_iter()
266 .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
267 .collect::<Vec<_>>()
268 }
269 Some(file::Keyring::Locked(_)) => {
270 return Err(crate::file::Error::Locked.into());
271 }
272 _ => unreachable!("A keyring must exist"),
273 }
274 }
275 };
276 Ok(items)
277 }
278
279 pub async fn create_item(
281 &self,
282 label: &str,
283 attributes: &impl AsAttributes,
284 secret: impl Into<Secret>,
285 replace: bool,
286 ) -> Result<()> {
287 match self {
288 Self::DBus(backend) => {
289 backend
290 .create_item(label, attributes, secret, replace, None)
291 .await?;
292 }
293 Self::File(keyring, _) => {
294 let kg = keyring.read().await;
295 match kg.as_ref() {
296 Some(file::Keyring::Unlocked(backend)) => {
297 backend
298 .create_item(label, attributes, secret, replace)
299 .await
300 .map_err(crate::Error::File)?;
301 }
302 Some(file::Keyring::Locked(_)) => {
303 return Err(crate::file::Error::Locked.into());
304 }
305 _ => unreachable!("A keyring must exist"),
306 }
307 }
308 };
309 Ok(())
310 }
311
312 pub async fn search_items(&self, attributes: &impl AsAttributes) -> Result<Vec<Item>> {
314 let items = match self {
315 Self::DBus(backend) => {
316 let items = backend.search_items(attributes).await?;
317 items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
318 }
319 Self::File(keyring, _) => {
320 let kg = keyring.read().await;
321 match kg.as_ref() {
322 Some(file::Keyring::Unlocked(backend)) => {
323 let items = backend
324 .search_items(attributes)
325 .await
326 .map_err(crate::Error::File)?;
327 items
328 .into_iter()
329 .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
330 .collect::<Vec<_>>()
331 }
332 Some(file::Keyring::Locked(_)) => {
333 return Err(crate::file::Error::Locked.into());
334 }
335 _ => unreachable!("A keyring must exist"),
336 }
337 }
338 };
339 Ok(items)
340 }
341}
342
343#[derive(Debug)]
345pub enum Item {
346 #[doc(hidden)]
347 File(
348 RwLock<Option<file::Item>>,
349 Arc<RwLock<Option<file::Keyring>>>,
350 ),
351 #[doc(hidden)]
352 DBus(dbus::Item),
353}
354
355impl Item {
356 fn for_file(item: file::Item, backend: Arc<RwLock<Option<file::Keyring>>>) -> Self {
357 Self::File(RwLock::new(Some(item)), backend)
358 }
359
360 fn for_dbus(item: dbus::Item) -> Self {
361 Self::DBus(item)
362 }
363
364 pub async fn label(&self) -> Result<String> {
366 let label = match self {
367 Self::File(item, _) => {
368 let item_guard = item.read().await;
369 let file_item = item_guard.as_ref().expect("Item must exist");
370 match file_item {
371 file::Item::Unlocked(unlocked) => unlocked.label().to_owned(),
372 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
373 }
374 }
375 Self::DBus(item) => item.label().await?,
376 };
377 Ok(label)
378 }
379
380 pub async fn set_label(&self, label: &str) -> Result<()> {
382 match self {
383 Self::File(item, keyring) => {
384 let mut item_guard = item.write().await;
385 let file_item = item_guard.as_mut().expect("Item must exist");
386
387 match file_item {
388 file::Item::Unlocked(unlocked) => {
389 unlocked.set_label(label);
390
391 let kg = keyring.read().await;
392 match kg.as_ref() {
393 Some(file::Keyring::Unlocked(backend)) => {
394 backend
395 .create_item(
396 unlocked.label(),
397 &unlocked.attributes(),
398 unlocked.secret(),
399 true,
400 )
401 .await
402 .map_err(crate::Error::File)?;
403 }
404 Some(file::Keyring::Locked(_)) => {
405 return Err(crate::file::Error::Locked.into());
406 }
407 None => unreachable!("A keyring must exist"),
408 }
409 }
410 file::Item::Locked(_) => {
411 return Err(crate::file::Error::Locked.into());
412 }
413 }
414 }
415 Self::DBus(item) => item.set_label(label).await?,
416 };
417 Ok(())
418 }
419
420 pub async fn attributes(&self) -> Result<HashMap<String, String>> {
422 let attributes = match self {
423 Self::File(item, _) => {
424 let item_guard = item.read().await;
425 let file_item = item_guard.as_ref().expect("Item must exist");
426 match file_item {
427 file::Item::Unlocked(unlocked) => unlocked
428 .attributes()
429 .iter()
430 .map(|(k, v)| (k.to_owned(), v.to_string()))
431 .collect::<HashMap<_, _>>(),
432 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
433 }
434 }
435 Self::DBus(item) => item.attributes().await?,
436 };
437 Ok(attributes)
438 }
439
440 #[cfg(feature = "schema")]
459 #[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
460 pub async fn attributes_as<T>(&self) -> Result<T>
461 where
462 T: for<'a> std::convert::TryFrom<&'a HashMap<String, String>, Error = crate::SchemaError>,
463 {
464 match self {
465 Self::File(..) => T::try_from(&self.attributes().await?)
466 .map_err(crate::file::Error::Schema)
467 .map_err(Into::into),
468 Self::DBus(_) => T::try_from(&self.attributes().await?)
469 .map_err(crate::dbus::Error::Schema)
470 .map_err(Into::into),
471 }
472 }
473
474 pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
476 match self {
477 Self::File(item, keyring) => {
478 let kg = keyring.read().await;
479
480 match kg.as_ref() {
481 Some(file::Keyring::Unlocked(backend)) => {
482 let mut item_guard = item.write().await;
483 let file_item = item_guard.as_mut().expect("Item must exist");
484
485 match file_item {
486 file::Item::Unlocked(unlocked) => {
487 let index = backend
488 .lookup_item_index(&unlocked.attributes())
489 .await
490 .map_err(crate::Error::File)?;
491
492 unlocked.set_attributes(attributes);
493
494 if let Some(index) = index {
495 backend
496 .replace_item_index(index, unlocked)
497 .await
498 .map_err(crate::Error::File)?;
499 } else {
500 backend
501 .create_item(
502 unlocked.label(),
503 attributes,
504 unlocked.secret(),
505 true,
506 )
507 .await
508 .map_err(crate::Error::File)?;
509 }
510 }
511 file::Item::Locked(_) => {
512 return Err(crate::file::Error::Locked.into());
513 }
514 }
515 }
516 Some(file::Keyring::Locked(_)) => {
517 return Err(crate::file::Error::Locked.into());
518 }
519 None => unreachable!("A keyring must exist"),
520 }
521 }
522 Self::DBus(item) => item.set_attributes(attributes).await?,
523 };
524 Ok(())
525 }
526
527 pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<()> {
529 match self {
530 Self::File(item, keyring) => {
531 let mut item_guard = item.write().await;
532 let file_item = item_guard.as_mut().expect("Item must exist");
533
534 match file_item {
535 file::Item::Unlocked(unlocked) => {
536 unlocked.set_secret(secret);
537
538 let kg = keyring.read().await;
539 match kg.as_ref() {
540 Some(file::Keyring::Unlocked(backend)) => {
541 backend
542 .create_item(
543 unlocked.label(),
544 &unlocked.attributes(),
545 unlocked.secret(),
546 true,
547 )
548 .await
549 .map_err(crate::Error::File)?;
550 }
551 Some(file::Keyring::Locked(_)) => {
552 return Err(crate::file::Error::Locked.into());
553 }
554 None => unreachable!("A keyring must exist"),
555 }
556 }
557 file::Item::Locked(_) => {
558 return Err(crate::file::Error::Locked.into());
559 }
560 }
561 }
562 Self::DBus(item) => item.set_secret(secret).await?,
563 };
564 Ok(())
565 }
566
567 pub async fn secret(&self) -> Result<Secret> {
569 let secret = match self {
570 Self::File(item, _) => {
571 let item_guard = item.read().await;
572 let file_item = item_guard.as_ref().expect("Item must exist");
573 match file_item {
574 file::Item::Unlocked(unlocked) => unlocked.secret(),
575 file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
576 }
577 }
578 Self::DBus(item) => item.secret().await?,
579 };
580 Ok(secret)
581 }
582
583 pub async fn is_locked(&self) -> Result<bool> {
585 match self {
586 Self::DBus(item) => item.is_locked().await.map_err(From::from),
587 Self::File(item, _) => {
588 let item_guard = item.read().await;
589 let file_item = item_guard.as_ref().expect("Item must exist");
590 Ok(file_item.is_locked())
591 }
592 }
593 }
594
595 pub async fn lock(&self) -> Result<()> {
597 match self {
598 Self::DBus(item) => item.lock(None).await?,
599 Self::File(item, keyring) => {
600 let mut item_guard = item.write().await;
601 let item_value = item_guard.take();
602 if let Some(file::Item::Unlocked(unlocked)) = item_value {
603 let kg = keyring.read().await;
604 match kg.as_ref() {
605 Some(file::Keyring::Unlocked(backend)) => {
606 let locked = backend
607 .lock_item(unlocked)
608 .await
609 .map_err(crate::Error::File)?;
610 *item_guard = Some(file::Item::Locked(locked));
611 }
612 Some(file::Keyring::Locked(_)) => {
613 *item_guard = Some(file::Item::Unlocked(unlocked));
614 return Err(crate::file::Error::Locked.into());
615 }
616 None => unreachable!("A keyring must exist"),
617 }
618 } else {
619 *item_guard = item_value;
620 }
621 }
622 }
623 Ok(())
624 }
625
626 pub async fn unlock(&self) -> Result<()> {
628 match self {
629 Self::DBus(item) => item.unlock(None).await?,
630 Self::File(item, keyring) => {
631 let mut item_guard = item.write().await;
632 let item_value = item_guard.take();
633 if let Some(file::Item::Locked(locked)) = item_value {
634 let kg = keyring.read().await;
635 match kg.as_ref() {
636 Some(file::Keyring::Unlocked(backend)) => {
637 let unlocked = backend
638 .unlock_item(locked)
639 .await
640 .map_err(crate::Error::File)?;
641 *item_guard = Some(file::Item::Unlocked(unlocked));
642 }
643 Some(file::Keyring::Locked(_)) => {
644 *item_guard = Some(file::Item::Locked(locked));
645 return Err(crate::file::Error::Locked.into());
646 }
647 None => unreachable!("A keyring must exist"),
648 }
649 } else {
650 *item_guard = item_value;
651 }
652 }
653 }
654 Ok(())
655 }
656
657 pub async fn delete(&self) -> Result<()> {
659 match self {
660 Self::File(item, keyring) => {
661 let item_guard = item.read().await;
662 let file_item = item_guard.as_ref().expect("Item must exist");
663
664 match file_item {
665 file::Item::Unlocked(unlocked) => {
666 let kg = keyring.read().await;
667 match kg.as_ref() {
668 Some(file::Keyring::Unlocked(backend)) => {
669 backend
670 .delete(&unlocked.attributes())
671 .await
672 .map_err(crate::Error::File)?;
673 }
674 Some(file::Keyring::Locked(_)) => {
675 return Err(crate::file::Error::Locked.into());
676 }
677 None => unreachable!("A keyring must exist"),
678 }
679 }
680 file::Item::Locked(_) => {
681 return Err(crate::file::Error::Locked.into());
682 }
683 }
684 }
685 Self::DBus(item) => {
686 item.delete(None).await?;
687 }
688 };
689 Ok(())
690 }
691
692 pub async fn created(&self) -> Result<Duration> {
694 match self {
695 Self::DBus(item) => Ok(item.created().await?),
696 Self::File(item, _) => {
697 let item_guard = item.read().await;
698 let file_item = item_guard.as_ref().expect("Item must exist");
699 match file_item {
700 file::Item::Unlocked(unlocked) => Ok(unlocked.created()),
701 file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
702 }
703 }
704 }
705 }
706
707 pub async fn modified(&self) -> Result<Duration> {
709 match self {
710 Self::DBus(item) => Ok(item.modified().await?),
711 Self::File(item, _) => {
712 let item_guard = item.read().await;
713 let file_item = item_guard.as_ref().expect("Item must exist");
714 match file_item {
715 file::Item::Unlocked(unlocked) => Ok(unlocked.modified()),
716 file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
717 }
718 }
719 }
720 }
721}