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!(
14
//!     items[0].as_unlocked().secret(),
15
//!     oo7::Secret::blob("My Password")
16
//! );
17
//!
18
//! keyring.delete(&[("account", "alice")]).await?;
19
//! #   Ok(())
20
//! # }
21
//! ```
22

            
23
#[cfg(feature = "unstable")]
24
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
25
pub mod api;
26
#[cfg(not(feature = "unstable"))]
27
pub(crate) mod api;
28

            
29
pub(crate) use api::AttributeValue;
30

            
31
mod error;
32
mod locked_item;
33
mod locked_keyring;
34
mod unlocked_item;
35
mod unlocked_keyring;
36

            
37
pub use error::{Error, InvalidItemError, WeakKeyError};
38
pub use locked_item::LockedItem;
39
pub use locked_keyring::LockedKeyring;
40
pub use unlocked_item::UnlockedItem;
41
pub use unlocked_keyring::UnlockedKeyring;
42

            
43
use crate::{AsAttributes, Key, Secret};
44

            
45
#[derive(Debug)]
46
pub enum Item {
47
    Locked(LockedItem),
48
    Unlocked(UnlockedItem),
49
}
50

            
51
impl Item {
52
4
    pub const fn is_locked(&self) -> bool {
53
4
        matches!(self, Self::Locked(_))
54
    }
55

            
56
4
    pub fn as_unlocked(&self) -> &UnlockedItem {
57
4
        match self {
58
4
            Self::Unlocked(item) => item,
59
            _ => panic!("The item is locked"),
60
        }
61
    }
62

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

            
70
    pub fn as_locked(&self) -> &LockedItem {
71
        match self {
72
            Self::Locked(item) => item,
73
            _ => panic!("The item is unlocked"),
74
        }
75
    }
76

            
77
    /// Check if this item matches the given attributes
78
2
    pub fn matches_attributes(&self, attributes: &impl AsAttributes, key: &Key) -> bool {
79
2
        match self {
80
2
            Self::Unlocked(unlocked) => {
81
2
                let item_attrs = unlocked.attributes();
82
8
                attributes.as_attributes().iter().all(|(k, value)| {
83
6
                    item_attrs.get(&k.to_string()).map(|v| v.as_ref()) == Some(value)
84
                })
85
            }
86
2
            Self::Locked(locked) => {
87
2
                let hashed_attrs = attributes.hash(key);
88

            
89
8
                hashed_attrs.iter().all(|(attr_key, mac_result)| {
90
                    mac_result
91
2
                        .as_ref()
92
2
                        .ok()
93
6
                        .map(|mac| locked.inner.has_attribute(attr_key, mac))
94
2
                        .unwrap_or(false)
95
                })
96
            }
97
        }
98
    }
99
}
100

            
101
#[derive(Debug)]
102
pub enum Keyring {
103
    Locked(LockedKeyring),
104
    Unlocked(UnlockedKeyring),
105
}
106

            
107
impl Keyring {
108
    /// Validate that a secret can decrypt the items in this keyring.
109
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
110
8
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
111
2
        match self {
112
4
            Self::Locked(keyring) => keyring.validate_secret(secret).await,
113
            Self::Unlocked(keyring) => keyring.validate_secret(secret).await,
114
        }
115
    }
116

            
117
    /// Get the modification timestamp
118
16
    pub async fn modified_time(&self) -> std::time::Duration {
119
2
        match self {
120
4
            Self::Locked(keyring) => keyring.modified_time().await,
121
9
            Self::Unlocked(keyring) => keyring.modified_time().await,
122
        }
123
    }
124

            
125
    /// Get the creation timestamp from the filesystem if the keyring has an
126
    /// associated file.
127
16
    pub async fn created_time(&self) -> Option<std::time::Duration> {
128
11
        let path = self.path()?;
129

            
130
        #[cfg(feature = "tokio")]
131
4
        let metadata = tokio::fs::metadata(path).await.ok()?;
132
        #[cfg(feature = "async-std")]
133
        let metadata = async_fs::metadata(path).await.ok()?;
134

            
135
2
        metadata
136
            .created()
137
            .ok()
138
6
            .and_then(|time| time.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
139
    }
140

            
141
16
    pub async fn items(&self) -> Result<Vec<Result<Item, InvalidItemError>>, Error> {
142
5
        match self {
143
4
            Self::Locked(keyring) => keyring.items().await,
144
11
            Self::Unlocked(keyring) => keyring.items().await,
145
        }
146
    }
147

            
148
    /// Return the associated file if any.
149
5
    pub fn path(&self) -> Option<&std::path::Path> {
150
3
        match self {
151
2
            Self::Locked(keyring) => keyring.path(),
152
3
            Self::Unlocked(keyring) => keyring.path(),
153
        }
154
    }
155

            
156
5
    pub const fn is_locked(&self) -> bool {
157
6
        matches!(self, Self::Locked(_))
158
    }
159

            
160
2
    pub fn as_unlocked(&self) -> &UnlockedKeyring {
161
2
        match self {
162
2
            Self::Unlocked(unlocked_keyring) => unlocked_keyring,
163
            _ => panic!("The keyring is locked"),
164
        }
165
    }
166

            
167
    pub fn as_locked(&self) -> &LockedKeyring {
168
        match self {
169
            Self::Locked(locked_keyring) => locked_keyring,
170
            _ => panic!("The keyring is unlocked"),
171
        }
172
    }
173
}