1
// org.freedesktop.Secret.Session
2

            
3
use std::{
4
    sync::Arc,
5
    time::{Duration, Instant},
6
};
7

            
8
use oo7::{Key, dbus::ServiceError};
9
use tokio::sync::Mutex;
10
use zbus::{
11
    interface,
12
    names::UniqueName,
13
    zvariant::{ObjectPath, OwnedObjectPath},
14
};
15

            
16
use crate::Service;
17

            
18
const SESSION_STALE_TIMEOUT: Duration = Duration::from_secs(60);
19

            
20
#[derive(Clone)]
21
pub struct Session {
22
    aes_key: Option<Arc<Key>>,
23
    service: Service,
24
    path: OwnedObjectPath,
25
    sender: UniqueName<'static>,
26
    peer_name: Option<String>,
27
    disconnected_at: Arc<Mutex<Option<Instant>>>,
28
}
29

            
30
#[interface(name = "org.freedesktop.Secret.Session")]
31
impl Session {
32
24
    pub async fn close(&self) -> Result<(), ServiceError> {
33
15
        tracing::info!("Closing session {} for {}.", self.path, self.sender);
34
12
        self.service.remove_session(&self.path).await;
35
24
        self.service
36
            .object_server()
37
6
            .remove::<Self, _>(&self.path)
38
18
            .await?;
39

            
40
6
        Ok(())
41
    }
42
}
43

            
44
impl Session {
45
32
    pub async fn new(
46
        aes_key: Option<Arc<Key>>,
47
        service: Service,
48
        sender: UniqueName<'static>,
49
        peer_name: Option<String>,
50
    ) -> Self {
51
65
        let index = service.session_index().await;
52
        Self {
53
34
            path: OwnedObjectPath::try_from(format!("/org/freedesktop/secrets/session/s{index}"))
54
                .unwrap(),
55
            aes_key,
56
            service,
57
            sender,
58
            peer_name,
59
62
            disconnected_at: Arc::new(Mutex::new(None)),
60
        }
61
    }
62

            
63
    pub fn sender(&self) -> &UniqueName<'static> {
64
        &self.sender
65
    }
66

            
67
19
    pub fn peer_name(&self) -> Option<&str> {
68
28
        self.peer_name.as_deref()
69
    }
70

            
71
25
    pub fn path(&self) -> &ObjectPath<'_> {
72
30
        &self.path
73
    }
74

            
75
15
    pub fn aes_key(&self) -> Option<Arc<Key>> {
76
14
        self.aes_key.as_ref().map(Arc::clone)
77
    }
78

            
79
    pub async fn mark_stale(&self) {
80
        *self.disconnected_at.lock().await = Some(Instant::now());
81
    }
82

            
83
60
    pub async fn unmark_stale(&self) {
84
29
        *self.disconnected_at.lock().await = None;
85
    }
86

            
87
    pub async fn is_stale(&self) -> bool {
88
        self.disconnected_at
89
            .lock()
90
            .await
91
            .is_some_and(|t| t.elapsed() > SESSION_STALE_TIMEOUT)
92
    }
93
}
94

            
95
#[cfg(test)]
96
mod tests {
97
    use crate::tests::TestServiceSetup;
98

            
99
    #[tokio::test]
100
    async fn close() -> Result<(), Box<dyn std::error::Error>> {
101
        let setup = TestServiceSetup::plain_session(true).await?;
102
        let path = setup.session.inner().path().to_owned();
103

            
104
        // Verify session exists on the server
105
        let session_check = setup.server.session(&path).await;
106
        assert!(
107
            session_check.is_some(),
108
            "Session should exist on server before close"
109
        );
110

            
111
        // Close the session
112
        setup.session.close().await?;
113

            
114
        // Verify session no longer exists on the server
115
        let session_check_after = setup.server.session(&path).await;
116
        assert!(
117
            session_check_after.is_none(),
118
            "Session should not exist on server after close"
119
        );
120

            
121
        Ok(())
122
    }
123
}