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