uv_keyring/credential.rs
1/*!
2
3# Platform-independent secure storage model
4
5This module defines a plug and play model for platform-specific credential stores.
6The model comprises two traits: [`CredentialBuilderApi`] for the underlying store
7and [`CredentialApi`] for the entries in the store. These traits must be implemented
8in a thread-safe way, a requirement captured in the [`CredentialBuilder`] and
9[`Credential`] types that wrap them.
10 */
11use std::any::Any;
12use std::collections::HashMap;
13
14use crate::Result;
15
16/// The API that [credentials](Credential) implement.
17#[async_trait::async_trait]
18pub trait CredentialApi {
19 /// Set the credential's password (a string).
20 ///
21 /// This will persist the password in the underlying store.
22 async fn set_password(&self, password: &str) -> Result<()> {
23 self.set_secret(password.as_bytes()).await
24 }
25
26 /// Set the credential's secret (a byte array).
27 ///
28 /// This will persist the secret in the underlying store.
29 async fn set_secret(&self, password: &[u8]) -> Result<()>;
30
31 /// Retrieve the password (a string) from the underlying credential.
32 ///
33 /// This has no effect on the underlying store. If there is no credential
34 /// for this entry, a [`NoEntry`](crate::Error::NoEntry) error is returned.
35 async fn get_password(&self) -> Result<String> {
36 let secret = self.get_secret().await?;
37 crate::error::decode_password(secret)
38 }
39
40 /// Retrieve a secret (a byte array) from the credential.
41 ///
42 /// This has no effect on the underlying store. If there is no credential
43 /// for this entry, a [NoEntry](crate::Error::NoEntry) error is returned.
44 async fn get_secret(&self) -> Result<Vec<u8>>;
45
46 /// Get the secure store attributes on this entry's credential.
47 ///
48 /// Each credential store may support reading and updating different
49 /// named attributes; see the documentation on each of the stores
50 /// for details. Note that the keyring itself uses some of these
51 /// attributes to map entries to their underlying credential; these
52 /// _controlled_ attributes are not available for reading or updating.
53 ///
54 /// We provide a default (no-op) implementation of this method
55 /// for backward compatibility with stores that don't implement it.
56 async fn get_attributes(&self) -> Result<HashMap<String, String>> {
57 // this should err in the same cases as get_secret, so first call that for effect
58 self.get_secret().await?;
59 // if we got this far, return success with no attributes
60 Ok(HashMap::new())
61 }
62
63 /// Update the secure store attributes on this entry's credential.
64 ///
65 /// Each credential store may support reading and updating different
66 /// named attributes; see the documentation on each of the stores
67 /// for details. The implementation will ignore any attribute names
68 /// that you supply that are not available for update. Because the
69 /// names used by the different stores tend to be distinct, you can
70 /// write cross-platform code that will work correctly on each platform.
71 ///
72 /// We provide a default no-op implementation of this method
73 /// for backward compatibility with stores that don't implement it.
74 async fn update_attributes(&self, _: &HashMap<&str, &str>) -> Result<()> {
75 // this should err in the same cases as get_secret, so first call that for effect
76 self.get_secret().await?;
77 // if we got this far, return success after setting no attributes
78 Ok(())
79 }
80
81 /// Delete the underlying credential, if there is one.
82 ///
83 /// This is not idempotent if the credential existed!
84 /// A second call to `delete_credential` will return
85 /// a [`NoEntry`](crate::Error::NoEntry) error.
86 async fn delete_credential(&self) -> Result<()>;
87
88 /// Return the underlying concrete object cast to [Any].
89 ///
90 /// This allows clients
91 /// to downcast the credential to its concrete type so they
92 /// can do platform-specific things with it (e.g.,
93 /// query its attributes in the underlying store).
94 fn as_any(&self) -> &dyn Any;
95
96 /// The `Debug` trait call for the object.
97 ///
98 /// This is used to implement the `Debug` trait on this type; it
99 /// allows generic code to provide debug printing as provided by
100 /// the underlying concrete object.
101 ///
102 /// We provide a (useless) default implementation for backward
103 /// compatibility with existing implementors who may have not
104 /// implemented the `Debug` trait for their credential objects
105 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 std::fmt::Debug::fmt(self.as_any(), f)
107 }
108}
109
110/// A thread-safe implementation of the [Credential API](CredentialApi).
111pub type Credential = dyn CredentialApi + Send + Sync;
112
113impl std::fmt::Debug for Credential {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 self.debug_fmt(f)
116 }
117}
118
119/// The API that [credential builders](CredentialBuilder) implement.
120pub(crate) trait CredentialBuilderApi {
121 /// Create a credential identified by the given target, service, and user.
122 ///
123 /// This typically has no effect on the content of the underlying store.
124 /// A credential need not be persisted until its password is set.
125 fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>>;
126
127 /// Return the underlying concrete object cast to [Any].
128 ///
129 /// Because credential builders need not have any internal structure,
130 /// this call is not so much for clients
131 /// as it is to allow automatic derivation of a Debug trait for builders.
132 fn as_any(&self) -> &dyn Any;
133}
134
135impl std::fmt::Debug for CredentialBuilder {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 self.as_any().fmt(f)
138 }
139}
140
141/// A thread-safe implementation of the [`CredentialBuilder` API](CredentialBuilderApi).
142pub(crate) type CredentialBuilder = dyn CredentialBuilderApi + Send + Sync;
143
144#[cfg(not(any(
145 all(target_os = "linux", feature = "secret-service"),
146 all(target_os = "freebsd", feature = "secret-service"),
147 all(target_os = "openbsd", feature = "secret-service"),
148 all(target_os = "macos", feature = "apple-native"),
149 all(target_os = "windows", feature = "windows-native"),
150)))]
151struct NopCredentialBuilder;
152
153#[cfg(not(any(
154 all(target_os = "linux", feature = "secret-service"),
155 all(target_os = "freebsd", feature = "secret-service"),
156 all(target_os = "openbsd", feature = "secret-service"),
157 all(target_os = "macos", feature = "apple-native"),
158 all(target_os = "windows", feature = "windows-native"),
159)))]
160impl CredentialBuilderApi for NopCredentialBuilder {
161 fn build(&self, _: Option<&str>, _: &str, _: &str) -> Result<Box<Credential>> {
162 Err(super::Error::NoDefaultCredentialBuilder)
163 }
164
165 fn as_any(&self) -> &dyn Any {
166 self
167 }
168}
169
170// Return a credential builder that always fails. This is the builder
171// used if none of the crate-supplied keystores were included in the build.
172#[cfg(not(any(
173 all(target_os = "linux", feature = "secret-service"),
174 all(target_os = "freebsd", feature = "secret-service"),
175 all(target_os = "openbsd", feature = "secret-service"),
176 all(target_os = "macos", feature = "apple-native"),
177 all(target_os = "windows", feature = "windows-native"),
178)))]
179pub(crate) fn nop_credential_builder() -> Box<CredentialBuilder> {
180 Box::new(NopCredentialBuilder)
181}