Skip to main content

keyring_search/
lib.rs

1/*!
2
3# Keyring
4
5This is a cross-platform library for searching the platform specific keystore.
6[Crates.io](https://crates.io/crates/keyring-search).
7Currently supported platforms are
8Linux,
9Windows,
10macOS, and iOS.
11
12## Design
13
14This crate, originally planned as a feature for
15[keyring](https://crates.io/crates/keyring) provides a broad search of
16the platform specific keystores based on user provided search parameters.
17 */
18
19use std::collections::HashMap;
20
21pub use error::{Error, Result};
22pub use search::{CredentialSearch, CredentialSearchResult, Limit};
23// Included keystore implementations and default choice thereof.
24
25pub mod mock;
26
27#[cfg(all(target_os = "linux", feature = "linux-keyutils"))]
28pub mod keyutils;
29#[cfg(all(
30    target_os = "linux",
31    feature = "secret-service",
32    not(feature = "linux-no-secret-service")
33))]
34pub mod secret_service;
35#[cfg(all(
36    target_os = "linux",
37    feature = "secret-service",
38    not(feature = "linux-default-keyutils")
39))]
40use crate::secret_service as default;
41#[cfg(all(
42    target_os = "linux",
43    feature = "linux-keyutils",
44    any(feature = "linux-default-keyutils", not(feature = "secret-service"))
45))]
46use keyutils as default;
47#[cfg(all(
48    target_os = "linux",
49    not(feature = "secret-service"),
50    not(feature = "linux-keyutils")
51))]
52use mock as default;
53
54#[cfg(all(target_os = "freebsd", feature = "secret-service"))]
55pub mod secret_service;
56#[cfg(all(target_os = "freebsd", feature = "secret-service"))]
57use crate::secret_service as default;
58#[cfg(all(target_os = "freebsd", not(feature = "secret-service")))]
59use mock as default;
60
61#[cfg(all(target_os = "openbsd", feature = "secret-service"))]
62pub mod secret_service;
63#[cfg(all(target_os = "openbsd", feature = "secret-service"))]
64use crate::secret_service as default;
65#[cfg(all(target_os = "openbsd", not(feature = "secret-service")))]
66use mock as default;
67
68#[cfg(all(target_os = "macos", feature = "platform-macos"))]
69pub mod macos;
70#[cfg(all(target_os = "macos", feature = "platform-macos"))]
71use macos as default;
72#[cfg(all(target_os = "macos", not(feature = "platform-macos")))]
73use mock as default;
74
75#[cfg(all(target_os = "windows", feature = "platform-windows"))]
76pub mod windows;
77#[cfg(all(target_os = "windows", not(feature = "platform-windows")))]
78use mock as default;
79#[cfg(all(target_os = "windows", feature = "platform-windows"))]
80use windows as default;
81
82#[cfg(all(target_os = "ios", feature = "platform-ios"))]
83pub mod ios;
84#[cfg(all(target_os = "ios", feature = "platform-ios"))]
85use ios as default;
86#[cfg(all(target_os = "ios", not(feature = "platform-ios")))]
87use mock as default;
88
89#[cfg(not(any(
90    target_os = "linux",
91    target_os = "freebsd",
92    target_os = "openbsd",
93    target_os = "macos",
94    target_os = "ios",
95    target_os = "windows",
96)))]
97use mock as default;
98
99pub mod error;
100pub mod search;
101
102pub fn set_default_credential_search(default_search: Box<CredentialSearch>) -> Result<Search> {
103    Ok(Search {
104        inner: default_search,
105    })
106}
107
108fn default_credential_search() -> Result<Search> {
109    let credentials = default::default_credential_search();
110    Ok(Search { inner: credentials })
111}
112
113pub struct Search {
114    inner: Box<CredentialSearch>,
115}
116/// The implementation of the Search structures methods.
117///
118/// The default search types are: Target, User, and Service.
119/// On linux-keyutils these all default to searching the 'session'
120/// keyring. If searching in a different keyring, utilize the
121/// platform specific `search_by_keyring` function
122impl Search {
123    /// Create a new instance of the Credential Search.
124    ///
125    /// The default credential search is used.
126    pub fn new() -> Result<Search> {
127        default_credential_search()
128    }
129    /// Specifies searching by target and the query string
130    ///
131    /// Can return:
132    /// [SearchError](Error::SearchError)
133    /// [NoResults](Error::NoResults)
134    /// [Unexpected](Error::Unexpected)
135    ///
136    /// # Example
137    ///     let search = keyring_search::Search::new().unwrap();
138    ///     let results = search.by_target("Foo.app");
139    pub fn by_target(&self, query: &str) -> CredentialSearchResult {
140        self.inner.by("target", query)
141    }
142    /// Specifies searching by user and the query string
143    ///
144    /// Can return:
145    /// [SearchError](Error::SearchError)
146    /// [NoResults](Error::NoResults)
147    /// [Unexpected](Error::Unexpected)
148    ///
149    /// # Example
150    ///     let search = keyring_search::Search::new().unwrap();
151    ///     let results = search.by_user("Mr. Foo Bar");
152    pub fn by_user(&self, query: &str) -> CredentialSearchResult {
153        self.inner.by("user", query)
154    }
155    /// Specifies searching by service and the query string
156    ///
157    /// Can return:
158    /// [SearchError](Error::SearchError)
159    /// [NoResults](Error::NoResults)
160    /// [Unexpected](Error::Unexpected)
161    ///
162    /// # Example
163    ///     let search = keyring_search::Search::new().unwrap();
164    ///     let results = search.by_service("Bar inc.");
165    pub fn by_service(&self, query: &str) -> CredentialSearchResult {
166        self.inner.by("service", query)
167    }
168}
169
170pub struct List {}
171
172/// Implementation of methods for the `List` structure.
173///
174/// `list_all`, lists all returned credentials
175/// `list_max`, lists a specified max amount of
176/// credentials. These are specified by calling [list_credentials](List::list_credentials).
177///
178/// Linux-keyutils search feature is limited to one result,
179/// no matter the `Limit`, one result will be returned.
180impl List {
181    /// List the credentials with given search result
182    ///
183    /// Takes CredentialSearchResult type and converts to a string
184    /// for printing. Matches the Limit type passed to constrain
185    /// the amount of results added to the string
186    pub fn list_credentials(search_result: &CredentialSearchResult, limit: Limit) -> String {
187        match limit {
188            Limit::All => Self::list_all(search_result),
189            Limit::Max(max) => Self::list_max(search_result, max),
190        }
191    }
192    /// List all credential search results.
193    ///
194    /// Is the result of passing the Limit::All type
195    /// to list_credentials.
196    fn list_all(result: &CredentialSearchResult) -> String {
197        let mut output = String::new();
198        match result {
199            Ok(search_result) => {
200                let mut entries: Vec<(String, HashMap<String, String>)> = search_result
201                    .iter()
202                    .map(|(k, v)| (k.clone(), v.clone()))
203                    .collect();
204                entries.sort_by_key(|(k, _)| k.parse::<i32>().unwrap_or(0));
205
206                for (outer_key, inner_map) in entries {
207                    output.push_str(&format!("{}\n", outer_key));
208                    let mut metadata: Vec<(String, String)> = inner_map
209                        .iter()
210                        .map(|(k, v)| (k.clone(), v.clone()))
211                        .collect();
212                    metadata.sort_by(|a, b| a.0.cmp(&b.0));
213                    for (key, value) in metadata {
214                        output.push_str(&format!("{}: {}\n", key, value));
215                    }
216                }
217                output
218            }
219            Err(err) => err.to_string(),
220        }
221    }
222    /// List a certain amount of credential search results.
223    ///
224    /// Is the result of passing the Limit::Max(i64) type
225    /// to list_credentials. The 64 bit integer represents
226    /// the total of the results passed.
227    fn list_max(result: &CredentialSearchResult, max: i64) -> String {
228        let mut output = String::new();
229        let mut count = 1;
230        match result {
231            Ok(search_result) => {
232                let mut entries: Vec<(String, HashMap<String, String>)> = search_result
233                    .iter()
234                    .map(|(k, v)| (k.clone(), v.clone()))
235                    .collect();
236                entries.sort_by_key(|(k, _)| k.parse::<i32>().unwrap_or(0));
237
238                for (outer_key, inner_map) in entries {
239                    output.push_str(&format!("{}\n", outer_key));
240                    let mut metadata: Vec<(String, String)> = inner_map
241                        .iter()
242                        .map(|(k, v)| (k.clone(), v.clone()))
243                        .collect();
244                    metadata.sort_by(|a, b| a.0.cmp(&b.0));
245                    for (key, value) in metadata {
246                        output.push_str(&format!("{}: {}\n", key, value));
247                    }
248                    count += 1;
249                    if count > max {
250                        break;
251                    }
252                }
253                println!("Search returned {} results\n", search_result.keys().len());
254                output
255            }
256            Err(err) => err.to_string(),
257        }
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    pub fn generate_random_string_of_len(len: usize) -> String {
264        // from the Rust Cookbook:
265        // https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html
266        use rand::{distributions::Alphanumeric, thread_rng, Rng};
267        thread_rng()
268            .sample_iter(&Alphanumeric)
269            .take(len)
270            .map(char::from)
271            .collect()
272    }
273
274    pub fn generate_random_string() -> String {
275        generate_random_string_of_len(30)
276    }
277}