Skip to content

Commit 9d506e5

Browse files
committed
Auto merge of #11993 - epage:member, r=weihanglo
fix: Allow win/mac credential managers to build on all platforms ### What does this PR try to resolve? This is a step towards #11987 by making two of the platform-specific credential managers build on all platforms using `cfg`. I haven't done `gnome-secret` yet because that is more of an oddball in that it isn't just platforms-specific but dependent on what is installed on that platform. ### How should we test and review this PR? ```console $ cargo check --workspace --exclude cargo-credential-gnome-secret ``` Note that the commits are broken down so you can view the movements of code separate from the functionality being changed. ### Additional information Other information you want to mention in this PR, such as prior arts, future extensions, an unresolved problem, or a TODO list. --> <!-- homu-ignore:end -->
2 parents d3a1bbd + 943edea commit 9d506e5

File tree

3 files changed

+161
-122
lines changed
  • crates/credential
    • cargo-credential/src
    • cargo-credential-macos-keychain/src
    • cargo-credential-wincred/src

3 files changed

+161
-122
lines changed
Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,58 @@
11
//! Cargo registry macos keychain credential process.
22
3-
use cargo_credential::{Credential, Error};
4-
use security_framework::os::macos::keychain::SecKeychain;
3+
#[cfg(target_os = "macos")]
4+
mod macos {
5+
use cargo_credential::{Credential, Error};
6+
use security_framework::os::macos::keychain::SecKeychain;
57

6-
struct MacKeychain;
8+
pub(crate) struct MacKeychain;
79

8-
/// The account name is not used.
9-
const ACCOUNT: &'static str = "";
10+
/// The account name is not used.
11+
const ACCOUNT: &'static str = "";
1012

11-
fn registry(registry_name: &str) -> String {
12-
format!("cargo-registry:{}", registry_name)
13-
}
14-
15-
impl Credential for MacKeychain {
16-
fn name(&self) -> &'static str {
17-
env!("CARGO_PKG_NAME")
13+
fn registry(registry_name: &str) -> String {
14+
format!("cargo-registry:{}", registry_name)
1815
}
1916

20-
fn get(&self, index_url: &str) -> Result<String, Error> {
21-
let keychain = SecKeychain::default().unwrap();
22-
let service_name = registry(index_url);
23-
let (pass, _item) = keychain.find_generic_password(&service_name, ACCOUNT)?;
24-
String::from_utf8(pass.as_ref().to_vec())
25-
.map_err(|_| "failed to convert token to UTF8".into())
26-
}
17+
impl Credential for MacKeychain {
18+
fn name(&self) -> &'static str {
19+
env!("CARGO_PKG_NAME")
20+
}
2721

28-
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
29-
let keychain = SecKeychain::default().unwrap();
30-
let service_name = registry(name.unwrap_or(index_url));
31-
if let Ok((_pass, mut item)) = keychain.find_generic_password(&service_name, ACCOUNT) {
32-
item.set_password(token.as_bytes())?;
33-
} else {
34-
keychain.add_generic_password(&service_name, ACCOUNT, token.as_bytes())?;
22+
fn get(&self, index_url: &str) -> Result<String, Error> {
23+
let keychain = SecKeychain::default().unwrap();
24+
let service_name = registry(index_url);
25+
let (pass, _item) = keychain.find_generic_password(&service_name, ACCOUNT)?;
26+
String::from_utf8(pass.as_ref().to_vec())
27+
.map_err(|_| "failed to convert token to UTF8".into())
3528
}
36-
Ok(())
37-
}
3829

39-
fn erase(&self, index_url: &str) -> Result<(), Error> {
40-
let keychain = SecKeychain::default().unwrap();
41-
let service_name = registry(index_url);
42-
let (_pass, item) = keychain.find_generic_password(&service_name, ACCOUNT)?;
43-
item.delete();
44-
Ok(())
30+
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
31+
let keychain = SecKeychain::default().unwrap();
32+
let service_name = registry(name.unwrap_or(index_url));
33+
if let Ok((_pass, mut item)) = keychain.find_generic_password(&service_name, ACCOUNT) {
34+
item.set_password(token.as_bytes())?;
35+
} else {
36+
keychain.add_generic_password(&service_name, ACCOUNT, token.as_bytes())?;
37+
}
38+
Ok(())
39+
}
40+
41+
fn erase(&self, index_url: &str) -> Result<(), Error> {
42+
let keychain = SecKeychain::default().unwrap();
43+
let service_name = registry(index_url);
44+
let (_pass, item) = keychain.find_generic_password(&service_name, ACCOUNT)?;
45+
item.delete();
46+
Ok(())
47+
}
4548
}
4649
}
4750

51+
#[cfg(not(target_os = "macos"))]
52+
use cargo_credential::UnsupportedCredential as MacKeychain;
53+
#[cfg(target_os = "macos")]
54+
use macos::MacKeychain;
55+
4856
fn main() {
4957
cargo_credential::main(MacKeychain);
5058
}
Lines changed: 99 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,122 @@
11
//! Cargo registry windows credential process.
22
3-
use cargo_credential::{Credential, Error};
4-
use std::ffi::OsStr;
5-
use std::os::windows::ffi::OsStrExt;
3+
#[cfg(windows)]
4+
mod win {
5+
use cargo_credential::{Credential, Error};
6+
use std::ffi::OsStr;
7+
use std::os::windows::ffi::OsStrExt;
68

7-
use windows_sys::core::PWSTR;
8-
use windows_sys::Win32::Foundation::ERROR_NOT_FOUND;
9-
use windows_sys::Win32::Foundation::FILETIME;
10-
use windows_sys::Win32::Foundation::TRUE;
11-
use windows_sys::Win32::Security::Credentials::CredDeleteW;
12-
use windows_sys::Win32::Security::Credentials::CredReadW;
13-
use windows_sys::Win32::Security::Credentials::CredWriteW;
14-
use windows_sys::Win32::Security::Credentials::CREDENTIALW;
15-
use windows_sys::Win32::Security::Credentials::CRED_PERSIST_LOCAL_MACHINE;
16-
use windows_sys::Win32::Security::Credentials::CRED_TYPE_GENERIC;
9+
use windows_sys::core::PWSTR;
10+
use windows_sys::Win32::Foundation::ERROR_NOT_FOUND;
11+
use windows_sys::Win32::Foundation::FILETIME;
12+
use windows_sys::Win32::Foundation::TRUE;
13+
use windows_sys::Win32::Security::Credentials::CredDeleteW;
14+
use windows_sys::Win32::Security::Credentials::CredReadW;
15+
use windows_sys::Win32::Security::Credentials::CredWriteW;
16+
use windows_sys::Win32::Security::Credentials::CREDENTIALW;
17+
use windows_sys::Win32::Security::Credentials::CRED_PERSIST_LOCAL_MACHINE;
18+
use windows_sys::Win32::Security::Credentials::CRED_TYPE_GENERIC;
1719

18-
struct WindowsCredential;
20+
pub(crate) struct WindowsCredential;
1921

20-
/// Converts a string to a nul-terminated wide UTF-16 byte sequence.
21-
fn wstr(s: &str) -> Vec<u16> {
22-
let mut wide: Vec<u16> = OsStr::new(s).encode_wide().collect();
23-
if wide.iter().any(|b| *b == 0) {
24-
panic!("nul byte in wide string");
22+
/// Converts a string to a nul-terminated wide UTF-16 byte sequence.
23+
fn wstr(s: &str) -> Vec<u16> {
24+
let mut wide: Vec<u16> = OsStr::new(s).encode_wide().collect();
25+
if wide.iter().any(|b| *b == 0) {
26+
panic!("nul byte in wide string");
27+
}
28+
wide.push(0);
29+
wide
2530
}
26-
wide.push(0);
27-
wide
28-
}
2931

30-
fn target_name(registry_name: &str) -> Vec<u16> {
31-
wstr(&format!("cargo-registry:{}", registry_name))
32-
}
33-
34-
impl Credential for WindowsCredential {
35-
fn name(&self) -> &'static str {
36-
env!("CARGO_PKG_NAME")
32+
fn target_name(registry_name: &str) -> Vec<u16> {
33+
wstr(&format!("cargo-registry:{}", registry_name))
3734
}
3835

39-
fn get(&self, index_url: &str) -> Result<String, Error> {
40-
let target_name = target_name(index_url);
41-
let p_credential: *mut CREDENTIALW = std::ptr::null_mut() as *mut _;
42-
unsafe {
43-
if CredReadW(
44-
target_name.as_ptr(),
45-
CRED_TYPE_GENERIC,
46-
0,
47-
p_credential as *mut _ as *mut _,
48-
) != TRUE
49-
{
50-
return Err(
51-
format!("failed to fetch token: {}", std::io::Error::last_os_error()).into(),
36+
impl Credential for WindowsCredential {
37+
fn name(&self) -> &'static str {
38+
env!("CARGO_PKG_NAME")
39+
}
40+
41+
fn get(&self, index_url: &str) -> Result<String, Error> {
42+
let target_name = target_name(index_url);
43+
let p_credential: *mut CREDENTIALW = std::ptr::null_mut() as *mut _;
44+
unsafe {
45+
if CredReadW(
46+
target_name.as_ptr(),
47+
CRED_TYPE_GENERIC,
48+
0,
49+
p_credential as *mut _ as *mut _,
50+
) != TRUE
51+
{
52+
return Err(format!(
53+
"failed to fetch token: {}",
54+
std::io::Error::last_os_error()
55+
)
56+
.into());
57+
}
58+
let bytes = std::slice::from_raw_parts(
59+
(*p_credential).CredentialBlob,
60+
(*p_credential).CredentialBlobSize as usize,
5261
);
62+
String::from_utf8(bytes.to_vec())
63+
.map_err(|_| "failed to convert token to UTF8".into())
5364
}
54-
let bytes = std::slice::from_raw_parts(
55-
(*p_credential).CredentialBlob,
56-
(*p_credential).CredentialBlobSize as usize,
57-
);
58-
String::from_utf8(bytes.to_vec()).map_err(|_| "failed to convert token to UTF8".into())
5965
}
60-
}
6166

62-
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
63-
let token = token.as_bytes();
64-
let target_name = target_name(index_url);
65-
let comment = match name {
66-
Some(name) => wstr(&format!("Cargo registry token for {}", name)),
67-
None => wstr("Cargo registry token"),
68-
};
69-
let mut credential = CREDENTIALW {
70-
Flags: 0,
71-
Type: CRED_TYPE_GENERIC,
72-
TargetName: target_name.as_ptr() as PWSTR,
73-
Comment: comment.as_ptr() as PWSTR,
74-
LastWritten: FILETIME {
75-
dwLowDateTime: 0,
76-
dwHighDateTime: 0,
77-
},
78-
CredentialBlobSize: token.len() as u32,
79-
CredentialBlob: token.as_ptr() as *mut u8,
80-
Persist: CRED_PERSIST_LOCAL_MACHINE,
81-
AttributeCount: 0,
82-
Attributes: std::ptr::null_mut(),
83-
TargetAlias: std::ptr::null_mut(),
84-
UserName: std::ptr::null_mut(),
85-
};
86-
let result = unsafe { CredWriteW(&mut credential, 0) };
87-
if result != TRUE {
88-
let err = std::io::Error::last_os_error();
89-
return Err(format!("failed to store token: {}", err).into());
67+
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
68+
let token = token.as_bytes();
69+
let target_name = target_name(index_url);
70+
let comment = match name {
71+
Some(name) => wstr(&format!("Cargo registry token for {}", name)),
72+
None => wstr("Cargo registry token"),
73+
};
74+
let mut credential = CREDENTIALW {
75+
Flags: 0,
76+
Type: CRED_TYPE_GENERIC,
77+
TargetName: target_name.as_ptr() as PWSTR,
78+
Comment: comment.as_ptr() as PWSTR,
79+
LastWritten: FILETIME {
80+
dwLowDateTime: 0,
81+
dwHighDateTime: 0,
82+
},
83+
CredentialBlobSize: token.len() as u32,
84+
CredentialBlob: token.as_ptr() as *mut u8,
85+
Persist: CRED_PERSIST_LOCAL_MACHINE,
86+
AttributeCount: 0,
87+
Attributes: std::ptr::null_mut(),
88+
TargetAlias: std::ptr::null_mut(),
89+
UserName: std::ptr::null_mut(),
90+
};
91+
let result = unsafe { CredWriteW(&mut credential, 0) };
92+
if result != TRUE {
93+
let err = std::io::Error::last_os_error();
94+
return Err(format!("failed to store token: {}", err).into());
95+
}
96+
Ok(())
9097
}
91-
Ok(())
92-
}
9398

94-
fn erase(&self, index_url: &str) -> Result<(), Error> {
95-
let target_name = target_name(index_url);
96-
let result = unsafe { CredDeleteW(target_name.as_ptr(), CRED_TYPE_GENERIC, 0) };
97-
if result != TRUE {
98-
let err = std::io::Error::last_os_error();
99-
if err.raw_os_error() == Some(ERROR_NOT_FOUND as i32) {
100-
eprintln!("not currently logged in to `{}`", index_url);
101-
return Ok(());
99+
fn erase(&self, index_url: &str) -> Result<(), Error> {
100+
let target_name = target_name(index_url);
101+
let result = unsafe { CredDeleteW(target_name.as_ptr(), CRED_TYPE_GENERIC, 0) };
102+
if result != TRUE {
103+
let err = std::io::Error::last_os_error();
104+
if err.raw_os_error() == Some(ERROR_NOT_FOUND as i32) {
105+
eprintln!("not currently logged in to `{}`", index_url);
106+
return Ok(());
107+
}
108+
return Err(format!("failed to remove token: {}", err).into());
102109
}
103-
return Err(format!("failed to remove token: {}", err).into());
110+
Ok(())
104111
}
105-
Ok(())
106112
}
107113
}
108114

115+
#[cfg(not(windows))]
116+
use cargo_credential::UnsupportedCredential as WindowsCredential;
117+
#[cfg(windows)]
118+
use win::WindowsCredential;
119+
109120
fn main() {
110121
cargo_credential::main(WindowsCredential);
111122
}

crates/credential/cargo-credential/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ pub trait Credential {
3434
fn erase(&self, index_url: &str) -> Result<(), Error>;
3535
}
3636

37+
pub struct UnsupportedCredential;
38+
39+
impl Credential for UnsupportedCredential {
40+
fn name(&self) -> &'static str {
41+
"unsupported"
42+
}
43+
44+
fn get(&self, _index_url: &str) -> Result<String, Error> {
45+
Err("unsupported".into())
46+
}
47+
48+
fn store(&self, _index_url: &str, _token: &str, _name: Option<&str>) -> Result<(), Error> {
49+
Err("unsupported".into())
50+
}
51+
52+
fn erase(&self, _index_url: &str) -> Result<(), Error> {
53+
Err("unsupported".into())
54+
}
55+
}
56+
3757
/// Runs the credential interaction by processing the command-line and
3858
/// environment variables.
3959
pub fn main(credential: impl Credential) {

0 commit comments

Comments
 (0)