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(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 pub fn new() -> Result<Arc<Self>> {
26 Ok(Self::new_internal(
27 ["".to_string(), ".".to_string(), "".to_string()],
28 false,
29 ))
30 }
31
32 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 fn vendor(&self) -> String {
87 "Windows Credential Manager, https://crates.io/crates/windows-native-keyring-store"
88 .to_string()
89 }
90
91 fn id(&self) -> String {
93 self.id.clone()
94 }
95
96 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 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 fn as_any(&self) -> &dyn std::any::Any {
147 self
148 }
149
150 fn persistence(&self) -> CredentialPersistence {
154 CredentialPersistence::UntilDelete
155 }
156
157 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 std::fmt::Debug::fmt(self, f)
160 }
161}