1
use std::{collections::HashMap, fmt};
2

            
3
use ashpd::WindowIdentifier;
4
use futures_util::{Stream, StreamExt};
5
use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value};
6

            
7
use super::{
8
    Collection, DBusSecret, DESTINATION, Item, PATH, Prompt, Properties, Session, Unlockable,
9
};
10
use crate::{
11
    AsAttributes, Key,
12
    dbus::{Algorithm, Error, ServiceError},
13
};
14

            
15
#[derive(Type)]
16
#[zvariant(signature = "o")]
17
#[doc(alias = "org.freedesktop.secrets")]
18
pub struct Service<'a>(zbus::Proxy<'a>);
19

            
20
impl 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

            
28
impl<'a> From<zbus::Proxy<'a>> for Service<'a> {
29
4
    fn from(value: zbus::Proxy<'a>) -> Self {
30
        Self(value)
31
    }
32
}
33

            
34
impl<'a> Service<'a> {
35
25
    pub async fn new(connection: &zbus::Connection) -> Result<Service<'a>, Error> {
36
16
        zbus::proxy::Builder::new(connection)
37
            .build()
38
21
            .await
39
9
            .map_err(From::from)
40
    }
41

            
42
8
    pub fn inner(&self) -> &zbus::Proxy<'_> {
43
        &self.0
44
    }
45

            
46
    #[doc(alias = "CollectionCreated")]
47
2
    pub async fn receive_collection_created(
48
        &self,
49
    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
50
6
        let stream = self.inner().receive_signal("CollectionCreated").await?;
51
4
        let conn = self.inner().connection();
52
10
        Ok(stream.filter_map(move |message| async move {
53
4
            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
54
2
            Collection::new(conn, path).await.ok()
55
        }))
56
    }
57

            
58
    #[doc(alias = "CollectionDeleted")]
59
2
    pub async fn receive_collection_deleted(
60
        &self,
61
    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
62
6
        let stream = self.inner().receive_signal("CollectionDeleted").await?;
63
12
        Ok(stream.filter_map(move |message| async move {
64
4
            message.body().deserialize::<OwnedObjectPath>().ok()
65
        }))
66
    }
67

            
68
    #[doc(alias = "CollectionChanged")]
69
2
    pub async fn receive_collection_changed(
70
        &self,
71
    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
72
6
        let stream = self.inner().receive_signal("CollectionChanged").await?;
73
4
        let conn = self.inner().connection();
74
10
        Ok(stream.filter_map(move |message| async move {
75
4
            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
76
2
            Collection::new(conn, path).await.ok()
77
        }))
78
    }
79

            
80
12
    pub async fn collections(&self) -> Result<Vec<Collection<'a>>, Error> {
81
15
        let collections_paths = self
82
            .inner()
83
            .get_property::<Vec<ObjectPath>>("Collections")
84
14
            .await?;
85
9
        Collection::from_paths(self.inner().connection(), collections_paths).await
86
    }
87

            
88
    #[doc(alias = "OpenSession")]
89
8
    pub async fn open_session(
90
        &self,
91
        client_public_key: Option<Key>,
92
    ) -> Result<(Option<Key>, Session<'a>), Error> {
93
16
        let (algorithm, key): (_, Value<'_>) = match client_public_key {
94
16
            None => (Algorithm::Plain, zvariant::Str::default().into()),
95
8
            Some(key) => (Algorithm::Encrypted, key.into()),
96
        };
97
30
        let (service_key, session_path) = self
98
            .inner()
99
8
            .call_method("OpenSession", &(&algorithm, key))
100
28
            .await
101
4
            .map_err::<ServiceError, _>(From::from)?
102
            .body()
103
            .deserialize::<(OwnedValue, OwnedObjectPath)>()?;
104
6
        let session = Session::new(self.inner().connection(), session_path).await?;
105

            
106
5
        let key = match algorithm {
107
5
            Algorithm::Plain => None,
108
8
            Algorithm::Encrypted => Some(Key::try_from(service_key)?),
109
        };
110

            
111
5
        Ok((key, session))
112
    }
113

            
114
    #[doc(alias = "CreateCollection")]
115
2
    pub async fn create_collection(
116
        &self,
117
        label: &str,
118
        alias: Option<&str>,
119
        window_id: Option<WindowIdentifier>,
120
    ) -> Result<Collection<'a>, Error> {
121
4
        let properties = Properties::for_collection(label);
122
10
        let (collection_path, prompt_path) = self
123
            .inner()
124
2
            .call_method("CreateCollection", &(properties, alias.unwrap_or_default()))
125
8
            .await
126
2
            .map_err::<ServiceError, _>(From::from)?
127
            .body()
128
            .deserialize::<(OwnedObjectPath, OwnedObjectPath)>()?;
129

            
130
4
        let collection_path =
131
            if let Some(prompt) = Prompt::new(self.inner().connection(), prompt_path).await? {
132
8
                let response = prompt.receive_completed(window_id).await?;
133
4
                OwnedObjectPath::try_from(response)?
134
            } else {
135
                collection_path
136
            };
137
4
        Collection::new(self.inner().connection(), collection_path).await
138
    }
139

            
140
    #[doc(alias = "SearchItems")]
141
4
    pub async fn search_items(
142
        &self,
143
        attributes: &impl AsAttributes,
144
    ) -> Result<(Vec<Item<'a>>, Vec<Item<'a>>), Error> {
145
20
        let (unlocked_item_paths, locked_item_paths) = self
146
            .inner()
147
4
            .call_method("SearchItems", &(attributes.as_attributes()))
148
16
            .await
149
4
            .map_err::<ServiceError, _>(From::from)?
150
            .body()
151
            .deserialize::<(Vec<OwnedObjectPath>, Vec<OwnedObjectPath>)>()?;
152
4
        let cnx = self.inner().connection();
153

            
154
4
        let unlocked_items = Item::from_paths(cnx, unlocked_item_paths).await?;
155
8
        let locked_items = Item::from_paths(cnx, locked_item_paths).await?;
156

            
157
4
        Ok((unlocked_items, locked_items))
158
    }
159

            
160
4
    pub async fn unlock(
161
        &self,
162
        items: &[impl Unlockable],
163
        window_id: Option<WindowIdentifier>,
164
    ) -> Result<Vec<OwnedObjectPath>, Error> {
165
20
        let (mut unlocked_item_paths, prompt_path) = self
166
            .inner()
167
4
            .call_method("Unlock", &(items))
168
16
            .await
169
4
            .map_err::<ServiceError, _>(From::from)?
170
            .body()
171
            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
172
4
        let cnx = self.inner().connection();
173

            
174
4
        if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
175
8
            let response = prompt.receive_completed(window_id).await?;
176
4
            let locked_paths = Vec::<OwnedObjectPath>::try_from(response)?;
177
2
            unlocked_item_paths.extend(locked_paths);
178
        };
179
4
        Ok(unlocked_item_paths)
180
    }
181

            
182
4
    pub async fn lock(
183
        &self,
184
        items: &[impl Unlockable],
185
        window_id: Option<WindowIdentifier>,
186
    ) -> Result<Vec<OwnedObjectPath>, Error> {
187
20
        let (mut locked_item_paths, prompt_path) = self
188
            .inner()
189
4
            .call_method("Lock", &(items))
190
16
            .await
191
4
            .map_err::<ServiceError, _>(From::from)?
192
            .body()
193
            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
194
4
        let cnx = self.inner().connection();
195

            
196
4
        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
4
        Ok(locked_item_paths)
203
    }
204

            
205
    #[doc(alias = "GetSecrets")]
206
2
    pub async fn secrets(
207
        &self,
208
        items: &[Item<'_>],
209
        session: &Session<'_>,
210
    ) -> Result<HashMap<Item<'_>, DBusSecret<'_>>, Error> {
211
14
        let secrets = self
212
            .inner()
213
2
            .call_method("GetSecrets", &(items, session))
214
8
            .await
215
4
            .map_err::<ServiceError, _>(From::from)?
216
            .body()
217
            .deserialize::<HashMap<OwnedObjectPath, super::secret::DBusSecretInner>>()?;
218

            
219
2
        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
2
        let mut output = HashMap::with_capacity(secrets.capacity());
223
8
        for (path, secret_inner) in secrets {
224
4
            output.insert(
225
8
                Item::new(cnx, path).await?,
226
8
                DBusSecret::from_inner(cnx, secret_inner).await?,
227
            );
228
        }
229

            
230
2
        Ok(output)
231
    }
232

            
233
    #[doc(alias = "ReadAlias")]
234
16
    pub async fn read_alias(&self, name: &str) -> Result<Option<Collection<'a>>, Error> {
235
20
        let collection_path = self
236
            .inner()
237
4
            .call_method("ReadAlias", &(name))
238
16
            .await
239
4
            .map_err::<ServiceError, _>(From::from)?
240
            .body()
241
            .deserialize::<OwnedObjectPath>()?;
242

            
243
8
        if collection_path != OwnedObjectPath::default() {
244
4
            let collection = Collection::new(self.inner().connection(), collection_path).await?;
245
4
            Ok(Some(collection))
246
        } else {
247
2
            Ok(None)
248
        }
249
    }
250

            
251
    #[doc(alias = "SetAlias")]
252
8
    pub async fn set_alias(&self, name: &str, collection: &Collection<'_>) -> Result<(), Error> {
253
12
        self.inner()
254
2
            .call_method("SetAlias", &(name, collection))
255
8
            .await
256
6
            .map_err::<ServiceError, _>(From::from)?;
257
2
        Ok(())
258
    }
259
}
260

            
261
impl 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
}