Skip to main content

veloren_voxygen_credentialstore/
linux.rs

1use super::{CredentialError, CredentialStatus, CredentialStore, Credentials};
2use secret_service::{EncryptionType, SecretService as Provider};
3use std::{collections::HashMap, sync::Arc};
4use tokio::sync::{Mutex, MutexGuard};
5
6const TITLE: &str = "Veloren";
7const APPLICATION: &str = "application";
8const AUTH_SERVER: &str = "auth_server";
9const USERNAME: &str = "username";
10const CONTENT_TYPE: &str = "text/plain";
11
12#[derive(Clone, Default)]
13pub struct SecretService {
14    provider: Arc<Mutex<Option<Provider<'static>>>>,
15}
16
17impl SecretService {
18    // Status, Load and Save are independent operations. Any of them can initiate a
19    // connection. It will be (re)created the next time this function is called. And
20    // any of them can drop it to prevent a potentially faulty connection.
21    async fn get_or_create_provider(&self) -> MutexGuard<'_, Option<Provider<'static>>> {
22        let mut provider = self.provider.lock().await;
23
24        if provider.is_none() {
25            // `None` is equivalent to `CredentialError::Connection`.
26            *provider = Provider::connect(EncryptionType::Dh).await.ok();
27        }
28
29        provider
30    }
31}
32
33impl CredentialStore for SecretService {
34    fn is_supported(&self) -> bool { true }
35
36    async fn status(&self) -> CredentialStatus {
37        let mut provider = self.get_or_create_provider().await;
38
39        if let Some(provider_ref) = provider.as_ref()
40            && let Ok(collection) = provider_ref.get_default_collection().await
41            && let Ok(locked) = collection.is_locked().await
42        {
43            if locked {
44                return CredentialStatus::Locked;
45            } else {
46                return CredentialStatus::Ready;
47            }
48        }
49
50        *provider = None;
51        CredentialStatus::Disabled
52    }
53
54    async fn load(&self, auth_server: String, username: String) -> Result<String, CredentialError> {
55        let mut provider = self.get_or_create_provider().await;
56        let mut error = CredentialError::Connection;
57        let attributes = HashMap::from([
58            (APPLICATION, TITLE),
59            (AUTH_SERVER, auth_server.as_str()),
60            (USERNAME, username.as_str()),
61        ]);
62
63        if let Some(provider_ref) = provider.as_ref()
64            && let Ok(search_items) = provider_ref
65                .search_items(attributes)
66                .await
67                .map_err(|_| error = CredentialError::Search)
68            && let Some(item) = match search_items.unlocked.first() {
69                Some(item) => Some(item),
70                None => match search_items.locked.first() {
71                    Some(item) => {
72                        if item.unlock().await.is_ok() {
73                            Some(item)
74                        } else {
75                            error = CredentialError::Unlock;
76                            None
77                        }
78                    },
79                    None => {
80                        // We don't need to drop provider here.
81                        return Err(CredentialError::Empty);
82                    },
83                },
84            }
85            && let Ok(secret) = item
86                .get_secret()
87                .await
88                .map_err(|_| error = CredentialError::Secret)
89            && let Ok(password) =
90                String::from_utf8(secret).map_err(|_| error = CredentialError::Utf8)
91        {
92            return Ok(password);
93        }
94
95        *provider = None;
96        Err(error)
97    }
98
99    async fn save(&self, credentials: Credentials) -> Result<(), CredentialError> {
100        let mut provider = self.get_or_create_provider().await;
101        let mut error = CredentialError::Connection;
102        let attributes = HashMap::from([
103            (APPLICATION, TITLE),
104            (AUTH_SERVER, credentials.auth_server.as_str()),
105            (USERNAME, credentials.username.as_str()),
106        ]);
107        let formatted_title = format!("{} ({})", TITLE, credentials.username);
108
109        if let Some(provider_ref) = provider.as_ref()
110            && let Ok(collection) = provider_ref
111                .get_default_collection()
112                .await
113                .map_err(|_| error = CredentialError::Collection)
114            && let Ok(_) = collection
115                .create_item(
116                    &formatted_title,
117                    attributes,
118                    credentials.password.as_bytes(),
119                    true,
120                    CONTENT_TYPE,
121                )
122                .await
123                .map_err(|_| error = CredentialError::Save)
124        {
125            return Ok(());
126        }
127
128        *provider = None;
129        Err(error)
130    }
131}