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 #[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}