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(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
53
    pub async fn delete(&self) -> Result<OwnedObjectPath, ServiceError> {
25
55
        let Some(collection) = self
26
            .service
27
28
            .collection_from_path(&self.collection_path)
28
39
            .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
37
        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
9
                self.service.clone(),
41
4
                crate::prompt::PromptRole::Unlock,
42
10
                collection.label().await,
43
10
                Some(collection.clone()),
44
            )
45
13
            .await;
46
8
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
47

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

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

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

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

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

            
70
20
            self.service
71
                .object_server()
72
5
                .at(&prompt_path, prompt)
73
15
                .await?;
74

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

            
81
5
            return Ok(prompt_path);
82
        }
83

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

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

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

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

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

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

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

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

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

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

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

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

            
175
            // Ensure content-type attribute is stored
176
16
            let mut attributes = inner.as_unlocked().attributes().clone();
177
16
            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
16
                    .entry(oo7::CONTENT_TYPE_ATTRIBUTE.to_string())
185
24
                    .and_modify(|v| *v = content_type.as_str().into());
186
            }
187
16
            inner.as_mut_unlocked().set_attributes(&attributes);
188
        }
189

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

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

            
202
8
        Ok(())
203
    }
204

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

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

            
219
76
        Ok(self
220
            .inner
221
15
            .lock()
222
45
            .await
223
15
            .as_ref()
224
15
            .unwrap()
225
15
            .as_unlocked()
226
15
            .attributes()
227
15
            .iter()
228
47
            .map(|(k, v)| (k.to_owned(), v.to_string()))
229
31
            .collect())
230
    }
231

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

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

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

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

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

            
277
74
        Ok(self
278
            .inner
279
16
            .lock()
280
46
            .await
281
15
            .as_ref()
282
15
            .unwrap()
283
15
            .as_unlocked()
284
14
            .label()
285
27
            .to_owned())
286
    }
287

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

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

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

            
314
10
        Ok(())
315
    }
316

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

            
326
91
        Ok(self
327
            .inner
328
13
            .lock()
329
44
            .await
330
15
            .as_ref()
331
16
            .unwrap()
332
16
            .as_unlocked()
333
15
            .created()
334
31
            .as_secs())
335
    }
336

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

            
346
89
        Ok(self
347
            .inner
348
14
            .lock()
349
43
            .await
350
15
            .as_ref()
351
15
            .unwrap()
352
15
            .as_unlocked()
353
15
            .modified()
354
32
            .as_secs())
355
    }
356
}
357

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

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

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

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

            
403
8
        drop(inner_guard);
404

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

            
408
8
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
409
16
        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
8
        Ok(())
418
    }
419

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

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

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

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

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

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