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")))]
22
pub mod api;
23
#[cfg(not(feature = "unstable"))]
24
pub(crate) mod api;
25

            
26
mod error;
27
mod locked_item;
28
mod locked_keyring;
29
mod unlocked_item;
30
mod unlocked_keyring;
31

            
32
pub use error::{Error, InvalidItemError, WeakKeyError};
33
pub use locked_item::LockedItem;
34
pub use locked_keyring::LockedKeyring;
35
pub use unlocked_item::UnlockedItem;
36
pub use unlocked_keyring::{ItemDefinition, UnlockedKeyring};
37

            
38
use crate::{AsAttributes, Key, Secret};
39

            
40
#[derive(Debug)]
41
pub enum Item {
42
    Locked(LockedItem),
43
    Unlocked(UnlockedItem),
44
}
45

            
46
impl From<UnlockedItem> for Item {
47
15
    fn from(item: UnlockedItem) -> Self {
48
17
        Self::Unlocked(item)
49
    }
50
}
51

            
52
impl From<LockedItem> for Item {
53
    fn from(item: LockedItem) -> Self {
54
        Self::Locked(item)
55
    }
56
}
57

            
58
impl Item {
59
13
    pub const fn is_locked(&self) -> bool {
60
13
        matches!(self, Self::Locked(_))
61
    }
62

            
63
13
    pub fn as_unlocked(&self) -> &UnlockedItem {
64
13
        match self {
65
13
            Self::Unlocked(item) => item,
66
            _ => panic!("The item is locked"),
67
        }
68
    }
69

            
70
10
    pub fn as_mut_unlocked(&mut self) -> &mut UnlockedItem {
71
10
        match self {
72
10
            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
10
    pub fn matches_attributes(&self, attributes: &impl AsAttributes, key: Option<&Key>) -> bool {
86
11
        match self {
87
10
            Self::Unlocked(unlocked) => {
88
10
                let item_attrs = unlocked.attributes();
89
41
                attributes.search_attributes().iter().all(|(k, value)| {
90
32
                    item_attrs.get(k.as_str()).map(|v| v.as_ref()) == Some(value.as_str())
91
                })
92
            }
93
4
            Self::Locked(locked) => locked.inner.matches(attributes, key),
94
        }
95
    }
96

            
97
    /// Check if this item has exactly the given attributes.
98
8
    pub fn matches_attributes_exact(
99
        &self,
100
        attributes: &impl AsAttributes,
101
        key: Option<&Key>,
102
    ) -> bool {
103
8
        match self {
104
8
            Self::Unlocked(unlocked) => unlocked.matches_exact(attributes),
105
            Self::Locked(locked) => locked.inner.matches_exact(attributes, key),
106
        }
107
    }
108
}
109

            
110
#[derive(Debug)]
111
pub enum Keyring {
112
    Locked(LockedKeyring),
113
    Unlocked(UnlockedKeyring),
114
}
115

            
116
impl From<LockedKeyring> for Keyring {
117
    fn from(keyring: LockedKeyring) -> Self {
118
        Self::Locked(keyring)
119
    }
120
}
121

            
122
impl From<UnlockedKeyring> for Keyring {
123
    fn from(keyring: UnlockedKeyring) -> Self {
124
        Self::Unlocked(keyring)
125
    }
126
}
127

            
128
impl Keyring {
129
    /// Validate that a secret can decrypt the items in this keyring.
130
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
131
35
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
132
8
        match self {
133
16
            Self::Locked(keyring) => keyring.validate_secret(secret).await,
134
            Self::Unlocked(keyring) => keyring.validate_secret(secret).await,
135
        }
136
    }
137

            
138
    pub async fn validate_unencrypted(&self) -> Result<bool, Error> {
139
        match self {
140
            Self::Locked(keyring) => keyring.validate_unencrypted().await,
141
            Self::Unlocked(keyring) => keyring.validate_unencrypted().await,
142
        }
143
    }
144

            
145
    /// Get the modification timestamp
146
116
    pub async fn modified_time(&self) -> std::time::Duration {
147
28
        match self {
148
8
            Self::Locked(keyring) => keyring.modified_time().await,
149
56
            Self::Unlocked(keyring) => keyring.modified_time().await,
150
        }
151
    }
152

            
153
    /// Get the creation timestamp from the filesystem if the keyring has an
154
    /// associated file.
155
111
    pub async fn created_time(&self) -> Option<std::time::Duration> {
156
87
        let path = self.path()?;
157

            
158
        #[cfg(feature = "tokio")]
159
        let metadata = tokio::fs::metadata(path).await.ok()?;
160
        #[cfg(feature = "async-std")]
161
        let metadata = async_fs::metadata(path).await.ok()?;
162

            
163
13
        metadata
164
            .created()
165
            .ok()
166
35
            .and_then(|time| time.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
167
    }
168

            
169
144
    pub async fn items(&self) -> Result<Vec<Item>, Error> {
170
32
        match self {
171
20
            Self::Locked(keyring) => Ok(keyring
172
4
                .items()
173
12
                .await?
174
4
                .into_iter()
175
4
                .map(Item::Locked)
176
4
                .collect()),
177
161
            Self::Unlocked(keyring) => Ok(keyring
178
32
                .items()
179
120
                .await?
180
36
                .into_iter()
181
36
                .map(Item::Unlocked)
182
72
                .collect()),
183
        }
184
    }
185

            
186
    /// Return the associated file if any.
187
29
    pub fn path(&self) -> Option<&std::path::Path> {
188
30
        match self {
189
4
            Self::Locked(keyring) => keyring.path(),
190
30
            Self::Unlocked(keyring) => keyring.path(),
191
        }
192
    }
193

            
194
23
    pub const fn is_locked(&self) -> bool {
195
19
        matches!(self, Self::Locked(_))
196
    }
197

            
198
20
    pub fn as_unlocked(&self) -> &UnlockedKeyring {
199
16
        match self {
200
21
            Self::Unlocked(unlocked_keyring) => unlocked_keyring,
201
            _ => panic!("The keyring is locked"),
202
        }
203
    }
204

            
205
    pub fn as_locked(&self) -> &LockedKeyring {
206
        match self {
207
            Self::Locked(locked_keyring) => locked_keyring,
208
            _ => panic!("The keyring is unlocked"),
209
        }
210
    }
211
}