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
13
    pub async fn delete(
25
        &self,
26
        #[zbus(header)] header: zbus::message::Header<'_>,
27
    ) -> Result<OwnedObjectPath, ServiceError> {
28
26
        let caller = if let Some(sender) = header.sender() {
29
            self.service.peer_display_name(sender).await
30
        } else {
31
26
            "unknown".to_string()
32
        };
33
53
        let Some(collection) = self
34
            .service
35
26
            .collection_from_path(&self.collection_path)
36
48
            .await
37
        else {
38
            return Err(ServiceError::NoSuchObject(format!(
39
                "Collection `{}` does not exist.",
40
                &self.collection_path
41
            )));
42
        };
43

            
44
        // Check if item or collection is locked
45
55
        if self.is_locked().await || collection.is_locked().await {
46
            // Create a prompt to unlock and delete the item
47
            let prompt = crate::prompt::Prompt::new(
48
8
                self.service.clone(),
49
5
                crate::prompt::PromptRole::Unlock,
50
8
                collection.label().await,
51
8
                Some(collection.clone()),
52
            )
53
12
            .await;
54
8
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
55

            
56
8
            let item_self = self.clone();
57
8
            let coll = collection.clone();
58
5
            let caller = caller.to_owned();
59
            let action = crate::prompt::PromptAction::new(
60
22
                move |unlock_secret: Option<oo7::Secret>| async move {
61
                    // Unlock the collection
62
9
                    coll.set_locked(false, unlock_secret).await?;
63

            
64
                    // Now delete the item
65
5
                    item_self.delete_unlocked(&coll, &caller).await?;
66

            
67
9
                    Ok(zbus::zvariant::Value::new(OwnedObjectPath::default())
68
4
                        .try_into_owned()
69
4
                        .unwrap())
70
                },
71
            );
72

            
73
4
            prompt.set_action(action).await;
74

            
75
            // Register the prompt
76
14
            self.service
77
10
                .register_prompt(prompt_path.clone(), prompt.clone())
78
9
                .await;
79

            
80
17
            self.service
81
                .object_server()
82
4
                .at(&prompt_path, prompt)
83
12
                .await?;
84

            
85
5
            tracing::debug!(
86
                "Delete prompt created at `{}` for locked item `{}`",
87
                prompt_path,
88
                self.path
89
            );
90

            
91
5
            return Ok(prompt_path);
92
        }
93

            
94
        // Item and collection are unlocked, proceed directly
95
22
        self.delete_unlocked(&collection, &caller).await?;
96
16
        Ok(OwnedObjectPath::default())
97
    }
98

            
99
    #[zbus(out_args("secret"))]
100
12
    pub async fn get_secret(
101
        &self,
102
        session: OwnedObjectPath,
103
    ) -> Result<(DBusSecretInner,), ServiceError> {
104
32
        let Some(session) = self.service.session(&session).await else {
105
8
            tracing::error!("The session `{}` does not exist.", session);
106
8
            return Err(ServiceError::NoSession(format!(
107
                "The session `{session}` does not exist."
108
            )));
109
        };
110

            
111
32
        let inner = self.inner.lock().await;
112
24
        let inner = inner.as_ref().unwrap();
113
12
        if inner.is_locked() {
114
12
            tracing::error!("Cannot get secret of a locked object `{}`", self.path);
115
12
            return Err(ServiceError::IsLocked(format!(
116
                "Cannot get secret of a locked object `{}`.",
117
                self.path
118
            )));
119
        }
120
24
        let secret = inner.as_unlocked().secret();
121
28
        let content_type = secret.content_type();
122

            
123
14
        tracing::debug!("Secret retrieved from the item: {}.", self.path);
124

            
125
27
        match session.aes_key() {
126
12
            Some(key) => {
127
24
                let iv = oo7::crypto::generate_iv().map_err(|err| {
128
                    custom_service_error(&format!("Failed to generate iv {err}."))
129
                })?;
130
24
                let encrypted = oo7::crypto::encrypt(secret, &key, &iv).map_err(|err| {
131
                    custom_service_error(&format!("Failed to encrypt secret {err}."))
132
                })?;
133

            
134
12
                Ok((DBusSecretInner(
135
24
                    session.path().clone().into(),
136
12
                    iv,
137
12
                    encrypted,
138
                    content_type,
139
                ),))
140
            }
141
8
            None => Ok((DBusSecretInner(
142
8
                session.path().clone().into(),
143
8
                Vec::new(),
144
16
                secret.to_vec(),
145
                content_type,
146
            ),)),
147
        }
148
    }
149

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

            
153
20
        let Some(session) = self.service.session(session).await else {
154
8
            tracing::error!("The session `{}` does not exist.", session);
155
8
            return Err(ServiceError::NoSession(format!(
156
                "The session `{session}` does not exist."
157
            )));
158
        };
159

            
160
        {
161
20
            let mut inner = self.inner.lock().await;
162
16
            let inner = inner.as_mut().unwrap();
163
8
            if inner.is_locked() {
164
12
                tracing::error!("Cannot set secret of a locked object `{}`", self.path);
165
12
                return Err(ServiceError::IsLocked(format!(
166
                    "Cannot set secret of a locked object `{}`.",
167
                    self.path
168
                )));
169
            }
170

            
171
16
            match session.aes_key() {
172
8
                Some(key) => {
173
16
                    let decrypted = oo7::crypto::decrypt(secret, &key, iv).map_err(|err| {
174
                        custom_service_error(&format!("Failed to decrypt secret {err}."))
175
                    })?;
176
16
                    inner.as_mut_unlocked().set_secret(decrypted);
177
                }
178
                None => {
179
6
                    inner.as_mut_unlocked().set_secret(secret);
180
                }
181
            }
182

            
183
            // Ensure content-type attribute is stored
184
16
            let mut attributes = inner.as_unlocked().attributes().clone();
185
16
            if !attributes.contains_key(oo7::CONTENT_TYPE_ATTRIBUTE) {
186
                attributes.insert(
187
                    oo7::CONTENT_TYPE_ATTRIBUTE.to_owned(),
188
                    content_type.as_str().into(),
189
                );
190
            } else {
191
                attributes
192
16
                    .entry(oo7::CONTENT_TYPE_ATTRIBUTE.to_string())
193
24
                    .and_modify(|v| *v = content_type.as_str().into());
194
            }
195
16
            inner.as_mut_unlocked().set_attributes(&attributes);
196
        }
197

            
198
8
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
199
20
        Collection::item_changed(&signal_emitter, &self.path).await?;
200

            
201
16
        if let Ok(signal_emitter) = self.service.signal_emitter(&self.path)
202
20
            && let Err(err) = self.modified_changed(&signal_emitter).await
203
        {
204
            tracing::error!(
205
                "Failed to emit PropertiesChanged signal for Modified: {}",
206
                err
207
            );
208
        }
209

            
210
16
        tracing::debug!("Secret updated for item: {}.", self.path);
211

            
212
8
        Ok(())
213
    }
214

            
215
    #[zbus(property, name = "Locked")]
216
54
    pub async fn is_locked(&self) -> bool {
217
38
        self.inner.lock().await.as_ref().unwrap().is_locked()
218
    }
219

            
220
    #[zbus(property, name = "Attributes")]
221
60
    pub async fn attributes(&self) -> zbus::fdo::Result<HashMap<String, String>> {
222
41
        let inner = self.inner.lock().await;
223
31
        let inner = inner.as_ref().unwrap();
224
15
        if inner.is_locked() {
225
12
            return Err(zbus::fdo::Error::Failed(format!(
226
                "Cannot get attributes of a locked object `{}`.",
227
                self.path
228
            )));
229
        }
230

            
231
15
        Ok(inner
232
15
            .as_unlocked()
233
15
            .attributes()
234
17
            .iter()
235
51
            .map(|(k, v)| (k.to_owned(), v.to_string()))
236
17
            .collect())
237
    }
238

            
239
    #[zbus(property, name = "Attributes")]
240
8
    pub async fn set_attributes(
241
        &self,
242
        attributes: HashMap<String, String>,
243
    ) -> Result<(), zbus::Error> {
244
        {
245
20
            let mut inner = self.inner.lock().await;
246
16
            let inner = inner.as_mut().unwrap();
247
8
            if inner.is_locked() {
248
12
                tracing::error!("Cannot set attributes of a locked object `{}`", self.path);
249
6
                return Err(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(
250
12
                    format!("Cannot set attributes of a locked object `{}`.", self.path),
251
                ))));
252
            }
253
16
            inner.as_mut_unlocked().set_attributes(&attributes);
254
        }
255

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

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

            
269
8
        tracing::debug!("Attributes updated for item `{}`.", self.path);
270
8
        Ok(())
271
    }
272

            
273
    #[zbus(property, name = "Label")]
274
58
    pub async fn label(&self) -> zbus::fdo::Result<String> {
275
38
        let inner = self.inner.lock().await;
276
28
        let inner = inner.as_ref().unwrap();
277
14
        if inner.is_locked() {
278
12
            return Err(zbus::fdo::Error::Failed(format!(
279
                "Cannot get label of a locked object `{}`.",
280
                self.path
281
            )));
282
        }
283

            
284
28
        Ok(inner.as_unlocked().label().to_owned())
285
    }
286

            
287
    #[zbus(property, name = "Label")]
288
40
    pub async fn set_label(&self, label: &str) -> Result<(), zbus::Error> {
289
        {
290
26
            let mut inner = self.inner.lock().await;
291
20
            let inner = inner.as_mut().unwrap();
292
10
            if inner.is_locked() {
293
12
                tracing::error!("Cannot set label of a locked object `{}`", self.path);
294
6
                return Err(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(
295
12
                    format!("Cannot set label of a locked object `{}`.", self.path),
296
                ))));
297
            }
298
20
            inner.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
26
        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
26
        self.label_changed(&signal_emitter).await?;
312
16
        self.modified_changed(&signal_emitter).await?;
313

            
314
10
        tracing::debug!("Label updated for item `{}`.", self.path);
315
10
        Ok(())
316
    }
317

            
318
    #[zbus(property, name = "Created")]
319
56
    pub async fn created_at(&self) -> zbus::fdo::Result<u64> {
320
37
        let inner = self.inner.lock().await;
321
28
        let inner = inner.as_ref().unwrap();
322
14
        if inner.is_locked() {
323
12
            return Err(zbus::fdo::Error::Failed(format!(
324
                "Cannot get created timestamp of a locked object `{}`.",
325
                self.path
326
            )));
327
        }
328
27
        Ok(inner.as_unlocked().created().as_secs())
329
    }
330

            
331
    #[zbus(property, name = "Modified")]
332
50
    pub async fn modified_at(&self) -> zbus::fdo::Result<u64> {
333
37
        let inner = self.inner.lock().await;
334
30
        let inner = inner.as_ref().unwrap();
335
15
        if inner.is_locked() {
336
12
            return Err(zbus::fdo::Error::Failed(format!(
337
                "Cannot get modified timestamp of a locked object `{}`.",
338
                self.path
339
            )));
340
        }
341
30
        Ok(inner.as_unlocked().modified().as_secs())
342
    }
343
}
344

            
345
impl Item {
346
14
    pub fn new(
347
        item: oo7::file::Item,
348
        service: Service,
349
        collection_path: OwnedObjectPath,
350
        path: OwnedObjectPath,
351
    ) -> Self {
352
        Self {
353
31
            inner: Arc::new(Mutex::new(Some(item))),
354
            path,
355
            collection_path,
356
            service,
357
        }
358
    }
359

            
360
16
    pub fn path(&self) -> &ObjectPath<'_> {
361
18
        &self.path
362
    }
363

            
364
8
    pub(crate) async fn set_locked(
365
        &self,
366
        locked: bool,
367
        keyring: &oo7::file::UnlockedKeyring,
368
    ) -> Result<(), ServiceError> {
369
20
        let mut inner_guard = self.inner.lock().await;
370

            
371
24
        if let Some(old_item) = inner_guard.take() {
372
16
            let new_item = match (old_item, locked) {
373
8
                (oo7::file::Item::Unlocked(unlocked), true) => {
374
16
                    let locked_item = keyring.lock_item(unlocked).await.map_err(|err| {
375
                        custom_service_error(&format!("Failed to lock item: {err}"))
376
                    })?;
377
8
                    oo7::file::Item::Locked(locked_item)
378
                }
379
8
                (oo7::file::Item::Locked(locked_item), false) => {
380
17
                    let unlocked = keyring.unlock_item(locked_item).await.map_err(|err| {
381
                        custom_service_error(&format!("Failed to unlock item: {err}"))
382
                    })?;
383
9
                    oo7::file::Item::Unlocked(unlocked)
384
                }
385
                (other, _) => other,
386
            };
387
8
            *inner_guard = Some(new_item);
388
        }
389

            
390
8
        drop(inner_guard);
391

            
392
9
        let signal_emitter = self.service.signal_emitter(&self.path)?;
393
23
        self.locked_changed(&signal_emitter).await?;
394

            
395
9
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
396
23
        Collection::item_changed(&signal_emitter, &self.path).await?;
397

            
398
4
        tracing::debug!(
399
            "Item: {} is {}.",
400
            self.path,
401
            if locked { "locked" } else { "unlocked" }
402
        );
403

            
404
9
        Ok(())
405
    }
406

            
407
13
    async fn delete_unlocked(
408
        &self,
409
        collection: &Collection,
410
        caller: &str,
411
    ) -> Result<(), ServiceError> {
412
        // Delete from keyring and collection's items list
413
34
        collection.delete_item(&self.path).await?;
414

            
415
        // Remove from object server
416
57
        self.service
417
            .object_server()
418
15
            .remove::<Item, _>(&self.path)
419
54
            .await?;
420

            
421
        // Emit ItemDeleted signal
422
15
        let signal_emitter = self.service.signal_emitter(&self.collection_path)?;
423
38
        Collection::item_deleted(&signal_emitter, &self.path).await?;
424

            
425
16
        tracing::info!("Item `{}` deleted by {caller}.", &self.path);
426

            
427
14
        Ok(())
428
    }
429
}
430

            
431
#[cfg(test)]
432
mod tests;