oo7/dbus/api/
service.rs

1use std::{collections::HashMap, fmt};
2
3use ashpd::WindowIdentifier;
4use futures_util::{Stream, StreamExt};
5use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value};
6
7use super::{
8    Collection, DBusSecret, DESTINATION, Item, PATH, Prompt, Properties, Session, Unlockable,
9};
10use crate::{
11    AsAttributes, Key,
12    dbus::{Algorithm, Error, ServiceError},
13};
14
15#[derive(Type)]
16#[zvariant(signature = "o")]
17#[doc(alias = "org.freedesktop.secrets")]
18pub struct Service<'a>(zbus::Proxy<'a>);
19
20impl zbus::proxy::Defaults for Service<'_> {
21    const INTERFACE: &'static Option<zbus::names::InterfaceName<'static>> = &Some(
22        zbus::names::InterfaceName::from_static_str_unchecked("org.freedesktop.Secret.Service"),
23    );
24    const DESTINATION: &'static Option<zbus::names::BusName<'static>> = &Some(DESTINATION);
25    const PATH: &'static Option<ObjectPath<'static>> = &Some(PATH);
26}
27
28impl<'a> From<zbus::Proxy<'a>> for Service<'a> {
29    fn from(value: zbus::Proxy<'a>) -> Self {
30        Self(value)
31    }
32}
33
34impl<'a> Service<'a> {
35    pub async fn new(connection: &zbus::Connection) -> Result<Service<'a>, Error> {
36        zbus::proxy::Builder::new(connection)
37            .build()
38            .await
39            .map_err(From::from)
40    }
41
42    pub fn inner(&self) -> &zbus::Proxy<'_> {
43        &self.0
44    }
45
46    #[doc(alias = "CollectionCreated")]
47    pub async fn receive_collection_created(
48        &self,
49    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
50        let stream = self.inner().receive_signal("CollectionCreated").await?;
51        let conn = self.inner().connection();
52        Ok(stream.filter_map(move |message| async move {
53            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
54            Collection::new(conn, path).await.ok()
55        }))
56    }
57
58    #[doc(alias = "CollectionDeleted")]
59    pub async fn receive_collection_deleted(
60        &self,
61    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
62        let stream = self.inner().receive_signal("CollectionDeleted").await?;
63        Ok(stream.filter_map(move |message| async move {
64            message.body().deserialize::<OwnedObjectPath>().ok()
65        }))
66    }
67
68    #[doc(alias = "CollectionChanged")]
69    pub async fn receive_collection_changed(
70        &self,
71    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
72        let stream = self.inner().receive_signal("CollectionChanged").await?;
73        let conn = self.inner().connection();
74        Ok(stream.filter_map(move |message| async move {
75            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
76            Collection::new(conn, path).await.ok()
77        }))
78    }
79
80    pub async fn collections(&self) -> Result<Vec<Collection<'a>>, Error> {
81        let collections_paths = self
82            .inner()
83            .get_property::<Vec<ObjectPath>>("Collections")
84            .await?;
85        Collection::from_paths(self.inner().connection(), collections_paths).await
86    }
87
88    #[doc(alias = "OpenSession")]
89    pub async fn open_session(
90        &self,
91        client_public_key: Option<Key>,
92    ) -> Result<(Option<Key>, Session<'a>), Error> {
93        let (algorithm, key): (_, Value<'_>) = match client_public_key {
94            None => (Algorithm::Plain, zvariant::Str::default().into()),
95            Some(key) => (Algorithm::Encrypted, key.into()),
96        };
97        let (service_key, session_path) = self
98            .inner()
99            .call_method("OpenSession", &(&algorithm, key))
100            .await
101            .map_err::<ServiceError, _>(From::from)?
102            .body()
103            .deserialize::<(OwnedValue, OwnedObjectPath)>()?;
104        let session = Session::new(self.inner().connection(), session_path).await?;
105
106        let key = match algorithm {
107            Algorithm::Plain => None,
108            Algorithm::Encrypted => Some(Key::try_from(service_key)?),
109        };
110
111        Ok((key, session))
112    }
113
114    #[doc(alias = "CreateCollection")]
115    pub async fn create_collection(
116        &self,
117        label: &str,
118        alias: &str,
119        window_id: Option<WindowIdentifier>,
120    ) -> Result<Collection<'a>, Error> {
121        let properties = Properties::for_collection(label);
122        let (collection_path, prompt_path) = self
123            .inner()
124            .call_method("CreateCollection", &(properties, alias))
125            .await
126            .map_err::<ServiceError, _>(From::from)?
127            .body()
128            .deserialize::<(OwnedObjectPath, OwnedObjectPath)>()?;
129
130        let collection_path =
131            if let Some(prompt) = Prompt::new(self.inner().connection(), prompt_path).await? {
132                let response = prompt.receive_completed(window_id).await?;
133                OwnedObjectPath::try_from(response)?
134            } else {
135                collection_path
136            };
137        Collection::new(self.inner().connection(), collection_path).await
138    }
139
140    #[doc(alias = "SearchItems")]
141    pub async fn search_items(
142        &self,
143        attributes: &impl AsAttributes,
144    ) -> Result<(Vec<Item<'a>>, Vec<Item<'a>>), Error> {
145        let (unlocked_item_paths, locked_item_paths) = self
146            .inner()
147            .call_method("SearchItems", &(attributes.as_attributes()))
148            .await
149            .map_err::<ServiceError, _>(From::from)?
150            .body()
151            .deserialize::<(Vec<OwnedObjectPath>, Vec<OwnedObjectPath>)>()?;
152        let cnx = self.inner().connection();
153
154        let unlocked_items = Item::from_paths(cnx, unlocked_item_paths).await?;
155        let locked_items = Item::from_paths(cnx, locked_item_paths).await?;
156
157        Ok((unlocked_items, locked_items))
158    }
159
160    pub async fn unlock(
161        &self,
162        items: &[impl Unlockable],
163        window_id: Option<WindowIdentifier>,
164    ) -> Result<Vec<OwnedObjectPath>, Error> {
165        let (mut unlocked_item_paths, prompt_path) = self
166            .inner()
167            .call_method("Unlock", &(items))
168            .await
169            .map_err::<ServiceError, _>(From::from)?
170            .body()
171            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
172        let cnx = self.inner().connection();
173
174        if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
175            let response = prompt.receive_completed(window_id).await?;
176            let locked_paths = Vec::<OwnedObjectPath>::try_from(response)?;
177            unlocked_item_paths.extend(locked_paths);
178        };
179        Ok(unlocked_item_paths)
180    }
181
182    pub async fn lock(
183        &self,
184        items: &[impl Unlockable],
185        window_id: Option<WindowIdentifier>,
186    ) -> Result<Vec<OwnedObjectPath>, Error> {
187        let (mut locked_item_paths, prompt_path) = self
188            .inner()
189            .call_method("Lock", &(items))
190            .await
191            .map_err::<ServiceError, _>(From::from)?
192            .body()
193            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
194        let cnx = self.inner().connection();
195
196        if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
197            let response = prompt.receive_completed(window_id).await?;
198            let locked_paths = Vec::<OwnedObjectPath>::try_from(response)?;
199            locked_item_paths.extend(locked_paths);
200        };
201
202        Ok(locked_item_paths)
203    }
204
205    #[doc(alias = "GetSecrets")]
206    pub async fn secrets(
207        &self,
208        items: &[Item<'_>],
209        session: &Session<'_>,
210    ) -> Result<HashMap<Item<'_>, DBusSecret<'_>>, Error> {
211        let secrets = self
212            .inner()
213            .call_method("GetSecrets", &(items, session))
214            .await
215            .map_err::<ServiceError, _>(From::from)?
216            .body()
217            .deserialize::<HashMap<OwnedObjectPath, super::secret::DBusSecretInner>>()?;
218
219        let cnx = self.inner().connection();
220        // Item's Hash implementation doesn't make use of any mutable internals
221        #[allow(clippy::mutable_key_type)]
222        let mut output = HashMap::with_capacity(secrets.capacity());
223        for (path, secret_inner) in secrets {
224            output.insert(
225                Item::new(cnx, path).await?,
226                DBusSecret::from_inner(cnx, secret_inner).await?,
227            );
228        }
229
230        Ok(output)
231    }
232
233    #[doc(alias = "ReadAlias")]
234    pub async fn read_alias(&self, name: &str) -> Result<Option<Collection<'a>>, Error> {
235        let collection_path = self
236            .inner()
237            .call_method("ReadAlias", &(name))
238            .await
239            .map_err::<ServiceError, _>(From::from)?
240            .body()
241            .deserialize::<OwnedObjectPath>()?;
242
243        if collection_path != OwnedObjectPath::default() {
244            let collection = Collection::new(self.inner().connection(), collection_path).await?;
245            Ok(Some(collection))
246        } else {
247            Ok(None)
248        }
249    }
250
251    #[doc(alias = "SetAlias")]
252    pub async fn set_alias(&self, name: &str, collection: &Collection<'_>) -> Result<(), Error> {
253        self.inner()
254            .call_method("SetAlias", &(name, collection))
255            .await
256            .map_err::<ServiceError, _>(From::from)?;
257        Ok(())
258    }
259}
260
261impl fmt::Debug for Service<'_> {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.debug_tuple("Service")
264            .field(&self.inner().path().as_str())
265            .finish()
266    }
267}