Skip to main content

oo7/dbus/
item.rs

1use std::{collections::HashMap, sync::Arc, time::Duration};
2
3use ashpd::WindowIdentifier;
4#[cfg(feature = "async-std")]
5use async_lock::RwLock;
6#[cfg(feature = "tokio")]
7use tokio::sync::RwLock;
8use zbus::zvariant::ObjectPath;
9
10use super::{Algorithm, Error, api};
11use 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)]
29pub 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
39impl 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}