cargo/tests/testsuite/credential_process.rs

703 lines
21 KiB
Rust
Raw Normal View History

2020-12-01 19:16:16 +00:00
//! Tests for credential-process.
2023-06-27 16:26:27 +00:00
use cargo_test_support::registry::{Package, TestRegistry};
2020-12-01 19:16:16 +00:00
use cargo_test_support::{basic_manifest, cargo_process, paths, project, registry, Project};
fn toml_bin(proj: &Project, name: &str) -> String {
proj.bin(name).display().to_string().replace('\\', "\\\\")
}
/// Setup for a test that will issue a command that needs to fetch a token.
///
/// This does the following:
///
/// * Spawn a thread that will act as an API server.
/// * Create a simple credential-process that will generate a fake token.
/// * Create a simple `foo` project to run the test against.
/// * Configure the credential-process config.
///
2022-10-26 04:15:45 +00:00
/// Returns the simple `foo` project to test against and the API server handle.
fn get_token_test() -> (Project, TestRegistry) {
2020-12-01 19:16:16 +00:00
// API server that checks that the token is included correctly.
let server = registry::RegistryBuilder::new()
.no_configure_token()
2022-12-12 17:49:22 +00:00
.token(cargo_test_support::registry::Token::Plaintext(
"sekrit".to_string(),
))
.alternative()
.http_api()
2023-07-21 22:30:01 +00:00
.http_index()
.auth_required()
.build();
2023-06-27 16:26:27 +00:00
let provider = build_provider(
"test-cred",
2023-07-21 22:30:01 +00:00
r#"{"Ok":{"kind":"get","token":"sekrit","cache":"session","operation_independent":false}}"#,
2023-06-27 16:26:27 +00:00
);
2020-12-01 19:16:16 +00:00
let p = project()
.file(
2024-01-26 19:40:46 +00:00
".cargo/config.toml",
2020-12-01 19:16:16 +00:00
&format!(
r#"
[registries.alternative]
index = "{}"
2023-06-27 16:26:27 +00:00
credential-provider = ["{provider}"]
2020-12-01 19:16:16 +00:00
"#,
server.index_url(),
2020-12-01 19:16:16 +00:00
),
)
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
description = "foo"
license = "MIT"
homepage = "https://example.com/"
"#,
)
.file("src/lib.rs", "")
.build();
(p, server)
2020-12-01 19:16:16 +00:00
}
#[cargo_test]
fn publish() {
// Checks that credential-process is used for `cargo publish`.
let (p, _t) = get_token_test();
2020-12-01 19:16:16 +00:00
p.cargo("publish --no-verify --registry alternative")
2020-12-01 19:16:16 +00:00
.with_stderr(
2023-06-27 16:26:27 +00:00
r#"[UPDATING] [..]
{"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"}
2020-12-01 19:16:16 +00:00
[PACKAGING] foo v0.1.0 [..]
[PACKAGED] [..]
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"publish","name":"foo","vers":"0.1.0","cksum":"[..]"}
2020-12-01 19:16:16 +00:00
[UPLOADING] foo v0.1.0 [..]
[UPLOADED] foo v0.1.0 [..]
note: Waiting [..]
You may press ctrl-c [..]
[PUBLISHED] foo v0.1.0 [..]
2023-06-27 16:26:27 +00:00
"#,
2020-12-01 19:16:16 +00:00
)
.run();
}
#[cargo_test]
fn basic_unsupported() {
// Non-action commands don't support login/logout.
let registry = registry::RegistryBuilder::new()
.no_configure_token()
.credential_provider(&["cargo:token-from-stdout", "false"])
.build();
2020-12-01 19:16:16 +00:00
cargo_process("login abcdefg")
.replace_crates_io(registry.index_url())
2020-12-01 19:16:16 +00:00
.with_status(101)
.with_stderr(
"\
[UPDATING] crates.io index
[ERROR] credential provider `cargo:token-from-stdout false` failed action `login`
2023-06-27 16:26:27 +00:00
Caused by:
requested operation not supported
2020-12-01 19:16:16 +00:00
",
)
.run();
cargo_process("logout")
.replace_crates_io(registry.index_url())
2020-12-01 19:16:16 +00:00
.with_status(101)
.with_stderr(
"\
[ERROR] credential provider `cargo:token-from-stdout false` failed action `logout`
2023-06-27 16:26:27 +00:00
Caused by:
requested operation not supported
2020-12-01 19:16:16 +00:00
",
)
.run();
}
#[cargo_test]
fn login() {
2023-06-27 16:26:27 +00:00
let registry = registry::RegistryBuilder::new()
.no_configure_token()
.credential_provider(&[
&build_provider("test-cred", r#"{"Ok": {"kind": "login"}}"#),
"cfg1",
"--cfg2",
])
.build();
2020-12-01 19:16:16 +00:00
cargo_process("login abcdefg -- cmd3 --cmd4")
2023-06-27 16:26:27 +00:00
.replace_crates_io(registry.index_url())
2020-12-01 19:16:16 +00:00
.with_stderr(
2023-06-27 16:26:27 +00:00
r#"[UPDATING] [..]
{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"login","token":"abcdefg","login-url":"[..]","args":["cfg1","--cfg2","cmd3","--cmd4"]}
2023-06-27 16:26:27 +00:00
"#,
2020-12-01 19:16:16 +00:00
)
.run();
}
#[cargo_test]
fn logout() {
let server = registry::RegistryBuilder::new()
.no_configure_token()
2023-06-27 16:26:27 +00:00
.credential_provider(&[&build_provider(
"test-cred",
r#"{"Ok": {"kind": "logout"}}"#,
)])
.build();
2020-12-01 19:16:16 +00:00
cargo_process("logout")
.replace_crates_io(server.index_url())
2020-12-01 19:16:16 +00:00
.with_stderr(
r#"{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"logout"}
2023-06-27 16:26:27 +00:00
"#,
2020-12-01 19:16:16 +00:00
)
.run();
}
#[cargo_test]
fn yank() {
let (p, _t) = get_token_test();
2020-12-01 19:16:16 +00:00
p.cargo("yank --version 0.1.0 --registry alternative")
2020-12-01 19:16:16 +00:00
.with_stderr(
2023-06-27 16:26:27 +00:00
r#"[UPDATING] [..]
{"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"}
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"yank","name":"foo","vers":"0.1.0"}
[YANK] foo@0.1.0
2023-06-27 16:26:27 +00:00
"#,
2020-12-01 19:16:16 +00:00
)
.run();
}
#[cargo_test]
fn owner() {
let (p, _t) = get_token_test();
2020-12-01 19:16:16 +00:00
p.cargo("owner --add username --registry alternative")
2023-06-27 16:26:27 +00:00
.with_stderr(
r#"[UPDATING] [..]
{"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"}
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"owners","name":"foo"}
2023-06-27 16:26:27 +00:00
[OWNER] completed!
"#,
)
.run();
}
#[cargo_test]
fn invalid_token_output() {
// Error when credential process does not output the expected format for a token.
let cred_proj = project()
.at("cred_proj")
.file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
.file("src/main.rs", r#"fn main() { print!("a\nb\n"); } "#)
.build();
cred_proj.cargo("build").run();
let _server = registry::RegistryBuilder::new()
.alternative()
.credential_provider(&[
"cargo:token-from-stdout",
&toml_bin(&cred_proj, "test-cred"),
])
2023-06-27 16:26:27 +00:00
.no_configure_token()
.build();
let p = project()
.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
.file("src/lib.rs", "")
.build();
p.cargo("publish --no-verify --registry alternative")
2023-06-27 16:26:27 +00:00
.with_status(101)
2020-12-01 19:16:16 +00:00
.with_stderr(
"\
[UPDATING] [..]
2023-06-27 16:26:27 +00:00
[ERROR] credential provider `[..]test-cred[EXE]` failed action `get`
Caused by:
process `[..]` returned more than one line of output; expected a single token
2020-12-01 19:16:16 +00:00
",
)
.run();
}
2023-06-27 16:26:27 +00:00
/// Builds a credential provider that echos the request from cargo to stderr,
/// and prints the `response` to stdout.
fn build_provider(name: &str, response: &str) -> String {
// The credential process to use.
let cred_proj = project()
.at(name)
.file("Cargo.toml", &basic_manifest(name, "1.0.0"))
.file(
"src/main.rs",
&r####"
fn main() {
println!(r#"{{"v":[1]}}"#);
assert_eq!(std::env::args().skip(1).next().unwrap(), "--cargo-plugin");
let mut buffer = String::new();
std::io::stdin().read_line(&mut buffer).unwrap();
eprint!("{}", buffer);
use std::io::Write;
std::io::stdout().write_all(r###"[RESPONSE]"###.as_bytes()).unwrap();
println!();
} "####
.replace("[RESPONSE]", response),
)
.build();
cred_proj.cargo("build").run();
toml_bin(&cred_proj, name)
}
#[cargo_test]
fn not_found() {
let registry = registry::RegistryBuilder::new()
.no_configure_token()
.http_index()
.auth_required()
.credential_provider(&[&build_provider(
"not_found",
r#"{"Err": {"kind": "not-found"}}"#,
)])
.build();
// should not suggest a _TOKEN environment variable since the cargo:token provider isn't available.
cargo_process("install -v foo")
.replace_crates_io(registry.index_url())
.with_status(101)
.with_stderr(
r#"[UPDATING] [..]
[CREDENTIAL] [..]not_found[..] get crates-io
{"v":1[..]
[ERROR] failed to query replaced source registry `crates-io`
Caused by:
no token found, please run `cargo login`
"#,
)
.run();
}
#[cargo_test]
fn all_not_found() {
let server = registry::RegistryBuilder::new()
.no_configure_token()
.auth_required()
.http_index()
.build();
let not_found = build_provider("not_found", r#"{"Err": {"kind": "not-found"}}"#);
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
format!(
r#"
[registry]
global-credential-providers = ["not_found"]
[credential-alias]
not_found = ["{not_found}"]
"#,
)
.as_bytes(),
)
.unwrap();
// should not suggest a _TOKEN environment variable since the cargo:token provider isn't available.
cargo_process("install -v foo")
.replace_crates_io(server.index_url())
.with_status(101)
.with_stderr(
r#"[UPDATING] [..]
[CREDENTIAL] [..]not_found[..] get crates-io
{"v":1,"registry":{"index-url":"[..]","name":"crates-io","headers":[[..]"WWW-Authenticate: Cargo login_url=\"https://test-registry-login/me\""[..]]},"kind":"get","operation":"read"}
[ERROR] failed to query replaced source registry `crates-io`
Caused by:
no token found, please run `cargo login`
"#,
)
.run();
}
#[cargo_test]
fn all_not_supported() {
let server = registry::RegistryBuilder::new()
.no_configure_token()
.auth_required()
.http_index()
.build();
let not_supported =
build_provider("not_supported", r#"{"Err": {"kind": "url-not-supported"}}"#);
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
format!(
r#"
[registry]
global-credential-providers = ["not_supported"]
[credential-alias]
not_supported = ["{not_supported}"]
"#,
)
.as_bytes(),
)
.unwrap();
cargo_process("install -v foo")
.replace_crates_io(server.index_url())
.with_status(101)
.with_stderr(
r#"[UPDATING] [..]
[CREDENTIAL] [..]not_supported[..] get crates-io
{"v":1,"registry":{"index-url":"[..]","name":"crates-io","headers":[[..]"WWW-Authenticate: Cargo login_url=\"https://test-registry-login/me\""[..]]},"kind":"get","operation":"read"}
[ERROR] failed to query replaced source registry `crates-io`
Caused by:
no credential providers could handle the request
"#,
)
.run();
}
2020-12-01 19:16:16 +00:00
#[cargo_test]
2023-06-27 16:26:27 +00:00
fn multiple_providers() {
let server = registry::RegistryBuilder::new()
.no_configure_token()
.build();
2023-06-27 16:26:27 +00:00
// Set up two credential providers: the first will fail with "UrlNotSupported"
// and Cargo should skip it. The second should succeed.
let url_not_supported = build_provider(
"url_not_supported",
r#"{"Err": {"kind": "url-not-supported"}}"#,
);
let success_provider = build_provider("success_provider", r#"{"Ok": {"kind": "login"}}"#);
2021-03-20 20:43:33 +00:00
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
2023-06-27 16:26:27 +00:00
format!(
r#"
[registry]
global-credential-providers = ["success_provider", "url_not_supported"]
[credential-alias]
success_provider = ["{success_provider}"]
url_not_supported = ["{url_not_supported}"]
"#,
)
.as_bytes(),
2020-12-01 19:16:16 +00:00
)
.unwrap();
cargo_process("login -v abcdefg")
.replace_crates_io(server.index_url())
2020-12-01 19:16:16 +00:00
.with_stderr(
2023-06-27 16:26:27 +00:00
r#"[UPDATING] [..]
[CREDENTIAL] [..]url_not_supported[..] login crates-io
{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"login","token":"abcdefg","login-url":"[..]"}
2023-06-27 16:26:27 +00:00
[CREDENTIAL] [..]success_provider[..] login crates-io
{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"login","token":"abcdefg","login-url":"[..]"}
2023-06-27 16:26:27 +00:00
"#,
2020-12-01 19:16:16 +00:00
)
.run();
}
#[cargo_test]
2023-06-27 16:26:27 +00:00
fn both_token_and_provider() {
let server = registry::RegistryBuilder::new()
.credential_provider(&["cargo:paseto"])
.build();
cargo_process("login -Z asymmetric-token")
.masquerade_as_nightly_cargo(&["asymmetric-token"])
.replace_crates_io(server.index_url())
.with_stderr(
r#"[UPDATING] [..]
[WARNING] registry `crates-io` has a token configured in [..] that will be ignored because this registry is configured to use credential-provider `cargo:paseto`
k3.public[..]
"#,
)
.run();
}
#[cargo_test]
fn registry_provider_overrides_global() {
2023-06-27 16:26:27 +00:00
let server = registry::RegistryBuilder::new().build();
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
2023-06-27 16:26:27 +00:00
format!(
r#"
[registry]
global-credential-providers = ["should-not-be-called"]
2023-06-27 16:26:27 +00:00
"#,
)
.as_bytes(),
)
.unwrap();
cargo_process("login -v abcdefg")
.env("CARGO_REGISTRY_CREDENTIAL_PROVIDER", "cargo:token")
2023-06-27 16:26:27 +00:00
.replace_crates_io(server.index_url())
.with_stderr(
r#"[UPDATING] [..]
[CREDENTIAL] cargo:token login crates-io
[LOGIN] token for `crates-io` saved
"#,
)
.run();
let credentials =
std::fs::read_to_string(paths::home().join(".cargo/credentials.toml")).unwrap();
assert_eq!(credentials, "[registry]\ntoken = \"abcdefg\"\n");
}
2023-06-27 16:26:27 +00:00
#[cargo_test]
fn both_asymmetric_and_token() {
let server = registry::RegistryBuilder::new().build();
2021-03-20 20:43:33 +00:00
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
format!(
r#"
[registry]
2023-06-27 16:26:27 +00:00
token = "foo"
secret-key = "bar"
"#,
)
.as_bytes(),
)
.unwrap();
cargo_process("login -Zasymmetric-token -v abcdefg")
.masquerade_as_nightly_cargo(&["asymmetric-token"])
2023-06-27 16:26:27 +00:00
.replace_crates_io(server.index_url())
.with_stderr(
r#"[UPDATING] [..]
2024-01-26 19:40:46 +00:00
[WARNING] registry `crates-io` has a `secret_key` configured in [..]config.toml that will be ignored because a `token` is also configured, and the `cargo:token` provider is configured with higher precedence
2023-06-27 16:26:27 +00:00
[CREDENTIAL] cargo:token login crates-io
[LOGIN] token for `crates-io` saved
"#,
)
.run();
}
#[cargo_test]
fn token_caching() {
let server = registry::RegistryBuilder::new()
.no_configure_token()
.no_configure_registry()
.token(cargo_test_support::registry::Token::Plaintext(
"sekrit".to_string(),
))
.alternative()
.http_api()
2023-07-21 22:30:01 +00:00
.http_index()
2023-06-27 16:26:27 +00:00
.build();
// Token should not be re-used if it is expired
let expired_provider = build_provider(
"expired_provider",
r#"{"Ok":{"kind":"get","token":"sekrit","cache":"expires","expiration":0,"operation_independent":true}}"#,
2023-06-27 16:26:27 +00:00
);
// Token should not be re-used for a different operation if it is not operation_independent
let non_independent_provider = build_provider(
"non_independent_provider",
2023-06-27 16:26:27 +00:00
r#"{"Ok":{"kind":"get","token":"sekrit","cache":"session","operation_independent":false}}"#,
);
let p = project()
2023-06-27 16:26:27 +00:00
.file(
2024-01-26 19:40:46 +00:00
".cargo/config.toml",
2023-06-27 16:26:27 +00:00
&format!(
r#"
[registries.alternative]
index = "{}"
credential-provider = ["{expired_provider}"]
"#,
server.index_url(),
),
)
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
description = "foo"
license = "MIT"
homepage = "https://example.com/"
"#,
)
.file("src/lib.rs", "")
.build();
2023-06-27 16:26:27 +00:00
let output = r#"[UPDATING] `alternative` index
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"read"}
2023-06-27 16:26:27 +00:00
[PACKAGING] foo v0.1.0 [..]
[PACKAGED] [..]
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"publish","name":"foo","vers":"0.1.0","cksum":"[..]"}
2023-06-27 16:26:27 +00:00
[UPLOADING] foo v0.1.0 [..]
[UPLOADED] foo v0.1.0 [..]
note: Waiting [..]
You may press ctrl-c [..]
[PUBLISHED] foo v0.1.0 [..]
"#;
// The output should contain two JSON messages from the provider in both cases:
2023-06-27 16:26:27 +00:00
// The first because the credential is expired, the second because the provider
// indicated that the token was non-operation-independent.
p.cargo("publish --registry alternative --no-verify")
2023-06-27 16:26:27 +00:00
.with_stderr(output)
.run();
p.change_file(
2024-01-26 19:40:46 +00:00
".cargo/config.toml",
2023-06-27 16:26:27 +00:00
&format!(
r#"
[registries.alternative]
index = "{}"
credential-provider = ["{non_independent_provider}"]
"#,
server.index_url(),
),
);
p.cargo("publish --registry alternative --no-verify")
2023-06-27 16:26:27 +00:00
.with_stderr(output)
.run();
}
#[cargo_test]
fn basic_provider() {
let cred_proj = project()
.at("cred_proj")
.file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
.file("src/main.rs", r#"fn main() {
eprintln!("CARGO={:?}", std::env::var("CARGO").ok());
eprintln!("CARGO_REGISTRY_NAME_OPT={:?}", std::env::var("CARGO_REGISTRY_NAME_OPT").ok());
eprintln!("CARGO_REGISTRY_INDEX_URL={:?}", std::env::var("CARGO_REGISTRY_INDEX_URL").ok());
print!("sekrit");
}"#)
.build();
cred_proj.cargo("build").run();
let _server = registry::RegistryBuilder::new()
.no_configure_token()
.credential_provider(&[
"cargo:token-from-stdout",
&toml_bin(&cred_proj, "test-cred"),
])
2023-06-27 16:26:27 +00:00
.token(cargo_test_support::registry::Token::Plaintext(
"sekrit".to_string(),
))
.alternative()
.http_api()
.auth_required()
.build();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
version = "0.0.1"
registry = "alternative"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
Package::new("bar", "0.0.1").alternative(true).publish();
p.cargo("check")
.with_stderr(
"\
2023-06-27 16:26:27 +00:00
[UPDATING] `alternative` index
CARGO=Some([..])
CARGO_REGISTRY_NAME_OPT=Some(\"alternative\")
CARGO_REGISTRY_INDEX_URL=Some([..])
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `alternative`)
[CHECKING] bar v0.0.1 (registry `alternative`)
[CHECKING] foo v0.0.1 ([..])
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn unsupported_version() {
let cred_proj = project()
.at("new-vers")
.file("Cargo.toml", &basic_manifest("new-vers", "1.0.0"))
.file(
"src/main.rs",
&r####"
fn main() {
println!(r#"{{"v":[998, 999]}}"#);
assert_eq!(std::env::args().skip(1).next().unwrap(), "--cargo-plugin");
let mut buffer = String::new();
std::io::stdin().read_line(&mut buffer).unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
panic!("child process should have been killed before getting here");
} "####,
)
.build();
cred_proj.cargo("build").run();
let provider = toml_bin(&cred_proj, "new-vers");
let registry = registry::RegistryBuilder::new()
.no_configure_token()
.credential_provider(&[&provider])
.build();
cargo_process("login abcdefg")
.replace_crates_io(registry.index_url())
.with_status(101)
.with_stderr(
r#"[UPDATING] [..]
[ERROR] credential provider `[..]` failed action `login`
Caused by:
credential provider supports protocol versions [998, 999], while Cargo supports [1]
"#,
)
.run();
}
#[cargo_test]
fn alias_builtin_warning() {
let registry = registry::RegistryBuilder::new()
.credential_provider(&[&"cargo:token"])
.build();
cargo_util::paths::append(
2024-01-26 19:40:46 +00:00
&paths::home().join(".cargo/config.toml"),
format!(
r#"
[credential-alias]
"cargo:token" = ["ignored"]
"#,
)
.as_bytes(),
)
.unwrap();
cargo_process("login abcdefg")
.replace_crates_io(registry.index_url())
.with_stderr(
r#"[UPDATING] [..]
[WARNING] credential-alias `cargo:token` (defined in `[..]`) will be ignored because it would shadow a built-in credential-provider
[LOGIN] token for `crates-io` saved
"#,
)
.run();
}