Skip to main content

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}