1
// org.freedesktop.Secret.Item
2

            
3
use std::{collections::HashMap, sync::Arc};
4

            
5
use oo7::dbus::{ServiceError, api::DBusSecretInner};
6
use tokio::sync::Mutex;
7
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
8

            
9
use crate::{Service, collection::Collection, error::custom_service_error};
10

            
11
#[derive(Debug, Clone)]
12
pub struct Item {
13
    // Properties
14
    pub(super) inner: Arc<Mutex<Option<oo7::file::Item>>>,
15
    // Other attributes
16
    service: Service,
17
    collection_path: OwnedObjectPath,
18
    path: OwnedObjectPath,
19
}
20

            
21
#[zbus::interface(name = "org.freedesktop.Secret.Item")]
22
impl Item {
23
    #[zbus(out_args("Prompt"))]
24
8
    pub async fn delete(&self) -> Result<OwnedObjectPath, ServiceError> {
25
8
        let Some(collection) = self
26
            .service
27
4
            .collection_from_path(&self.collection_path)
28
6
            .await
29
        else {
30
            return Err(ServiceError::NoSuchObject(format!(
31
                "Collection `{}` does not exist.",
32
                &self.collection_path
33
            )));
34
        };
35

            
36
        // Check if item or collection is locked
37
6
        if self.is_locked().await || collection.is_locked().await {
38
            // Create a prompt to unlock and delete the item
39
            let prompt = crate::prompt::Prompt::new(
40
4
                self.service.clone(),
41
2
                crate::prompt::PromptRole::Unlock,
42
4
                collection.label().await,
43
4
                Some(collection.clone()),
44
            )
45
6
            .await;
46
4
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
47

            
48
4
            let item_self = self.clone();
49
2
            let coll = collection.clone();
50
10
            let action =
51
                crate::prompt::PromptAction::new(move |unlock_secret: oo7::Secret| async move {
52
                    // Unlock the collection
53
4
                    coll.set_locked(false, Some(unlock_secret)).await?;
54

            
55
                    // Now delete the item
56
2
                    item_self.delete_unlocked(&coll).await?;
57

            
58
4
                    Ok(zbus::zvariant::Value::new(OwnedObjectPath::default())
59
2
                        .try_into_owned()
60
2
                        .unwrap())
61
                });
62

            
63
2
            prompt.set_action(action).await;
64

            
65
            // Register the prompt
66
6
            self.service
67
4
                .register_prompt(prompt_path.clone(), prompt.clone())
68
4
                .await;
69

            
70
8
            self.service
71
                .object_server()
72
2
                .at(&prompt_path, prompt)
73
6
                .await?;
74

            
75
4
            tracing::debug!(
76
                "Delete prompt created at `{}` for locked item `{}`",
77
                prompt_path,
78
                self.path
79
            );
80

            
81
2
            return Ok(prompt_path);
82
        }
83

            
84
        // Item and collection are unlocked, proceed directly
85
2
        self.delete_unlocked(&collection).await?;
86
2
        Ok(OwnedObjectPath::default())
87
    }
88

            
89
    #[zbus(out_args("secret"))]
90
2
    pub async fn get_secret(
91
        &self,
92
        session: OwnedObjectPath,
93
    ) -> Result<(DBusSecretInner,), ServiceError> {
94
4
        let Some(session) = self.service.session(&session).await else {
95
5
            tracing::error!("The session `{}` does not exist.", session);
96
4
            return Err(ServiceError::NoSession(format!(
97
                "The session `{session}` does not exist."
98
            )));
99
        };
100

            
101
4
        if self.is_locked().await {
102
5
            tracing::error!("Cannot get secret of a locked object `{}`", self.path);
103
4
            return Err(ServiceError::IsLocked(format!(
104
                "Cannot get secret of a locked object `{}`.",
105
                self.path
106
            )));
107
        }
108

            
109
4
        let inner = self.inner.lock().await;
110
4
        let inner = inner.as_ref().unwrap();
111
4
        let secret = inner.as_unlocked().secret();
112
4
        let content_type = secret.content_type();
113

            
114
4
        tracing::debug!("Secret retrieved from the item: {}.", self.path);
115

            
116
4
        match session.aes_key() {
117
2
            Some(key) => {
118
4
                let iv = oo7::crypto::generate_iv().map_err(|err| {
119
                    custom_service_error(&format!("Failed to generate iv {err}."))
120
                })?;
121
4
                let encrypted = oo7::crypto::encrypt(secret, &key, &iv).map_err(|err| {
122
                    custom_service_error(&format!("Failed to encrypt secret {err}."))
123
                })?;
124

            
125
2
                Ok((DBusSecretInner(
126
4
                    session.path().clone().into(),
127
2
                    iv,
128
2
                    encrypted,
129
                    content_type,
130
                ),))
131
            }
132
2
            None => Ok((DBusSecretInner(
133
2
                session.path().clone().into(),
134
2
                Vec::new(),
135
4
                secret.to_vec(),
136
                content_type,
137
            ),)),
138
        }
139
    }
140

            
141
10
    pub async fn set_secret(&self, secret: DBusSecretInner) -> Result<(), ServiceError> {
142
2
        let DBusSecretInner(session, iv, secret, content_type) = secret;
143

            
144
4
        let Some(session) = self.service.session(&session).await else {
145
5
            tracing::error!("The session `{}` does not exist.", session);
146
4
            return Err(ServiceError::NoSession(format!(
147
                "The session `{session}` does not exist."
148
            )));
149
        };
150

            
151
4
        if self.is_locked().await {
152
5
            tracing::error!("Cannot set secret of a locked object `{}`", self.path);
153
4
            return Err(ServiceError::IsLocked(format!(
154
                "Cannot set secret of a locked object `{}`.",
155
                self.path
156
            )));
157
        }
158

            
159
        {
160
4
            let mut inner = self.inner.lock().await;
161
4
            let inner = inner.as_mut().unwrap();
162

            
163
2
            match session.aes_key() {
164
2
                Some(key) => {
165
4
                    let decrypted = oo7::crypto::decrypt(secret, &key, &iv).map_err(|err| {
166
                        custom_service_error(&format!("Failed to decrypt secret {err}."))
167
                    })?;
168
4
                    inner.as_mut_unlocked().set_secret(decrypted);
169
                }
170
                None => {
171
2
                    inner.as_mut_unlocked().set_secret(secret);
172
                }
173
            }
174

            
175
            // Ensure content-type attribute is stored
176
4
            let mut attributes = inner.as_unlocked().attributes().clone();
177
4
            if !attributes.contains_key(oo7::CONTENT_TYPE_ATTRIBUTE) {
178
                attributes.insert(
179
                    oo7::CONTENT_TYPE_ATTRIBUTE.to_owned(),
180
                    content_type.as_str().into(),
181
                );
182
            } else {
183
                attributes
184
4
                    .entry(oo7::CONTENT_TYPE_ATTRIBUTE.to_string())
185
6
                    .and_modify(|v| *v = content_type.as_str().into());
186
            }
187
4
            inner.as_mut_unlocked().set_attributes(&attributes);
188
        }
189

            
190
2
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
191
4
        Collection::item_changed(&signal_emitter, &self.path).await?;
192

            
193
4
        if let Ok(signal_emitter) = self.service.signal_emitter(&self.path) {
194
4
            if let Err(err) = self.modified_changed(&signal_emitter).await {
195
                tracing::error!(
196
                    "Failed to emit PropertiesChanged signal for Modified: {}",
197
                    err
198
                );
199
            }
200
        }
201

            
202
2
        Ok(())
203
    }
204

            
205
    #[zbus(property, name = "Locked")]
206
8
    pub async fn is_locked(&self) -> bool {
207
4
        self.inner.lock().await.as_ref().unwrap().is_locked()
208
    }
209

            
210
    #[zbus(property, name = "Attributes")]
211
8
    pub async fn attributes(&self) -> zbus::fdo::Result<HashMap<String, String>> {
212
4
        if self.is_locked().await {
213
4
            return Err(zbus::fdo::Error::Failed(format!(
214
                "Cannot get attributes of a locked object `{}`.",
215
                self.path
216
            )));
217
        }
218

            
219
10
        Ok(self
220
            .inner
221
2
            .lock()
222
6
            .await
223
2
            .as_ref()
224
2
            .unwrap()
225
2
            .as_unlocked()
226
2
            .attributes()
227
2
            .iter()
228
6
            .map(|(k, v)| (k.to_owned(), v.to_string()))
229
4
            .collect())
230
    }
231

            
232
    #[zbus(property, name = "Attributes")]
233
2
    pub async fn set_attributes(
234
        &self,
235
        attributes: HashMap<String, String>,
236
    ) -> Result<(), zbus::Error> {
237
4
        if self.is_locked().await {
238
5
            tracing::error!("Cannot set attributes of a locked object `{}`", self.path);
239
2
            return Err(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(
240
4
                format!("Cannot set attributes of a locked object `{}`.", self.path),
241
            ))));
242
        }
243

            
244
        {
245
4
            let mut inner = self.inner.lock().await;
246
2
            inner
247
                .as_mut()
248
                .unwrap()
249
                .as_mut_unlocked()
250
2
                .set_attributes(&attributes);
251
        }
252

            
253
4
        let signal_emitter = self
254
            .service
255
2
            .signal_emitter(&self.collection_path)
256
2
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
257
4
        Collection::item_changed(&signal_emitter, &self.path).await?;
258

            
259
4
        let signal_emitter = self
260
            .service
261
2
            .signal_emitter(&self.path)
262
2
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
263
4
        self.attributes_changed(&signal_emitter).await?;
264
2
        self.modified_changed(&signal_emitter).await?;
265
2
        Ok(())
266
    }
267

            
268
    #[zbus(property, name = "Label")]
269
8
    pub async fn label(&self) -> zbus::fdo::Result<String> {
270
4
        if self.is_locked().await {
271
4
            return Err(zbus::fdo::Error::Failed(format!(
272
                "Cannot get label of a locked object `{}`.",
273
                self.path
274
            )));
275
        }
276

            
277
10
        Ok(self
278
            .inner
279
2
            .lock()
280
6
            .await
281
2
            .as_ref()
282
2
            .unwrap()
283
2
            .as_unlocked()
284
2
            .label()
285
4
            .to_owned())
286
    }
287

            
288
    #[zbus(property, name = "Label")]
289
8
    pub async fn set_label(&self, label: &str) -> Result<(), zbus::Error> {
290
4
        if self.is_locked().await {
291
5
            tracing::error!("Cannot set label of a locked object `{}`", self.path);
292
2
            return Err(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(
293
4
                format!("Cannot set label of a locked object `{}`.", self.path),
294
            ))));
295
        }
296
        {
297
4
            let mut inner = self.inner.lock().await;
298
4
            inner.as_mut().unwrap().as_mut_unlocked().set_label(label);
299
        }
300

            
301
4
        let signal_emitter = self
302
            .service
303
2
            .signal_emitter(&self.collection_path)
304
2
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
305
4
        Collection::item_changed(&signal_emitter, &self.path).await?;
306

            
307
4
        let signal_emitter = self
308
            .service
309
2
            .signal_emitter(&self.path)
310
2
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
311
4
        self.label_changed(&signal_emitter).await?;
312
2
        self.modified_changed(&signal_emitter).await?;
313

            
314
2
        Ok(())
315
    }
316

            
317
    #[zbus(property, name = "Created")]
318
8
    pub async fn created_at(&self) -> zbus::fdo::Result<u64> {
319
4
        if self.is_locked().await {
320
4
            return Err(zbus::fdo::Error::Failed(format!(
321
                "Cannot get created timestamp of a locked object `{}`.",
322
                self.path
323
            )));
324
        }
325

            
326
12
        Ok(self
327
            .inner
328
2
            .lock()
329
6
            .await
330
2
            .as_ref()
331
2
            .unwrap()
332
2
            .as_unlocked()
333
2
            .created()
334
4
            .as_secs())
335
    }
336

            
337
    #[zbus(property, name = "Modified")]
338
8
    pub async fn modified_at(&self) -> zbus::fdo::Result<u64> {
339
4
        if self.is_locked().await {
340
4
            return Err(zbus::fdo::Error::Failed(format!(
341
                "Cannot get modified timestamp of a locked object `{}`.",
342
                self.path
343
            )));
344
        }
345

            
346
12
        Ok(self
347
            .inner
348
2
            .lock()
349
6
            .await
350
2
            .as_ref()
351
2
            .unwrap()
352
2
            .as_unlocked()
353
2
            .modified()
354
4
            .as_secs())
355
    }
356
}
357

            
358
impl Item {
359
2
    pub fn new(
360
        item: oo7::file::Item,
361
        service: Service,
362
        collection_path: OwnedObjectPath,
363
        path: OwnedObjectPath,
364
    ) -> Self {
365
        Self {
366
4
            inner: Arc::new(Mutex::new(Some(item))),
367
            path,
368
            collection_path,
369
            service,
370
        }
371
    }
372

            
373
2
    pub fn path(&self) -> &ObjectPath<'_> {
374
2
        &self.path
375
    }
376

            
377
2
    pub(crate) async fn set_locked(
378
        &self,
379
        locked: bool,
380
        keyring: &oo7::file::UnlockedKeyring,
381
    ) -> Result<(), ServiceError> {
382
4
        let mut inner_guard = self.inner.lock().await;
383

            
384
6
        if let Some(old_item) = inner_guard.take() {
385
4
            let new_item = match (old_item, locked) {
386
2
                (oo7::file::Item::Unlocked(unlocked), true) => {
387
4
                    let locked_item = keyring.lock_item(unlocked).await.map_err(|err| {
388
                        custom_service_error(&format!("Failed to lock item: {err}"))
389
                    })?;
390
2
                    oo7::file::Item::Locked(locked_item)
391
                }
392
2
                (oo7::file::Item::Locked(locked_item), false) => {
393
4
                    let unlocked = keyring.unlock_item(locked_item).await.map_err(|err| {
394
                        custom_service_error(&format!("Failed to unlock item: {err}"))
395
                    })?;
396
2
                    oo7::file::Item::Unlocked(unlocked)
397
                }
398
                (other, _) => other,
399
            };
400
2
            *inner_guard = Some(new_item);
401
        }
402

            
403
2
        drop(inner_guard);
404

            
405
2
        let signal_emitter = self.service.signal_emitter(&self.path)?;
406
4
        self.locked_changed(&signal_emitter).await?;
407

            
408
2
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
409
4
        Collection::item_changed(&signal_emitter, &self.path).await?;
410

            
411
4
        tracing::debug!(
412
            "Item: {} is {}.",
413
            self.path,
414
            if locked { "locked" } else { "unlocked" }
415
        );
416

            
417
2
        Ok(())
418
    }
419

            
420
8
    async fn delete_unlocked(&self, collection: &Collection) -> Result<(), ServiceError> {
421
        // Delete from keyring and collection's items list
422
4
        collection.delete_item(&self.path).await?;
423

            
424
        // Remove from object server
425
8
        self.service
426
            .object_server()
427
2
            .remove::<Item, _>(&self.path)
428
6
            .await?;
429

            
430
        // Emit ItemDeleted signal
431
2
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
432
4
        Collection::item_deleted(&signal_emitter, &self.path).await?;
433

            
434
4
        tracing::info!("Item `{}` deleted.", &self.path);
435

            
436
2
        Ok(())
437
    }
438
}
439

            
440
#[cfg(test)]
441
mod tests;