diff --git a/Cargo.lock b/Cargo.lock index 63f11dfc9bb..e5c19b95251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ "base64", "bytesize", "cargo-credential", - "cargo-credential-gnome-secret", + "cargo-credential-libsecret", "cargo-credential-macos-keychain", "cargo-credential-wincred", "cargo-platform 0.1.4", @@ -355,7 +355,7 @@ dependencies = [ ] [[package]] -name = "cargo-credential-gnome-secret" +name = "cargo-credential-libsecret" version = "0.3.1" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 4d2d6f6c790..a9587ea2d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ base64 = "0.21.2" bytesize = "1.2" cargo = { path = "" } cargo-credential = { version = "0.3.0", path = "credential/cargo-credential" } -cargo-credential-gnome-secret = { version = "0.3.1", path = "credential/cargo-credential-gnome-secret" } +cargo-credential-libsecret = { version = "0.3.1", path = "credential/cargo-credential-libsecret" } cargo-credential-wincred = { version = "0.3.0", path = "credential/cargo-credential-wincred" } cargo-credential-macos-keychain = { version = "0.3.0", path = "credential/cargo-credential-macos-keychain" } cargo-platform = { path = "crates/cargo-platform", version = "0.1.4" } @@ -124,7 +124,7 @@ base64.workspace = true bytesize.workspace = true cargo-platform.workspace = true cargo-credential.workspace = true -cargo-credential-gnome-secret.workspace = true +cargo-credential-libsecret.workspace = true cargo-credential-macos-keychain.workspace = true cargo-credential-wincred.workspace = true cargo-util.workspace = true diff --git a/crates/xtask-bump-check/src/xtask.rs b/crates/xtask-bump-check/src/xtask.rs index 1a3a1a77c95..f891523315d 100644 --- a/crates/xtask-bump-check/src/xtask.rs +++ b/crates/xtask-bump-check/src/xtask.rs @@ -153,7 +153,7 @@ fn bump_check(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> Carg "--exclude", "cargo-credential-1password", "--exclude", - "cargo-credential-gnome-secret", + "cargo-credential-libsecret", "--exclude", "cargo-credential-macos-keychain", "--exclude", diff --git a/credential/cargo-credential-gnome-secret/src/lib.rs b/credential/cargo-credential-gnome-secret/src/lib.rs deleted file mode 100644 index 473e3191daf..00000000000 --- a/credential/cargo-credential-gnome-secret/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(target_os = "linux")] -mod libsecret; -#[cfg(not(target_os = "linux"))] -pub use cargo_credential::UnsupportedCredential as GnomeSecret; -#[cfg(target_os = "linux")] -pub use libsecret::GnomeSecret; diff --git a/credential/cargo-credential-gnome-secret/src/libsecret.rs b/credential/cargo-credential-gnome-secret/src/libsecret.rs deleted file mode 100644 index 44fc4dea4f5..00000000000 --- a/credential/cargo-credential-gnome-secret/src/libsecret.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! 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 GnomeSecret; - -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 GnomeSecret { - fn perform( - &self, - registry: &RegistryInfo, - action: &Action, - _args: &[&str], - ) -> Result { - // Dynamically load libsecret to avoid users needing to install - // additional -dev packages when building this provider. - let lib; - let secret_password_lookup_sync: Symbol; - let secret_password_store_sync: Symbol; - let secret_password_clear_sync: Symbol; - 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), - } - } -} diff --git a/credential/cargo-credential-gnome-secret/src/main.rs b/credential/cargo-credential-gnome-secret/src/main.rs deleted file mode 100644 index 9ba535dcd07..00000000000 --- a/credential/cargo-credential-gnome-secret/src/main.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Cargo registry gnome libsecret credential process. - -fn main() { - cargo_credential::main(GnomeSecret); -} diff --git a/credential/cargo-credential-gnome-secret/Cargo.toml b/credential/cargo-credential-libsecret/Cargo.toml similarity index 88% rename from credential/cargo-credential-gnome-secret/Cargo.toml rename to credential/cargo-credential-libsecret/Cargo.toml index e88790c542b..1bd4bb7d063 100644 --- a/credential/cargo-credential-gnome-secret/Cargo.toml +++ b/credential/cargo-credential-libsecret/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo-credential-gnome-secret" +name = "cargo-credential-libsecret" version = "0.3.1" edition.workspace = true license.workspace = true @@ -10,4 +10,3 @@ description = "A Cargo credential process that stores tokens with GNOME libsecre anyhow.workspace = true cargo-credential.workspace = true libloading.workspace = true - diff --git a/credential/cargo-credential-gnome-secret/README.md b/credential/cargo-credential-libsecret/README.md similarity index 90% rename from credential/cargo-credential-gnome-secret/README.md rename to credential/cargo-credential-libsecret/README.md index 7a4b0283856..f169323e061 100644 --- a/credential/cargo-credential-gnome-secret/README.md +++ b/credential/cargo-credential-libsecret/README.md @@ -1,4 +1,4 @@ -# cargo-credential-gnome-secret +# 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. diff --git a/credential/cargo-credential-libsecret/src/lib.rs b/credential/cargo-credential-libsecret/src/lib.rs new file mode 100644 index 00000000000..f83b424ee51 --- /dev/null +++ b/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 { + // Dynamically load libsecret to avoid users needing to install + // additional -dev packages when building this provider. + let lib; + let secret_password_lookup_sync: Symbol; + let secret_password_store_sync: Symbol; + let secret_password_clear_sync: Symbol; + 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; diff --git a/src/cargo/util/auth/mod.rs b/src/cargo/util/auth/mod.rs index 8ff32f52dea..7354b8cab6c 100644 --- a/src/cargo/util/auth/mod.rs +++ b/src/cargo/util/auth/mod.rs @@ -451,7 +451,7 @@ fn credential_action( "cargo:token-from-stdout" => Box::new(BasicProcessCredential {}), "cargo:wincred" => Box::new(cargo_credential_wincred::WindowsCredential {}), "cargo:macos-keychain" => Box::new(cargo_credential_macos_keychain::MacKeychain {}), - "cargo:libsecret" => Box::new(cargo_credential_gnome_secret::GnomeSecret {}), + "cargo:libsecret" => Box::new(cargo_credential_libsecret::LibSecretCredential {}), process => Box::new(CredentialProcessCredential::new(process)), }; config.shell().verbose(|c| {