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 {
32
    inner: Arc<api::Service>,
33
    aes_key: Option<Arc<Key>>,
34
    session: Arc<api::Session>,
35
    algorithm: Algorithm,
36
}
37

            
38
impl Service {
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
    pub async fn new() -> Result<Self, Error> {
52
        let cnx = zbus::connection::Builder::session()?
53
            .method_timeout(std::time::Duration::from_secs(30))
54
            .build()
55
            .await?;
56
        Self::new_with_connection(&cnx).await
57
    }
58

            
59
    /// Create a new instance of the Service with a custom connection.
60
    ///
61
    /// An encrypted communication would be attempted first and would fall back
62
    /// to a plain one if that fails.
63
24
    pub async fn new_with_connection(cnx: &zbus::Connection) -> Result<Self, Error> {
64
33
        let service = match Self::encrypted_with_connection(cnx).await {
65
8
            Ok(service) => Ok(service),
66
            Err(Error::ZBus(zbus::Error::MethodError(..))) => {
67
                Self::plain_with_connection(cnx).await
68
            }
69
            Err(Error::Service(ServiceError::ZBus(zbus::Error::MethodError(..)))) => {
70
                Self::plain_with_connection(cnx).await
71
            }
72
            Err(e) => Err(e),
73
        }?;
74
7
        Ok(service)
75
    }
76

            
77
    /// Create a new instance of the Service with plain algorithm.
78
    pub async fn plain() -> Result<Self, Error> {
79
        let cnx = zbus::connection::Builder::session()?
80
            .method_timeout(std::time::Duration::from_secs(30))
81
            .build()
82
            .await?;
83
        Self::plain_with_connection(&cnx).await
84
    }
85

            
86
    /// Create a new instance of the Service with plain algorithm and a custom
87
    /// connection.
88
50
    pub async fn plain_with_connection(cnx: &zbus::Connection) -> Result<Self, Error> {
89
36
        Self::with_algorithm_and_connection(Algorithm::Plain, cnx).await
90
    }
91

            
92
    /// Create a new instance of the Service with encrypted algorithm.
93
    pub async fn encrypted() -> Result<Self, Error> {
94
        let cnx = zbus::connection::Builder::session()?
95
            .method_timeout(std::time::Duration::from_secs(30))
96
            .build()
97
            .await?;
98
        Self::encrypted_with_connection(&cnx).await
99
    }
100

            
101
    /// Create a new instance of the Service with encrypted algorithm and a
102
    /// custom connection.
103
48
    pub async fn encrypted_with_connection(cnx: &zbus::Connection) -> Result<Self, Error> {
104
34
        Self::with_algorithm_and_connection(Algorithm::Encrypted, cnx).await
105
    }
106

            
107
    /// Create a new instance of the Service with a specific algorithm and
108
    /// connection.
109
22
    async fn with_algorithm_and_connection(
110
        algorithm: Algorithm,
111
        cnx: &zbus::Connection,
112
    ) -> Result<Self, Error> {
113
38
        let service = Arc::new(api::Service::new(cnx).await?);
114

            
115
42
        let (aes_key, session) = match algorithm {
116
            Algorithm::Plain => {
117
26
                #[cfg(feature = "tracing")]
118
                tracing::debug!("Starting an unencrypted Secret Service session");
119
40
                let (_service_key, session) = service.open_session(None).await?;
120
13
                (None, session)
121
            }
122
            Algorithm::Encrypted => {
123
24
                #[cfg(feature = "tracing")]
124
                tracing::debug!("Starting an encrypted Secret Service session");
125
25
                let private_key = Key::generate_private_key()?;
126
25
                let public_key = Key::generate_public_key(&private_key)?;
127
37
                let (service_key, session) = service.open_session(Some(public_key)).await?;
128
37
                let aes_key = service_key
129
42
                    .map(|service_key| Key::generate_aes_key(&private_key, &service_key))
130
                    .transpose()?
131
12
                    .map(Arc::new);
132

            
133
12
                (aes_key, session)
134
            }
135
        };
136

            
137
22
        Ok(Self {
138
20
            aes_key,
139
23
            inner: service,
140
19
            session: Arc::new(session),
141
23
            algorithm,
142
        })
143
    }
144

            
145
    /// Retrieve the default collection if any or create one.
146
    ///
147
    /// The created collection label is set to `Default`. If you want to
148
    /// translate the string, use [Self::with_alias_or_create] instead.
149
80
    pub async fn default_collection(&self) -> Result<Collection, Error> {
150
        // TODO: Figure how to make those labels translatable
151
58
        self.with_alias_or_create(Self::DEFAULT_COLLECTION, "Default", None)
152
71
            .await
153
    }
154

            
155
    /// Retrieve the session collection if any or create one.
156
    ///
157
    /// The created collection label is set to `Default`. If you want to
158
    /// translate the string, use [Self::with_alias_or_create] instead.
159
16
    pub async fn session_collection(&self) -> Result<Collection, Error> {
160
        // TODO: Figure how to make those labels translatable
161
12
        self.with_alias_or_create(Self::SESSION_COLLECTION, "Session", None)
162
16
            .await
163
    }
164

            
165
21
    pub async fn with_alias_or_create(
166
        &self,
167
        alias: &str,
168
        label: &str,
169
        window_id: Option<WindowIdentifier>,
170
    ) -> Result<Collection, Error> {
171
75
        match self.with_alias(alias).await {
172
17
            Ok(Some(collection)) => Ok(collection),
173
            Ok(None) => self.create_collection(label, Some(alias), window_id).await,
174
            Err(err) => Err(err),
175
        }
176
    }
177

            
178
    /// Find a collection with it alias.
179
    ///
180
    /// Applications should make use of [`Service::default_collection`] instead.
181
83
    pub async fn with_alias(&self, alias: &str) -> Result<Option<Collection>, Error> {
182
91
        Ok(self
183
            .inner
184
20
            .read_alias(alias)
185
70
            .await?
186
51
            .map(|collection| self.new_collection(collection)))
187
    }
188

            
189
    /// Get a list of all the available collections.
190
16
    pub async fn collections(&self) -> Result<Vec<Collection>, Error> {
191
20
        Ok(self
192
            .inner
193
4
            .collections()
194
16
            .await?
195
4
            .into_iter()
196
12
            .map(|collection| self.new_collection(collection))
197
4
            .collect::<Vec<_>>())
198
    }
199

            
200
    /// Create a new collection.
201
6
    pub async fn create_collection(
202
        &self,
203
        label: &str,
204
        alias: Option<&str>,
205
        window_id: Option<WindowIdentifier>,
206
    ) -> Result<Collection, Error> {
207
24
        self.inner
208
6
            .create_collection(label, alias, window_id)
209
25
            .await
210
32
            .map(|collection| self.new_collection(collection))
211
    }
212

            
213
    /// Find a collection with it label.
214
10
    pub async fn with_label(&self, label: &str) -> Result<Option<Collection>, Error> {
215
6
        let collections = self.collections().await?;
216
8
        for collection in collections {
217
10
            if collection.label().await? == label {
218
2
                return Ok(Some(collection));
219
            }
220
        }
221
2
        Ok(None)
222
    }
223

            
224
    /// Stream yielding when new collections get created
225
2
    pub async fn receive_collection_created(
226
        &self,
227
    ) -> Result<impl Stream<Item = Collection> + '_, Error> {
228
12
        Ok(self
229
            .inner
230
2
            .receive_collection_created()
231
8
            .await?
232
8
            .map(|collection| self.new_collection(collection)))
233
    }
234

            
235
    /// Stream yielding when existing collections get changed
236
2
    pub async fn receive_collection_changed(
237
        &self,
238
    ) -> Result<impl Stream<Item = Collection> + '_, Error> {
239
12
        Ok(self
240
            .inner
241
2
            .receive_collection_changed()
242
8
            .await?
243
8
            .map(|collection| self.new_collection(collection)))
244
    }
245

            
246
    /// Stream yielding when existing collections get deleted
247
2
    pub async fn receive_collection_deleted(
248
        &self,
249
    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
250
6
        self.inner.receive_collection_deleted().await
251
    }
252

            
253
    // Get public `Collection` from `api::Collection`
254
22
    fn new_collection(&self, collection: api::Collection) -> Collection {
255
        Collection::new(
256
41
            Arc::clone(&self.inner),
257
41
            Arc::clone(&self.session),
258
18
            self.algorithm,
259
23
            collection,
260
18
            self.aes_key.clone(), // Cheap clone, it is an Arc,
261
        )
262
    }
263
}
264

            
265
impl Drop for Service {
266
16
    fn drop(&mut self) {
267
        // Only close the session if this is the last reference to it
268
14
        if Arc::strong_count(&self.session) == 1 {
269
11
            let session = Arc::clone(&self.session);
270
            #[cfg(feature = "tokio")]
271
            {
272
15
                tokio::spawn(async move {
273
6
                    let _ = session.close().await;
274
                });
275
            }
276
            #[cfg(feature = "async-std")]
277
            {
278
                blocking::unblock(move || {
279
                    futures_lite::future::block_on(async move {
280
                        let _ = session.close().await;
281
                    })
282
                })
283
                .detach();
284
            }
285
        }
286
    }
287
}