diff options
Diffstat (limited to 'src/tools/cargo/credential/cargo-credential-libsecret')
3 files changed, 254 insertions, 0 deletions
diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml b/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml new file mode 100644 index 000000000..1bd4bb7d0 --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cargo-credential-libsecret" +version = "0.3.1" +edition.workspace = true +license.workspace = true +repository = "https://github.com/rust-lang/cargo" +description = "A Cargo credential process that stores tokens with GNOME libsecret." + +[dependencies] +anyhow.workspace = true +cargo-credential.workspace = true +libloading.workspace = true diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/README.md b/src/tools/cargo/credential/cargo-credential-libsecret/README.md new file mode 100644 index 000000000..f169323e0 --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-libsecret/README.md @@ -0,0 +1,7 @@ +# cargo-credential-libsecret + +This is the implementation for the Cargo credential helper for [GNOME libsecret]. +See the [credential-process] documentation for how to use this. + +[GNOME libsecret]: https://wiki.gnome.org/Projects/Libsecret +[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/src/lib.rs b/src/tools/cargo/credential/cargo-credential-libsecret/src/lib.rs new file mode 100644 index 000000000..f83b424ee --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-libsecret/src/lib.rs @@ -0,0 +1,235 @@ +#[cfg(target_os = "linux")] +mod linux { + //! Implementation of the libsecret credential helper. + + use anyhow::Context; + use cargo_credential::{ + read_token, Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, + Secret, + }; + use libloading::{Library, Symbol}; + use std::ffi::{CStr, CString}; + use std::os::raw::{c_char, c_int}; + use std::ptr::{null, null_mut}; + + #[allow(non_camel_case_types)] + type gchar = c_char; + + #[allow(non_camel_case_types)] + type gboolean = c_int; + + type GQuark = u32; + + #[repr(C)] + struct GError { + domain: GQuark, + code: c_int, + message: *mut gchar, + } + + #[repr(C)] + struct GCancellable { + _private: [u8; 0], + } + + #[repr(C)] + struct SecretSchema { + name: *const gchar, + flags: SecretSchemaFlags, + attributes: [SecretSchemaAttribute; 32], + } + + #[repr(C)] + #[derive(Copy, Clone)] + struct SecretSchemaAttribute { + name: *const gchar, + attr_type: SecretSchemaAttributeType, + } + + #[repr(C)] + enum SecretSchemaFlags { + None = 0, + } + + #[repr(C)] + #[derive(Copy, Clone)] + enum SecretSchemaAttributeType { + String = 0, + } + + type SecretPasswordStoreSync = extern "C" fn( + schema: *const SecretSchema, + collection: *const gchar, + label: *const gchar, + password: *const gchar, + cancellable: *mut GCancellable, + error: *mut *mut GError, + ... + ) -> gboolean; + type SecretPasswordClearSync = extern "C" fn( + schema: *const SecretSchema, + cancellable: *mut GCancellable, + error: *mut *mut GError, + ... + ) -> gboolean; + type SecretPasswordLookupSync = extern "C" fn( + schema: *const SecretSchema, + cancellable: *mut GCancellable, + error: *mut *mut GError, + ... + ) -> *mut gchar; + + pub struct LibSecretCredential; + + fn label(index_url: &str) -> CString { + CString::new(format!("cargo-registry:{}", index_url)).unwrap() + } + + fn schema() -> SecretSchema { + let mut attributes = [SecretSchemaAttribute { + name: null(), + attr_type: SecretSchemaAttributeType::String, + }; 32]; + attributes[0] = SecretSchemaAttribute { + name: b"url\0".as_ptr() as *const gchar, + attr_type: SecretSchemaAttributeType::String, + }; + SecretSchema { + name: b"org.rust-lang.cargo.registry\0".as_ptr() as *const gchar, + flags: SecretSchemaFlags::None, + attributes, + } + } + + impl Credential for LibSecretCredential { + fn perform( + &self, + registry: &RegistryInfo, + action: &Action, + _args: &[&str], + ) -> Result<CredentialResponse, Error> { + // Dynamically load libsecret to avoid users needing to install + // additional -dev packages when building this provider. + let lib; + let secret_password_lookup_sync: Symbol<SecretPasswordLookupSync>; + let secret_password_store_sync: Symbol<SecretPasswordStoreSync>; + let secret_password_clear_sync: Symbol<SecretPasswordClearSync>; + unsafe { + lib = Library::new("libsecret-1.so").context( + "failed to load libsecret: try installing the `libsecret` \ + or `libsecret-1-0` package with the system package manager", + )?; + secret_password_lookup_sync = lib + .get(b"secret_password_lookup_sync\0") + .map_err(Box::new)?; + secret_password_store_sync = + lib.get(b"secret_password_store_sync\0").map_err(Box::new)?; + secret_password_clear_sync = + lib.get(b"secret_password_clear_sync\0").map_err(Box::new)?; + } + + let index_url_c = CString::new(registry.index_url).unwrap(); + match action { + cargo_credential::Action::Get(_) => { + let mut error: *mut GError = null_mut(); + let attr_url = CString::new("url").unwrap(); + let schema = schema(); + unsafe { + let token_c = secret_password_lookup_sync( + &schema, + null_mut(), + &mut error, + attr_url.as_ptr(), + index_url_c.as_ptr(), + null() as *const gchar, + ); + if !error.is_null() { + return Err(format!( + "failed to get token: {}", + CStr::from_ptr((*error).message) + .to_str() + .unwrap_or_default() + ) + .into()); + } + if token_c.is_null() { + return Err(Error::NotFound); + } + let token = Secret::from( + CStr::from_ptr(token_c) + .to_str() + .map_err(|e| format!("expected utf8 token: {}", e))? + .to_string(), + ); + Ok(CredentialResponse::Get { + token, + cache: CacheControl::Session, + operation_independent: true, + }) + } + } + cargo_credential::Action::Login(options) => { + let label = label(registry.name.unwrap_or(registry.index_url)); + let token = CString::new(read_token(options, registry)?.expose()).unwrap(); + let mut error: *mut GError = null_mut(); + let attr_url = CString::new("url").unwrap(); + let schema = schema(); + unsafe { + secret_password_store_sync( + &schema, + b"default\0".as_ptr() as *const gchar, + label.as_ptr(), + token.as_ptr(), + null_mut(), + &mut error, + attr_url.as_ptr(), + index_url_c.as_ptr(), + null() as *const gchar, + ); + if !error.is_null() { + return Err(format!( + "failed to store token: {}", + CStr::from_ptr((*error).message) + .to_str() + .unwrap_or_default() + ) + .into()); + } + } + Ok(CredentialResponse::Login) + } + cargo_credential::Action::Logout => { + let schema = schema(); + let mut error: *mut GError = null_mut(); + let attr_url = CString::new("url").unwrap(); + unsafe { + secret_password_clear_sync( + &schema, + null_mut(), + &mut error, + attr_url.as_ptr(), + index_url_c.as_ptr(), + null() as *const gchar, + ); + if !error.is_null() { + return Err(format!( + "failed to erase token: {}", + CStr::from_ptr((*error).message) + .to_str() + .unwrap_or_default() + ) + .into()); + } + } + Ok(CredentialResponse::Logout) + } + _ => Err(Error::OperationNotSupported), + } + } + } +} + +#[cfg(not(target_os = "linux"))] +pub use cargo_credential::UnsupportedCredential as LibSecretCredential; +#[cfg(target_os = "linux")] +pub use linux::LibSecretCredential; |