windows_native_keyring_store/
store.rs1use 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#[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 pub fn new() -> Result<Arc<Self>> {
37 Ok(Self::new_internal(
38 ["".to_string(), ".".to_string(), "".to_string()],
39 false,
40 ))
41 }
42
43 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 fn vendor(&self) -> String {
98 "Windows Credential Manager, https://crates.io/crates/windows-native-keyring-store"
99 .to_string()
100 }
101
102 fn id(&self) -> String {
104 self.id.clone()
105 }
106
107 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 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 fn as_any(&self) -> &dyn std::any::Any {
158 self
159 }
160
161 fn persistence(&self) -> CredentialPersistence {
165 CredentialPersistence::UntilDelete
166 }
167
168 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 std::fmt::Debug::fmt(self, f)
171 }
172}