windows_native_keyring_store/
store.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use keyring_core::api::{CredentialPersistence, CredentialStoreApi};
6use keyring_core::attributes::parse_attributes;
7use keyring_core::{Entry, Error, Result};
8
9use crate::cred::Cred;
10use crate::utils::enumerate_credentials;
11
12/// The store for Windows native credentials
13#[derive(Debug, Clone)]
14pub struct Store {
15    pub id: String,
16    pub delimiters: [String; 3],
17    pub service_no_divider: bool,
18}
19
20impl Store {
21    /// Create the default store: prefix and suffix empty, divider '.'.
22    ///
23    /// This is the configuration that matches the config for this store
24    /// in earlier versions of keyring.
25    pub fn new() -> Result<Arc<Self>> {
26        Ok(Self::new_internal(
27            ["".to_string(), ".".to_string(), "".to_string()],
28            false,
29        ))
30    }
31
32    /// Create a custom-configured store.
33    ///
34    /// The delimiter config options are `prefix`, `divider`, and `suffix`. They
35    /// default to `keyring:`, `@`, and the empty string, respectively.
36    ///
37    /// If you want to be sure that key descriptions cannot be ambiguous, specify
38    /// the config option `service_no_divider` to `true`.
39    pub fn new_with_configuration(config: &HashMap<&str, &str>) -> Result<Arc<Self>> {
40        let config = parse_attributes(
41            &["prefix", "divider", "suffix", "*service_no_divider"],
42            Some(config),
43        )?;
44        let prefix = match config.get("prefix") {
45            Some(prefix) => prefix.to_string(),
46            None => "".to_string(),
47        };
48        let divider = match config.get("divider") {
49            Some(divider) => divider.to_string(),
50            None => ".".to_string(),
51        };
52        let suffix = match config.get("suffix") {
53            Some(suffix) => suffix.to_string(),
54            None => "".to_string(),
55        };
56        let service_no_divider = config
57            .get("service_no_divider")
58            .is_some_and(|s| s.eq("true"));
59        Ok(Self::new_internal(
60            [prefix, divider, suffix],
61            service_no_divider,
62        ))
63    }
64
65    fn new_internal(delimiters: [String; 3], service_no_divider: bool) -> Arc<Self> {
66        let now = SystemTime::now();
67        let elapsed = if now.lt(&UNIX_EPOCH) {
68            UNIX_EPOCH.duration_since(now).unwrap()
69        } else {
70            now.duration_since(UNIX_EPOCH).unwrap()
71        };
72        Arc::new(Store {
73            id: format!(
74                "Crate version {}, Instantiated at {}",
75                env!("CARGO_PKG_VERSION"),
76                elapsed.as_secs_f64()
77            ),
78            delimiters,
79            service_no_divider,
80        })
81    }
82}
83
84impl CredentialStoreApi for Store {
85    /// See the keyring-core API docs.
86    fn vendor(&self) -> String {
87        "Windows Credential Manager, https://crates.io/crates/windows-native-keyring-store"
88            .to_string()
89    }
90
91    /// See the keyring-core API docs.
92    fn id(&self) -> String {
93        self.id.clone()
94    }
95
96    /// See the keyring-core API docs.
97    ///
98    /// Building a credential does not create a key in the store.
99    /// It's setting a password that does that.
100    fn build(
101        &self,
102        service: &str,
103        user: &str,
104        modifiers: Option<&HashMap<&str, &str>>,
105    ) -> Result<Entry> {
106        let mods = parse_attributes(&["target", "persistence"], modifiers)?;
107        let target = mods.get("target").map(|s| s.as_str());
108        let persistence = mods
109            .get("persistence")
110            .map(|s| s.as_str())
111            .unwrap_or("Enterprise");
112        let cred = Cred::build_from_specifiers(
113            target,
114            &self.delimiters,
115            self.service_no_divider,
116            service,
117            user,
118            persistence.parse()?,
119        )?;
120        Ok(Entry::new_with_credential(Arc::new(cred)))
121    }
122
123    /// See the keyring-core API docs.
124    fn search(&self, spec: &HashMap<&str, &str>) -> Result<Vec<Entry>> {
125        let spec = parse_attributes(&["pattern"], Some(spec))?;
126        let expr = if let Some(val) = spec.get("pattern") {
127            if let Ok(pat) = regex::Regex::new(val) {
128                Some(pat)
129            } else {
130                return Err(Error::Invalid(
131                    val.to_string(),
132                    "is not a valid regular expression".to_string(),
133                ));
134            }
135        } else {
136            None
137        };
138        let creds = enumerate_credentials(expr, &self.delimiters)?;
139        Ok(creds
140            .into_iter()
141            .map(|c| Entry::new_with_credential(Arc::new(c)))
142            .collect())
143    }
144
145    /// See the keyring-core API docs.
146    fn as_any(&self) -> &dyn std::any::Any {
147        self
148    }
149
150    /// See the keyring-core API docs.
151    ///
152    /// Since this keystore keeps credentials in kernel memory, they vanish on reboot
153    fn persistence(&self) -> CredentialPersistence {
154        CredentialPersistence::UntilDelete
155    }
156
157    /// See the keychain-core API docs.
158    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        std::fmt::Debug::fmt(self, f)
160    }
161}