mongodb_atlas_cli/secrets/
keyring.rs

1use keyring::Entry;
2
3use super::{ApiKeys, Secret, SecretStore, SecretStoreError, ServiceAccount, UserAccount};
4use crate::{
5    config::AuthType,
6    secrets::encoding::{decode_password, encode_password},
7};
8
9const KEY_USER_ACCOUNT_ACCESS_TOKEN: &str = "access_token";
10const KEY_USER_ACCOUNT_REFRESH_TOKEN: &str = "refresh_token";
11const KEY_API_KEYS_PUBLIC_API_KEY: &str = "public_api_key";
12const KEY_API_KEYS_PRIVATE_API_KEY: &str = "private_api_key";
13const KEY_SERVICE_ACCOUNT_CLIENT_ID: &str = "client_id";
14const KEY_SERVICE_ACCOUNT_CLIENT_SECRET: &str = "client_secret";
15
16pub struct KeyringSecretStore {}
17
18impl KeyringSecretStore {
19    pub fn new() -> Option<Self> {
20        // Determine if the keyring is available by trying to access a dummy entry
21        if keyring_entry("default", "dummy").is_ok() {
22            Some(Self {})
23        } else {
24            None
25        }
26    }
27}
28
29fn build_service_name(profile_name: &str) -> String {
30    format!("atlascli_{}", profile_name)
31}
32
33fn keyring_entry(profile_name: &str, property_name: &str) -> Result<Entry, SecretStoreError> {
34    Entry::new(&build_service_name(profile_name), property_name).map_err(|e| {
35        SecretStoreError::InvalidKeyStoreFormat {
36            reason: e.to_string(),
37        }
38    })
39}
40
41fn get_keyring_value(
42    profile_name: &str,
43    property_name: &str,
44) -> Result<Option<String>, SecretStoreError> {
45    let entry = keyring_entry(profile_name, property_name)?;
46    match entry.get_password() {
47        Ok(value) => Ok(decode_password(value)?),
48        Err(e) => match e {
49            keyring::Error::NoEntry => Ok(None),
50            e => Err(SecretStoreError::InvalidKeyStoreFormat {
51                reason: e.to_string(),
52            }),
53        },
54    }
55}
56
57fn try_delete_entry(profile_name: &str, property_name: &str) {
58    if let Ok(entry) = keyring_entry(profile_name, property_name) {
59        _ = entry.delete_credential();
60    }
61}
62
63fn get_base_secret<S: Into<Secret>>(
64    profile_name: &str,
65    property_1: &str,
66    property_2: &str,
67    constructor: impl FnOnce(String, String) -> S,
68) -> Result<Option<Secret>, SecretStoreError> {
69    let Some(value_1) = get_keyring_value(profile_name, property_1)? else {
70        return Ok(None);
71    };
72    let Some(value_2) = get_keyring_value(profile_name, property_2)? else {
73        return Ok(None);
74    };
75    Ok(Some(constructor(value_1, value_2).into()))
76}
77
78fn set_keyring_value(
79    profile_name: &str,
80    property_name: &str,
81    value: &str,
82) -> Result<(), SecretStoreError> {
83    let entry = keyring_entry(profile_name, property_name)?;
84    entry
85        .set_password(encode_password(value).as_ref())
86        .map_err(|e| SecretStoreError::KeyStoreUnavailable {
87            reason: e.to_string(),
88        })
89}
90
91impl SecretStore for KeyringSecretStore {
92    fn get(
93        &self,
94        profile_name: &str,
95        auth_type: AuthType,
96    ) -> Result<Option<Secret>, SecretStoreError> {
97        Ok(match auth_type {
98            AuthType::UserAccount => get_base_secret(
99                profile_name,
100                KEY_USER_ACCOUNT_ACCESS_TOKEN,
101                KEY_USER_ACCOUNT_REFRESH_TOKEN,
102                UserAccount::new,
103            )?,
104            AuthType::ApiKeys => get_base_secret(
105                profile_name,
106                KEY_API_KEYS_PUBLIC_API_KEY,
107                KEY_API_KEYS_PRIVATE_API_KEY,
108                ApiKeys::new,
109            )?,
110            AuthType::ServiceAccount => get_base_secret(
111                profile_name,
112                KEY_SERVICE_ACCOUNT_CLIENT_ID,
113                KEY_SERVICE_ACCOUNT_CLIENT_SECRET,
114                ServiceAccount::new,
115            )?,
116        })
117    }
118
119    fn set(&mut self, profile_name: &str, secret: Secret) -> Result<(), SecretStoreError> {
120        match secret {
121            Secret::ApiKeys(api_keys) => {
122                set_keyring_value(
123                    profile_name,
124                    KEY_API_KEYS_PUBLIC_API_KEY,
125                    &api_keys.public_api_key,
126                )?;
127                set_keyring_value(
128                    profile_name,
129                    KEY_API_KEYS_PRIVATE_API_KEY,
130                    &api_keys.private_api_key,
131                )?;
132                Ok(())
133            }
134            Secret::ServiceAccount(service_account) => {
135                set_keyring_value(
136                    profile_name,
137                    KEY_SERVICE_ACCOUNT_CLIENT_ID,
138                    &service_account.client_id,
139                )?;
140                set_keyring_value(
141                    profile_name,
142                    KEY_SERVICE_ACCOUNT_CLIENT_SECRET,
143                    &service_account.client_secret,
144                )?;
145                Ok(())
146            }
147            Secret::UserAccount(user_account) => {
148                set_keyring_value(
149                    profile_name,
150                    KEY_USER_ACCOUNT_ACCESS_TOKEN,
151                    &user_account.access_token,
152                )?;
153                set_keyring_value(
154                    profile_name,
155                    KEY_USER_ACCOUNT_REFRESH_TOKEN,
156                    &user_account.refresh_token,
157                )?;
158                Ok(())
159            }
160        }
161    }
162
163    fn delete(&mut self, profile_name: &str) -> Result<(), SecretStoreError> {
164        try_delete_entry(profile_name, KEY_USER_ACCOUNT_ACCESS_TOKEN);
165        try_delete_entry(profile_name, KEY_USER_ACCOUNT_REFRESH_TOKEN);
166        try_delete_entry(profile_name, KEY_API_KEYS_PUBLIC_API_KEY);
167        try_delete_entry(profile_name, KEY_API_KEYS_PRIVATE_API_KEY);
168        try_delete_entry(profile_name, KEY_SERVICE_ACCOUNT_CLIENT_ID);
169        try_delete_entry(profile_name, KEY_SERVICE_ACCOUNT_CLIENT_SECRET);
170
171        Ok(())
172    }
173}