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
mod error;
30
mod locked_item;
31
mod locked_keyring;
32
mod unlocked_item;
33
mod unlocked_keyring;
34

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

            
41
use crate::{AsAttributes, Key, Secret};
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
154
3
    pub const fn is_locked(&self) -> bool {
155
3
        matches!(self, Self::Locked(_))
156
    }
157

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

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