//! Tests for registry authentication. use cargo_test_support::compare::assert_e2e; use cargo_test_support::prelude::*; use cargo_test_support::registry::{Package, RegistryBuilder, Token}; use cargo_test_support::str; use cargo_test_support::{project, Execs, Project}; fn cargo(p: &Project, s: &str) -> Execs { let mut e = p.cargo(s); e.masquerade_as_nightly_cargo(&["asymmetric-token"]) .arg("-Zasymmetric-token"); e.env( "CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS", "cargo:paseto cargo:token", ); e } fn make_project() -> Project { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" 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_test] fn requires_credential_provider() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_api() .build(); let p = make_project(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: authenticated registries require a credential-provider to be configured see https://doc.rust-lang.org/cargo/reference/registry-authentication.html for details "#]]) .run(); } #[cargo_test] fn simple() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "build") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn simple_with_asymmetric() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .token(cargo_test_support::registry::Token::rfc_key()) .build(); let p = make_project(); cargo(&p, "build") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_config() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_registry() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE_INDEX", registry.index_url().as_str(), ) .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_token() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_token_with_asymmetric() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn bad_environment_token_with_asymmetric_subject() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT", "incorrect", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn bad_environment_token_with_asymmetric_incorrect_subject() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::rfc_key()) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT", "incorrect", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn bad_environment_token_with_incorrect_asymmetric() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", "k3.secret.9Vxr5hVlI_g_orBZN54vPz20bmB4O76wB_MVqUSuJJJqHFLwP8kdn_RY5g6J6pQG", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn missing_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .run(); } #[cargo_test] fn missing_token_git() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .build(); let p = make_project(); cargo(&p, "build") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .run(); } #[cargo_test] fn incorrect_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .run(); } #[cargo_test] fn incorrect_token_git() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_api() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [ERROR] failed to download from `http://127.0.0.1:[..]/dl/bar/0.0.1/download` Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/0.0.1/download` (127.0.0.1), got 401 body: Unauthorized message from server. "#]]) .run(); } #[cargo_test] fn anonymous_alt_registry() { // An alternative registry that requires auth, but is not in the config. let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .no_configure_registry() .http_index() .build(); let p = make_project(); cargo(&p, &format!("install --index {} bar", registry.index_url())) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [ERROR] no token found for `sparse+http://127.0.0.1:[..]/index/` consider setting up an alternate registry in Cargo's configuration as described by https://doc.rust-lang.org/cargo/reference/registries.html [registries] my-registry = { index = "sparse+http://127.0.0.1:[..]/index/" } "#]]) .run(); } #[cargo_test] fn login() { let _registry = RegistryBuilder::new() .alternative() .no_configure_token() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "login --registry alternative") .with_stdin("sekrit") .run(); } #[cargo_test] fn login_existing_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "login --registry alternative") .with_stdin("sekrit") .run(); } #[cargo_test] fn duplicate_index() { let server = RegistryBuilder::new() .alternative() .no_configure_token() .auth_required() .build(); let p = make_project(); // Two alternative registries with the same index. cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE1_INDEX", server.index_url().as_str(), ) .env( "CARGO_REGISTRIES_ALTERNATIVE2_INDEX", server.index_url().as_str(), ) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 2 packages to latest compatible versions [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: multiple registries are configured with the same index url 'registry+[ROOTURL]/alternative-registry': alternative1, alternative2 "#]]) .run(); } #[cargo_test] fn token_not_logged() { // Checks that the token isn't displayed in debug output (for both HTTP // index and registry API). Note that this doesn't fully verify the // correct behavior since we don't have an HTTP2 server, and curl behaves // significantly differently when using HTTP2. let crates_io = RegistryBuilder::new() .http_api() .http_index() .auth_required() .token(Token::Plaintext("a-unique_token".to_string())) .build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); let output = cargo(&p, "publish") .replace_crates_io(crates_io.index_url()) .env("CARGO_HTTP_DEBUG", "true") .env("CARGO_LOG", "trace") .exec_with_output() .unwrap(); let log = String::from_utf8(output.stderr).unwrap(); assert_e2e().eq( &log, str![[r#" ... [PUBLISHED] foo v0.1.0 at registry `crates-io` "#]], ); let authorizations: Vec<_> = log .lines() .filter(|line| { line.contains("http-debug:") && line.to_lowercase().contains("authorization") }) .collect(); assert!(authorizations.iter().all(|line| line.contains("REDACTED"))); // Total authorizations: // 1. Initial config.json // 2. config.json again for verification // 3. /index/3/b/bar // 4. /dl/bar/1.0.0/download // 5. /api/v1/crates/new // 6. config.json for the "wait for publish" // 7. /index/3/f/foo for the "wait for publish" assert_eq!(authorizations.len(), 7); assert!(!log.contains("a-unique_token")); }