Skip to main content

oo7/file/
mod.rs

1//! File backend implementation that can be backed by the [Secret portal](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Secret.html).
2//!
3//! ```no_run
4//! use oo7::{Secret, file::UnlockedKeyring};
5//!
6//! # async fn run() -> oo7::Result<()> {
7//! let keyring = UnlockedKeyring::load("default.keyring", Some(Secret::text("some_text"))).await?;
8//! keyring
9//!     .create_item("My Label", &[("account", "alice")], "My Password", true)
10//!     .await?;
11//!
12//! let items = keyring.search_items(&[("account", "alice")]).await?;
13//! assert_eq!(items[0].secret(), oo7::Secret::blob("My Password"));
14//!
15//! keyring.delete(&[("account", "alice")]).await?;
16//! #   Ok(())
17//! # }
18//! ```
19
20#[cfg(feature = "unstable")]
21#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
22pub mod api;
23#[cfg(not(feature = "unstable"))]
24pub(crate) mod api;
25
26mod error;
27mod locked_item;
28mod locked_keyring;
29mod unlocked_item;
30mod unlocked_keyring;
31
32pub use error::{Error, InvalidItemError, WeakKeyError};
33pub use locked_item::LockedItem;
34pub use locked_keyring::LockedKeyring;
35pub use unlocked_item::UnlockedItem;
36pub use unlocked_keyring::{ItemDefinition, UnlockedKeyring};
37
38use crate::{AsAttributes, Key, Secret};
39
40#[derive(Debug)]
41pub enum Item {
42    Locked(LockedItem),
43    Unlocked(UnlockedItem),
44}
45
46impl From<UnlockedItem> for Item {
47    fn from(item: UnlockedItem) -> Self {
48        Self::Unlocked(item)
49    }
50}
51
52impl From<LockedItem> for Item {
53    fn from(item: LockedItem) -> Self {
54        Self::Locked(item)
55    }
56}
57
58impl Item {
59    pub const fn is_locked(&self) -> bool {
60        matches!(self, Self::Locked(_))
61    }
62
63    pub fn as_unlocked(&self) -> &UnlockedItem {
64        match self {
65            Self::Unlocked(item) => item,
66            _ => panic!("The item is locked"),
67        }
68    }
69
70    pub fn as_mut_unlocked(&mut self) -> &mut UnlockedItem {
71        match self {
72            Self::Unlocked(item) => item,
73            _ => panic!("The item is locked"),
74        }
75    }
76
77    pub fn as_locked(&self) -> &LockedItem {
78        match self {
79            Self::Locked(item) => item,
80            _ => panic!("The item is unlocked"),
81        }
82    }
83
84    /// Check if this item matches the given attributes
85    pub fn matches_attributes(&self, attributes: &impl AsAttributes, key: Option<&Key>) -> bool {
86        match self {
87            Self::Unlocked(unlocked) => {
88                let item_attrs = unlocked.attributes();
89                attributes.search_attributes().iter().all(|(k, value)| {
90                    item_attrs.get(k.as_str()).map(|v| v.as_ref()) == Some(value.as_str())
91                })
92            }
93            Self::Locked(locked) => locked.inner.matches(attributes, key),
94        }
95    }
96}
97
98#[derive(Debug)]
99pub enum Keyring {
100    Locked(LockedKeyring),
101    Unlocked(UnlockedKeyring),
102}
103
104impl From<LockedKeyring> for Keyring {
105    fn from(keyring: LockedKeyring) -> Self {
106        Self::Locked(keyring)
107    }
108}
109
110impl From<UnlockedKeyring> for Keyring {
111    fn from(keyring: UnlockedKeyring) -> Self {
112        Self::Unlocked(keyring)
113    }
114}
115
116impl Keyring {
117    /// Validate that a secret can decrypt the items in this keyring.
118    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
119    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
120        match self {
121            Self::Locked(keyring) => keyring.validate_secret(secret).await,
122            Self::Unlocked(keyring) => keyring.validate_secret(secret).await,
123        }
124    }
125
126    pub async fn validate_unencrypted(&self) -> Result<bool, Error> {
127        match self {
128            Self::Locked(keyring) => keyring.validate_unencrypted().await,
129            Self::Unlocked(keyring) => keyring.validate_unencrypted().await,
130        }
131    }
132
133    /// Get the modification timestamp
134    pub async fn modified_time(&self) -> std::time::Duration {
135        match self {
136            Self::Locked(keyring) => keyring.modified_time().await,
137            Self::Unlocked(keyring) => keyring.modified_time().await,
138        }
139    }
140
141    /// Get the creation timestamp from the filesystem if the keyring has an
142    /// associated file.
143    pub async fn created_time(&self) -> Option<std::time::Duration> {
144        let path = self.path()?;
145
146        #[cfg(feature = "tokio")]
147        let metadata = tokio::fs::metadata(path).await.ok()?;
148        #[cfg(feature = "async-std")]
149        let metadata = async_fs::metadata(path).await.ok()?;
150
151        metadata
152            .created()
153            .ok()
154            .and_then(|time| time.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
155    }
156
157    pub async fn items(&self) -> Result<Vec<Item>, Error> {
158        match self {
159            Self::Locked(keyring) => Ok(keyring
160                .items()
161                .await?
162                .into_iter()
163                .map(Item::Locked)
164                .collect()),
165            Self::Unlocked(keyring) => Ok(keyring
166                .items()
167                .await?
168                .into_iter()
169                .map(Item::Unlocked)
170                .collect()),
171        }
172    }
173
174    /// Return the associated file if any.
175    pub fn path(&self) -> Option<&std::path::Path> {
176        match self {
177            Self::Locked(keyring) => keyring.path(),
178            Self::Unlocked(keyring) => keyring.path(),
179        }
180    }
181
182    pub const fn is_locked(&self) -> bool {
183        matches!(self, Self::Locked(_))
184    }
185
186    pub fn as_unlocked(&self) -> &UnlockedKeyring {
187        match self {
188            Self::Unlocked(unlocked_keyring) => unlocked_keyring,
189            _ => panic!("The keyring is locked"),
190        }
191    }
192
193    pub fn as_locked(&self) -> &LockedKeyring {
194        match self {
195            Self::Locked(locked_keyring) => locked_keyring,
196            _ => panic!("The keyring is unlocked"),
197        }
198    }
199}