Skip to content

Make cargo-credential-gnome-secret built-in as cargo:libsecret #12521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 17, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ base64 = "0.21.2"
bytesize = "1.2"
cargo = { path = "" }
cargo-credential = { version = "0.3.0", path = "credential/cargo-credential" }
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" }
@@ -123,6 +124,7 @@ base64.workspace = true
bytesize.workspace = true
cargo-platform.workspace = true
cargo-credential.workspace = true
cargo-credential-libsecret.workspace = true
cargo-credential-macos-keychain.workspace = true
cargo-credential-wincred.workspace = true
cargo-util.workspace = true
2 changes: 1 addition & 1 deletion crates/xtask-bump-check/src/xtask.rs
Original file line number Diff line number Diff line change
@@ -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",
226 changes: 0 additions & 226 deletions credential/cargo-credential-gnome-secret/src/libsecret.rs

This file was deleted.

12 changes: 0 additions & 12 deletions credential/cargo-credential-gnome-secret/src/main.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -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.
235 changes: 235 additions & 0 deletions credential/cargo-credential-libsecret/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -109,6 +109,7 @@ allow = [
"MPL-2.0",
"Unicode-DFS-2016",
"CC0-1.0",
"ISC",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
1 change: 1 addition & 0 deletions src/cargo/util/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -451,6 +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_libsecret::LibSecretCredential {}),
process => Box::new(CredentialProcessCredential::new(process)),
};
config.shell().verbose(|c| {
15 changes: 1 addition & 14 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
@@ -1094,6 +1094,7 @@ executed within the Cargo process. They are identified with the `cargo:` prefix.
* `cargo:token` - Uses Cargo's config and `credentials.toml` to store the token (default).
* `cargo:wincred` - Uses the Windows Credential Manager to store the token.
* `cargo:macos-keychain` - Uses the macOS Keychain to store the token.
* `cargo:libsecret` - Uses [libsecret](https://wiki.gnome.org/Projects/Libsecret) to store tokens on Linux systems.
* `cargo:token-from-stdout <command>` - Launch a subprocess that returns a token
on stdout. Newlines will be trimmed. The process inherits the user's stdin and stderr.
It should exit 0 on success, and nonzero on error.
@@ -1130,20 +1131,6 @@ In the config, add it to `global-credential-providers`:
global-credential-providers = ["cargo-credential-1password"]
```

A wrapper is available for GNOME
[libsecret](https://wiki.gnome.org/Projects/Libsecret) to store tokens on
Linux systems. Due to build limitations, this wrapper is not available as a
pre-compiled binary. This can be built and installed manually. First, install
libsecret using your system package manager (for example, `sudo apt install
libsecret-1-dev`). Then build and install the wrapper with `cargo install
cargo-credential-gnome-secret`.
In the config, use a path to the binary like this:

```toml
[registry]
global-credential-providers = ["cargo-credential-gnome-secret"]
```

#### JSON Interface
When using an external credential provider, Cargo communicates with the credential
provider using stdin/stdout messages passed as a single line of JSON.