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", 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
19
    fn from(item: UnlockedItem) -> Self {
48
16
        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
12
    pub fn matches_attributes(&self, attributes: &impl AsAttributes, key: &Key) -> bool {
86
13
        match self {
87
12
            Self::Unlocked(unlocked) => {
88
13
                let item_attrs = unlocked.attributes();
89
50
                attributes.as_attributes().iter().all(|(k, value)| {
90
41
                    item_attrs.get(k.as_str()).map(|v| v.as_ref()) == Some(value.as_str())
91
                })
92
            }
93
4
            Self::Locked(locked) => {
94
4
                let hashed_attrs = attributes.hash(key);
95

            
96
16
                hashed_attrs.iter().all(|(attr_key, mac_result)| {
97
                    mac_result
98
4
                        .as_ref()
99
4
                        .ok()
100
12
                        .map(|mac| locked.inner.has_attribute(attr_key.as_str(), mac))
101
4
                        .unwrap_or(false)
102
                })
103
            }
104
        }
105
    }
106
}
107

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

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

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

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

            
136
    /// Get the modification timestamp
137
111
    pub async fn modified_time(&self) -> std::time::Duration {
138
32
        match self {
139
8
            Self::Locked(keyring) => keyring.modified_time().await,
140
54
            Self::Unlocked(keyring) => keyring.modified_time().await,
141
        }
142
    }
143

            
144
    /// Get the creation timestamp from the filesystem if the keyring has an
145
    /// associated file.
146
121
    pub async fn created_time(&self) -> Option<std::time::Duration> {
147
88
        let path = self.path()?;
148

            
149
        #[cfg(feature = "tokio")]
150
25
        let metadata = tokio::fs::metadata(path).await.ok()?;
151
        #[cfg(feature = "async-std")]
152
        let metadata = async_fs::metadata(path).await.ok()?;
153

            
154
14
        metadata
155
            .created()
156
            .ok()
157
38
            .and_then(|time| time.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
158
    }
159

            
160
155
    pub async fn items(&self) -> Result<Vec<Item>, Error> {
161
30
        match self {
162
20
            Self::Locked(keyring) => Ok(keyring
163
4
                .items()
164
12
                .await?
165
4
                .into_iter()
166
4
                .map(Item::Locked)
167
4
                .collect()),
168
158
            Self::Unlocked(keyring) => Ok(keyring
169
30
                .items()
170
136
                .await?
171
36
                .into_iter()
172
32
                .map(Item::Unlocked)
173
72
                .collect()),
174
        }
175
    }
176

            
177
    /// Return the associated file if any.
178
32
    pub fn path(&self) -> Option<&std::path::Path> {
179
28
        match self {
180
4
            Self::Locked(keyring) => keyring.path(),
181
32
            Self::Unlocked(keyring) => keyring.path(),
182
        }
183
    }
184

            
185
18
    pub const fn is_locked(&self) -> bool {
186
20
        matches!(self, Self::Locked(_))
187
    }
188

            
189
16
    pub fn as_unlocked(&self) -> &UnlockedKeyring {
190
18
        match self {
191
16
            Self::Unlocked(unlocked_keyring) => unlocked_keyring,
192
            _ => panic!("The keyring is locked"),
193
        }
194
    }
195

            
196
    pub fn as_locked(&self) -> &LockedKeyring {
197
        match self {
198
            Self::Locked(locked_keyring) => locked_keyring,
199
            _ => panic!("The keyring is unlocked"),
200
        }
201
    }
202
}