1
use std::{collections::HashMap, sync::Arc, time::Duration};
2

            
3
use ashpd::WindowIdentifier;
4
#[cfg(feature = "async-std")]
5
use async_lock::RwLock;
6
#[cfg(feature = "tokio")]
7
use tokio::sync::RwLock;
8
use zbus::zvariant::ObjectPath;
9

            
10
use super::{Algorithm, Error, api};
11
use crate::{AsAttributes, Key, Secret};
12

            
13
/// A secret with a label and attributes to identify it.
14
///
15
/// An item might be locked or unlocked, use [`Item::lock`] or [`Item::unlock`]
16
/// to lock or unlock it. Note that the Secret Service might not be able to
17
/// lock/unlock individual items and may lock/unlock the entire collection in
18
/// such case.
19
///
20
/// The item is attributes are used to identify and find the item later using
21
/// [`Collection::search_items`](crate::dbus::Collection::search_items).
22
/// They are not stored or transferred in a secure manner.
23
///
24
/// **Note**
25
///
26
/// If the item is deleted using [`Item::delete`] any future usage of it API
27
/// will fail with [`Error::Deleted`].
28
#[derive(Debug)]
29
pub struct Item {
30
    inner: Arc<api::Item>,
31
    session: Arc<api::Session>,
32
    service: Arc<api::Service>,
33
    algorithm: Algorithm,
34
    /// Defines whether the Item has been deleted or not
35
    available: RwLock<bool>,
36
    aes_key: Option<Arc<Key>>,
37
}
38

            
39
impl Item {
40
    pub(crate) fn new(
41
        service: Arc<api::Service>,
42
        session: Arc<api::Session>,
43
        algorithm: Algorithm,
44
        item: api::Item,
45
        aes_key: Option<Arc<Key>>,
46
    ) -> Self {
47
        Self {
48
            inner: Arc::new(item),
49
            service,
50
            session,
51
            algorithm,
52
            available: RwLock::new(true),
53
            aes_key,
54
        }
55
    }
56

            
57
    pub(crate) async fn is_available(&self) -> bool {
58
        *self.available.read().await
59
    }
60

            
61
    /// Get whether the item is locked.
62
    pub async fn is_locked(&self) -> Result<bool, Error> {
63
        if !self.is_available().await {
64
            Err(Error::Deleted)
65
        } else {
66
            self.inner.is_locked().await
67
        }
68
    }
69

            
70
    /// The item label.
71
    pub async fn label(&self) -> Result<String, Error> {
72
        if !self.is_available().await {
73
            Err(Error::Deleted)
74
        } else {
75
            self.inner.label().await
76
        }
77
    }
78

            
79
    /// Set the item label.
80
    pub async fn set_label(&self, label: &str) -> Result<(), Error> {
81
        if !self.is_available().await {
82
            Err(Error::Deleted)
83
        } else {
84
            self.inner.set_label(label).await
85
        }
86
    }
87

            
88
    /// The UNIX time when the item was created.
89
    pub async fn created(&self) -> Result<Duration, Error> {
90
        if !self.is_available().await {
91
            Err(Error::Deleted)
92
        } else {
93
            self.inner.created().await
94
        }
95
    }
96

            
97
    /// The UNIX time when the item was modified.
98
    pub async fn modified(&self) -> Result<Duration, Error> {
99
        if !self.is_available().await {
100
            Err(Error::Deleted)
101
        } else {
102
            self.inner.modified().await
103
        }
104
    }
105

            
106
    /// Retrieve the item attributes.
107
    pub async fn attributes(&self) -> Result<HashMap<String, String>, Error> {
108
        if !self.is_available().await {
109
            Err(Error::Deleted)
110
        } else {
111
            self.inner.attributes().await
112
        }
113
    }
114

            
115
    /// Retrieve the item attributes as a typed schema.
116
    ///
117
    /// # Example
118
    ///
119
    /// ```no_run
120
    /// # use oo7::{SecretSchema, dbus::Item};
121
    /// # #[derive(SecretSchema, Debug)]
122
    /// # #[schema(name = "org.example.Password")]
123
    /// # struct PasswordSchema {
124
    /// #     username: String,
125
    /// #     server: String,
126
    /// # }
127
    /// # async fn example(item: &Item) -> Result<(), Box<dyn std::error::Error>> {
128
    /// let schema = item.attributes_as::<PasswordSchema>().await?;
129
    /// println!("Username: {}", schema.username);
130
    /// # Ok(())
131
    /// # }
132
    /// ```
133
    #[cfg(feature = "schema")]
134
    #[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
135
    pub async fn attributes_as<T>(&self) -> Result<T, Error>
136
    where
137
        T: std::convert::TryFrom<HashMap<String, String>, Error = crate::SchemaError>,
138
    {
139
        let attrs = self.attributes().await?;
140
        T::try_from(attrs).map_err(Into::into)
141
    }
142

            
143
    /// Update the item attributes.
144
    pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<(), Error> {
145
        if !self.is_available().await {
146
            Err(Error::Deleted)
147
        } else {
148
            self.inner.set_attributes(attributes).await
149
        }
150
    }
151

            
152
    /// Delete the item.
153
    pub async fn delete(&self, window_id: Option<WindowIdentifier>) -> Result<(), Error> {
154
        if !self.is_available().await {
155
            Err(Error::Deleted)
156
        } else {
157
            self.inner.delete(window_id).await?;
158
            *self.available.write().await = false;
159
            Ok(())
160
        }
161
    }
162

            
163
    /// Retrieve the currently stored secret.
164
    pub async fn secret(&self) -> Result<Secret, Error> {
165
        if !self.is_available().await {
166
            Err(Error::Deleted)
167
        } else {
168
            self.inner
169
                .secret(&self.session)
170
                .await?
171
                .decrypt(self.aes_key.as_ref())
172
        }
173
    }
174

            
175
    /// Modify the stored secret on the item.
176
    ///
177
    /// # Arguments
178
    ///
179
    /// * `secret` - The secret to store.
180
    #[doc(alias = "SetSecret")]
181
    pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<(), Error> {
182
        if !self.is_available().await {
183
            Err(Error::Deleted)
184
        } else {
185
            let secret = match self.algorithm {
186
                Algorithm::Plain => api::DBusSecret::new(Arc::clone(&self.session), secret),
187
                Algorithm::Encrypted => {
188
                    let aes_key = self.aes_key.as_ref().unwrap();
189
                    api::DBusSecret::new_encrypted(Arc::clone(&self.session), secret, aes_key)?
190
                }
191
            };
192
            self.inner.set_secret(&secret).await?;
193
            Ok(())
194
        }
195
    }
196

            
197
    /// Unlock the item.
198
    pub async fn unlock(&self, window_id: Option<WindowIdentifier>) -> Result<(), Error> {
199
        if !self.is_available().await {
200
            Err(Error::Deleted)
201
        } else {
202
            self.service
203
                .unlock(&[self.inner.inner().path()], window_id)
204
                .await?;
205
            Ok(())
206
        }
207
    }
208

            
209
    /// Lock the item.
210
    pub async fn lock(&self, window_id: Option<WindowIdentifier>) -> Result<(), Error> {
211
        if !self.is_available().await {
212
            Err(Error::Deleted)
213
        } else {
214
            self.service
215
                .lock(&[self.inner.inner().path()], window_id)
216
                .await?;
217
            Ok(())
218
        }
219
    }
220

            
221
    /// Returns item path
222
    pub fn path(&self) -> &ObjectPath<'_> {
223
        self.inner.inner().path()
224
    }
225
}