cargo_credential_libsecret/
lib.rs

1//! > This crate is maintained by the Cargo team, primarily for use by Cargo
2//! > and not intended for external use (except as a transitive dependency). This
3//! > crate may make major changes to its APIs or be deprecated without warning.
4
5#[cfg(target_os = "linux")]
6mod linux {
7    //! Implementation of the libsecret credential helper.
8
9    use anyhow::Context;
10    use cargo_credential::{
11        Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, Secret,
12        read_token,
13    };
14    use libloading::{Library, Symbol};
15    use std::ffi::{CStr, CString};
16    use std::os::raw::{c_char, c_int};
17    use std::ptr::{null, null_mut};
18
19    #[allow(non_camel_case_types)]
20    type gchar = c_char;
21
22    #[allow(non_camel_case_types)]
23    type gboolean = c_int;
24
25    #[allow(non_camel_case_types)]
26    type gint = c_int;
27
28    #[allow(non_camel_case_types)]
29    type gpointer = *mut ();
30
31    type GQuark = u32;
32
33    #[repr(C)]
34    struct GError {
35        domain: GQuark,
36        code: c_int,
37        message: *mut gchar,
38    }
39
40    #[repr(C)]
41    struct GCancellable {
42        _private: [u8; 0],
43    }
44
45    #[repr(C)]
46    struct SecretSchema {
47        name: *const gchar,
48        flags: SecretSchemaFlags,
49        attributes: [SecretSchemaAttribute; 32],
50        reserved: gint,
51        reserved1: gpointer,
52        reserved2: gpointer,
53        reserved3: gpointer,
54        reserved4: gpointer,
55        reserved5: gpointer,
56        reserved6: gpointer,
57        reserved7: gpointer,
58    }
59
60    #[repr(C)]
61    #[derive(Copy, Clone)]
62    struct SecretSchemaAttribute {
63        name: *const gchar,
64        attr_type: SecretSchemaAttributeType,
65    }
66
67    #[repr(C)]
68    enum SecretSchemaFlags {
69        None = 0,
70    }
71
72    #[repr(C)]
73    #[derive(Copy, Clone)]
74    enum SecretSchemaAttributeType {
75        String = 0,
76    }
77
78    type SecretPasswordStoreSync = extern "C" fn(
79        schema: *const SecretSchema,
80        collection: *const gchar,
81        label: *const gchar,
82        password: *const gchar,
83        cancellable: *mut GCancellable,
84        error: *mut *mut GError,
85        ...
86    ) -> gboolean;
87    type SecretPasswordClearSync = extern "C" fn(
88        schema: *const SecretSchema,
89        cancellable: *mut GCancellable,
90        error: *mut *mut GError,
91        ...
92    ) -> gboolean;
93    type SecretPasswordLookupSync = extern "C" fn(
94        schema: *const SecretSchema,
95        cancellable: *mut GCancellable,
96        error: *mut *mut GError,
97        ...
98    ) -> *mut gchar;
99
100    pub struct LibSecretCredential {
101        libsecret: Library,
102    }
103
104    fn label(index_url: &str) -> CString {
105        CString::new(format!("cargo-registry:{}", index_url)).unwrap()
106    }
107
108    fn schema() -> SecretSchema {
109        let mut attributes = [SecretSchemaAttribute {
110            name: null(),
111            attr_type: SecretSchemaAttributeType::String,
112        }; 32];
113        attributes[0] = SecretSchemaAttribute {
114            name: c"url".as_ptr() as *const gchar,
115            attr_type: SecretSchemaAttributeType::String,
116        };
117        SecretSchema {
118            name: c"org.rust-lang.cargo.registry".as_ptr() as *const gchar,
119            flags: SecretSchemaFlags::None,
120            attributes,
121            reserved: 0,
122            reserved1: null_mut(),
123            reserved2: null_mut(),
124            reserved3: null_mut(),
125            reserved4: null_mut(),
126            reserved5: null_mut(),
127            reserved6: null_mut(),
128            reserved7: null_mut(),
129        }
130    }
131
132    impl LibSecretCredential {
133        pub fn new() -> Result<LibSecretCredential, Error> {
134            // Dynamically load libsecret to avoid users needing to install
135            // additional -dev packages when building this provider.
136            let libsecret = unsafe { Library::new("libsecret-1.so.0") }.context(
137                "failed to load libsecret: try installing the `libsecret` \
138                or `libsecret-1-0` package with the system package manager",
139            )?;
140            Ok(Self { libsecret })
141        }
142    }
143
144    impl Credential for &LibSecretCredential {
145        fn perform(
146            &self,
147            registry: &RegistryInfo<'_>,
148            action: &Action<'_>,
149            _args: &[&str],
150        ) -> Result<CredentialResponse, Error> {
151            let secret_password_lookup_sync: Symbol<'_, SecretPasswordLookupSync>;
152            let secret_password_store_sync: Symbol<'_, SecretPasswordStoreSync>;
153            let secret_password_clear_sync: Symbol<'_, SecretPasswordClearSync>;
154            unsafe {
155                secret_password_lookup_sync = self
156                    .libsecret
157                    .get(b"secret_password_lookup_sync\0")
158                    .map_err(Box::new)?;
159                secret_password_store_sync = self
160                    .libsecret
161                    .get(b"secret_password_store_sync\0")
162                    .map_err(Box::new)?;
163                secret_password_clear_sync = self
164                    .libsecret
165                    .get(b"secret_password_clear_sync\0")
166                    .map_err(Box::new)?;
167            }
168
169            let index_url_c = CString::new(registry.index_url).unwrap();
170            let mut error: *mut GError = null_mut();
171            let attr_url = c"url".as_ptr() as *const gchar;
172            let schema = schema();
173            match action {
174                cargo_credential::Action::Get(_) => unsafe {
175                    let token_c = secret_password_lookup_sync(
176                        &schema,
177                        null_mut(),
178                        &mut error,
179                        attr_url,
180                        index_url_c.as_ptr(),
181                        null() as *const gchar,
182                    );
183                    if !error.is_null() {
184                        return Err(format!(
185                            "failed to get token: {}",
186                            CStr::from_ptr((*error).message)
187                                .to_str()
188                                .unwrap_or_default()
189                        )
190                        .into());
191                    }
192                    if token_c.is_null() {
193                        return Err(Error::NotFound);
194                    }
195                    let token = Secret::from(
196                        CStr::from_ptr(token_c)
197                            .to_str()
198                            .map_err(|e| format!("expected utf8 token: {}", e))?
199                            .to_string(),
200                    );
201                    Ok(CredentialResponse::Get {
202                        token,
203                        cache: CacheControl::Session,
204                        operation_independent: true,
205                    })
206                },
207                cargo_credential::Action::Login(options) => {
208                    let label = label(registry.name.unwrap_or(registry.index_url));
209                    let token = CString::new(read_token(options, registry)?.expose()).unwrap();
210                    unsafe {
211                        secret_password_store_sync(
212                            &schema,
213                            c"default".as_ptr() as *const gchar,
214                            label.as_ptr(),
215                            token.as_ptr(),
216                            null_mut(),
217                            &mut error,
218                            attr_url,
219                            index_url_c.as_ptr(),
220                            null() as *const gchar,
221                        );
222                        if !error.is_null() {
223                            return Err(format!(
224                                "failed to store token: {}",
225                                CStr::from_ptr((*error).message)
226                                    .to_str()
227                                    .unwrap_or_default()
228                            )
229                            .into());
230                        }
231                    }
232                    Ok(CredentialResponse::Login)
233                }
234                cargo_credential::Action::Logout => {
235                    unsafe {
236                        secret_password_clear_sync(
237                            &schema,
238                            null_mut(),
239                            &mut error,
240                            attr_url,
241                            index_url_c.as_ptr(),
242                            null() as *const gchar,
243                        );
244                        if !error.is_null() {
245                            return Err(format!(
246                                "failed to erase token: {}",
247                                CStr::from_ptr((*error).message)
248                                    .to_str()
249                                    .unwrap_or_default()
250                            )
251                            .into());
252                        }
253                    }
254                    Ok(CredentialResponse::Logout)
255                }
256                _ => Err(Error::OperationNotSupported),
257            }
258        }
259    }
260}
261
262#[cfg(not(target_os = "linux"))]
263pub use cargo_credential::UnsupportedCredential as LibSecretCredential;
264#[cfg(target_os = "linux")]
265pub use linux::LibSecretCredential;