1
use std::sync::Arc;
2

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

            
7
use super::{Algorithm, Collection, Error, ServiceError, api};
8
use crate::Key;
9

            
10
/// The entry point of communicating with a [`org.freedesktop.Secrets`](https://specifications.freedesktop.org/secret-service-spec/latest/index.html) implementation.
11
///
12
/// It will automatically create a session for you and allow you to retrieve
13
/// collections or create new ones.
14
///
15
/// Certain actions requires on the Secret Service implementation requires a
16
/// user prompt to complete like creating a collection, locking or unlocking a
17
/// collection. The library handles that automatically for you.
18
///
19
/// ```no_run
20
/// use oo7::dbus::Service;
21
///
22
/// # async fn run() -> oo7::Result<()> {
23
/// let service = Service::new().await?;
24
/// let collection = service.default_collection().await?;
25
/// // Do something with the collection
26
///
27
/// #   Ok(())
28
/// }
29
/// ```
30
#[derive(Debug)]
31
pub struct Service<'a> {
32
    inner: Arc<api::Service<'a>>,
33
    aes_key: Option<Arc<Key>>,
34
    session: Arc<api::Session<'static>>,
35
    algorithm: Algorithm,
36
}
37

            
38
impl<'a> Service<'a> {
39
    /// The default collection alias.
40
    ///
41
    /// In general, you are supposed to use [`Service::default_collection`].
42
    pub const DEFAULT_COLLECTION: &'static str = "default";
43

            
44
    /// A session collection.
45
    ///
46
    /// The collection is cleared when the user ends the session.
47
    pub const SESSION_COLLECTION: &'static str = "session";
48

            
49
    /// Create a new instance of the Service, an encrypted communication would
50
    /// be attempted first and would fall back to a plain one if that fails.
51
8
    pub async fn new() -> Result<Service<'a>, Error> {
52
10
        let service = match Self::encrypted().await {
53
2
            Ok(service) => Ok(service),
54
            Err(Error::ZBus(zbus::Error::MethodError(..))) => Self::plain().await,
55
            Err(Error::Service(ServiceError::ZBus(zbus::Error::MethodError(..)))) => {
56
                Self::plain().await
57
            }
58
            Err(e) => Err(e),
59
        }?;
60
2
        Ok(service)
61
    }
62

            
63
    /// Create a new instance of the Service with plain algorithm.
64
8
    pub async fn plain() -> Result<Service<'a>, Error> {
65
6
        Self::with_algorithm(Algorithm::Plain).await
66
    }
67

            
68
    /// Create a new instance of the Service with encrypted algorithm.
69
8
    pub async fn encrypted() -> Result<Service<'a>, Error> {
70
6
        Self::with_algorithm(Algorithm::Encrypted).await
71
    }
72

            
73
    /// Create a new instance of the Service.
74
8
    async fn with_algorithm(algorithm: Algorithm) -> Result<Service<'a>, Error> {
75
12
        let cnx = zbus::connection::Builder::session()?
76
4
            .method_timeout(std::time::Duration::from_secs(30))
77
            .build()
78
8
            .await?;
79

            
80
4
        let service = Arc::new(api::Service::new(&cnx).await?);
81

            
82
4
        let (aes_key, session) = match algorithm {
83
            Algorithm::Plain => {
84
4
                #[cfg(feature = "tracing")]
85
                tracing::debug!("Starting an unencrypted Secret Service session");
86
6
                let (_service_key, session) = service.open_session(None).await?;
87
2
                (None, session)
88
            }
89
            Algorithm::Encrypted => {
90
4
                #[cfg(feature = "tracing")]
91
                tracing::debug!("Starting an encrypted Secret Service session");
92
4
                let private_key = Key::generate_private_key()?;
93
4
                let public_key = Key::generate_public_key(&private_key)?;
94
6
                let (service_key, session) = service.open_session(Some(public_key)).await?;
95
6
                let aes_key = service_key
96
6
                    .map(|service_key| Key::generate_aes_key(&private_key, &service_key))
97
                    .transpose()?
98
2
                    .map(Arc::new);
99

            
100
2
                (aes_key, session)
101
            }
102
        };
103

            
104
2
        Ok(Self {
105
2
            aes_key,
106
2
            inner: service,
107
2
            session: Arc::new(session),
108
2
            algorithm,
109
        })
110
    }
111

            
112
    /// Retrieve the default collection if any or create one.
113
    ///
114
    /// The created collection label is set to `Default`. If you want to
115
    /// translate the string, use [Self::with_alias_or_create] instead.
116
8
    pub async fn default_collection(&self) -> Result<Collection<'a>, Error> {
117
        // TODO: Figure how to make those labels translatable
118
6
        self.with_alias_or_create(Self::DEFAULT_COLLECTION, "Default", None)
119
8
            .await
120
    }
121

            
122
    /// Retrieve the session collection if any or create one.
123
    ///
124
    /// The created collection label is set to `Default`. If you want to
125
    /// translate the string, use [Self::with_alias_or_create] instead.
126
8
    pub async fn session_collection(&self) -> Result<Collection<'a>, Error> {
127
        // TODO: Figure how to make those labels translatable
128
6
        self.with_alias_or_create(Self::SESSION_COLLECTION, "Session", None)
129
8
            .await
130
    }
131

            
132
2
    pub async fn with_alias_or_create(
133
        &self,
134
        alias: &str,
135
        label: &str,
136
        window_id: Option<WindowIdentifier>,
137
    ) -> Result<Collection<'a>, Error> {
138
8
        match self.with_alias(alias).await {
139
2
            Ok(Some(collection)) => Ok(collection),
140
            Ok(None) => self.create_collection(label, Some(alias), window_id).await,
141
            Err(err) => Err(err),
142
        }
143
    }
144

            
145
    /// Find a collection with it alias.
146
    ///
147
    /// Applications should make use of [`Service::default_collection`] instead.
148
8
    pub async fn with_alias(&self, alias: &str) -> Result<Option<Collection<'a>>, Error> {
149
10
        Ok(self
150
            .inner
151
2
            .read_alias(alias)
152
8
            .await?
153
6
            .map(|collection| self.new_collection(collection)))
154
    }
155

            
156
    /// Get a list of all the available collections.
157
    pub async fn collections(&self) -> Result<Vec<Collection<'a>>, Error> {
158
        Ok(self
159
            .inner
160
            .collections()
161
            .await?
162
            .into_iter()
163
            .map(|collection| self.new_collection(collection))
164
            .collect::<Vec<_>>())
165
    }
166

            
167
    /// Create a new collection.
168
    pub async fn create_collection(
169
        &self,
170
        label: &str,
171
        alias: Option<&str>,
172
        window_id: Option<WindowIdentifier>,
173
    ) -> Result<Collection<'a>, Error> {
174
        self.inner
175
            .create_collection(label, alias, window_id)
176
            .await
177
            .map(|collection| self.new_collection(collection))
178
    }
179

            
180
    /// Find a collection with it label.
181
    pub async fn with_label(&self, label: &str) -> Result<Option<Collection<'a>>, Error> {
182
        let collections = self.collections().await?;
183
        for collection in collections {
184
            if collection.label().await? == label {
185
                return Ok(Some(collection));
186
            }
187
        }
188
        Ok(None)
189
    }
190

            
191
    /// Stream yielding when new collections get created
192
    pub async fn receive_collection_created(
193
        &self,
194
    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
195
        Ok(self
196
            .inner
197
            .receive_collection_created()
198
            .await?
199
            .map(|collection| self.new_collection(collection)))
200
    }
201

            
202
    /// Stream yielding when existing collections get changed
203
    pub async fn receive_collection_changed(
204
        &self,
205
    ) -> Result<impl Stream<Item = Collection<'a>> + '_, Error> {
206
        Ok(self
207
            .inner
208
            .receive_collection_changed()
209
            .await?
210
            .map(|collection| self.new_collection(collection)))
211
    }
212

            
213
    /// Stream yielding when existing collections get deleted
214
    pub async fn receive_collection_deleted(
215
        &self,
216
    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
217
        self.inner.receive_collection_deleted().await
218
    }
219

            
220
    // Get public `Collection` from `api::Collection`
221
2
    fn new_collection(&self, collection: api::Collection<'a>) -> Collection<'a> {
222
        Collection::new(
223
4
            Arc::clone(&self.inner),
224
4
            Arc::clone(&self.session),
225
2
            self.algorithm,
226
2
            collection,
227
2
            self.aes_key.clone(), // Cheap clone, it is an Arc,
228
        )
229
    }
230
}
231

            
232
impl Drop for Service<'_> {
233
2
    fn drop(&mut self) {
234
        // Only close the session if this is the last reference to it
235
2
        if Arc::strong_count(&self.session) == 1 {
236
2
            let session = Arc::clone(&self.session);
237
            #[cfg(feature = "tokio")]
238
            {
239
2
                tokio::spawn(async move {
240
                    let _ = session.close().await;
241
                });
242
            }
243
            #[cfg(feature = "async-std")]
244
            {
245
                blocking::unblock(move || {
246
                    futures_lite::future::block_on(async move {
247
                        let _ = session.close().await;
248
                    })
249
                })
250
                .detach();
251
            }
252
        }
253
    }
254
}
255

            
256
#[cfg(test)]
257
#[cfg(feature = "tokio")]
258
mod tests {
259
    use super::Service;
260

            
261
    #[tokio::test]
262
    #[ignore = "Requires prompting"]
263
    async fn create_collection() {
264
        let service = Service::new().await.unwrap();
265
        let collection = service
266
            .create_collection("somelabel", None, None)
267
            .await
268
            .unwrap();
269

            
270
        let found_collection = service.with_label("somelabel").await.unwrap();
271
        assert!(found_collection.is_some());
272

            
273
        assert_eq!(
274
            found_collection.unwrap().label().await.unwrap(),
275
            collection.label().await.unwrap()
276
        );
277

            
278
        collection.delete(None).await.unwrap();
279

            
280
        let found_collection = service.with_label("somelabel").await.unwrap();
281
        assert!(found_collection.is_none());
282
    }
283

            
284
    #[tokio::test]
285
    async fn default_collections() {
286
        let service = Service::new().await.unwrap();
287

            
288
        assert!(service.default_collection().await.is_ok());
289
        assert!(service.session_collection().await.is_ok());
290
    }
291

            
292
    #[tokio::test]
293
    async fn encrypted_session() {
294
        let service = Service::encrypted().await.unwrap();
295
        assert!(service.default_collection().await.is_ok());
296
    }
297

            
298
    #[tokio::test]
299
    async fn plain_session() {
300
        let service = Service::plain().await.unwrap();
301
        assert!(service.default_collection().await.is_ok());
302
    }
303
}