From 053b9f9178a3c35612384c9c39ad26f53fafdf53 Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Mon, 30 Oct 2017 14:29:37 +0000 Subject: [PATCH] Adding support to provide login credentials for an alternate registry. --- src/bin/login.rs | 13 ++- src/bin/owner.rs | 3 + src/bin/publish.rs | 4 + src/bin/search.rs | 5 +- src/bin/yank.rs | 5 +- src/cargo/core/source/source_id.rs | 21 +++-- src/cargo/ops/registry.rs | 69 +++++++++------ src/cargo/util/config.rs | 14 ++- tests/alt-registry.rs | 47 ++++++++++ tests/login.rs | 132 ++++++++++++++++++----------- 10 files changed, 217 insertions(+), 96 deletions(-) diff --git a/src/bin/login.rs b/src/bin/login.rs index 7a5397778..d55176ca9 100755 --- a/src/bin/login.rs +++ b/src/bin/login.rs @@ -17,6 +17,7 @@ pub struct Options { flag_locked: bool, #[serde(rename = "flag_Z")] flag_z: Vec, + flag_registry: Option, } pub const USAGE: &'static str = " @@ -34,6 +35,7 @@ Options: --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo + --registry REGISTRY Registry to use "; @@ -47,13 +49,16 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { let token = match options.arg_token { Some(token) => token, None => { - let host = match options.flag_host { - Some(ref host) => host.clone(), + let host = match options.flag_registry { + Some(ref registry) => { + config.get_registry_index(registry)? + } None => { let src = SourceId::crates_io(config)?; let mut src = RegistrySource::remote(&src, config); src.update()?; - src.config()?.unwrap().api.unwrap() + let config = src.config()?.unwrap(); + options.flag_host.clone().unwrap_or(config.api.unwrap()) } }; println!("please visit {}me and paste the API Token below", host); @@ -66,6 +71,6 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { } }; - ops::registry_login(config, token, options.flag_host)?; + ops::registry_login(config, token, options.flag_registry)?; Ok(()) } diff --git a/src/bin/owner.rs b/src/bin/owner.rs index 6c76a6faf..e5d5e3530 100644 --- a/src/bin/owner.rs +++ b/src/bin/owner.rs @@ -16,6 +16,7 @@ pub struct Options { flag_locked: bool, #[serde(rename = "flag_Z")] flag_z: Vec, + flag_registry: Option, } pub const USAGE: &'static str = " @@ -37,6 +38,7 @@ Options: --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo + --registry REGISTRY Registry to use This command will modify the owners for a package on the specified registry (or default). Note that owners of a package can upload new versions, yank old @@ -61,6 +63,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { to_add: options.flag_add, to_remove: options.flag_remove, list: options.flag_list, + registry: options.flag_registry, }; ops::modify_owners(config, &opts)?; Ok(()) diff --git a/src/bin/publish.rs b/src/bin/publish.rs index c34a0e270..b29ed9155 100644 --- a/src/bin/publish.rs +++ b/src/bin/publish.rs @@ -21,6 +21,7 @@ pub struct Options { flag_locked: bool, #[serde(rename = "flag_Z")] flag_z: Vec, + flag_registry: Option, } pub const USAGE: &'static str = " @@ -46,6 +47,7 @@ Options: --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo + --registry REGISTRY Registry to publish to "; @@ -67,6 +69,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { flag_jobs: jobs, flag_dry_run: dry_run, flag_target: target, + flag_registry: registry, .. } = options; @@ -100,6 +103,7 @@ about this warning."; target: target.as_ref().map(|t| &t[..]), jobs: jobs, dry_run: dry_run, + registry: registry, })?; Ok(()) } diff --git a/src/bin/search.rs b/src/bin/search.rs index 165dea1c8..8392f9af6 100644 --- a/src/bin/search.rs +++ b/src/bin/search.rs @@ -16,6 +16,7 @@ pub struct Options { arg_query: Vec, #[serde(rename = "flag_Z")] flag_z: Vec, + flag_registry: Option, } pub const USAGE: &'static str = " @@ -36,6 +37,7 @@ Options: --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo + --registry REGISTRY Registry to use "; pub fn execute(options: Options, config: &mut Config) -> CliResult { @@ -50,6 +52,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { flag_host: host, // TODO: Depricated, remove flag_limit: limit, arg_query: query, + flag_registry: registry, .. } = options; @@ -77,6 +80,6 @@ about this warning."; host }; - ops::search(&query.join("+"), config, index, cmp::min(100, limit.unwrap_or(10)) as u8)?; + ops::search(&query.join("+"), config, index, cmp::min(100, limit.unwrap_or(10)) as u8, registry)?; Ok(()) } diff --git a/src/bin/yank.rs b/src/bin/yank.rs index a00892a51..8ddf52421 100644 --- a/src/bin/yank.rs +++ b/src/bin/yank.rs @@ -15,6 +15,7 @@ pub struct Options { flag_locked: bool, #[serde(rename = "flag_Z")] flag_z: Vec, + flag_registry: Option, } pub static USAGE: &'static str = " @@ -35,6 +36,7 @@ Options: --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo + --registry REGISTRY Registry to use The yank command removes a previously pushed crate's version from the server's index. This command does not delete any data, and the crate will still be @@ -57,7 +59,8 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { options.flag_vers, options.flag_token, options.flag_index, - options.flag_undo)?; + options.flag_undo, + options.flag_registry)?; Ok(()) } diff --git a/src/cargo/core/source/source_id.rs b/src/cargo/core/source/source_id.rs index 566e9855a..ed34c95e6 100644 --- a/src/cargo/core/source/source_id.rs +++ b/src/cargo/core/source/source_id.rs @@ -183,17 +183,16 @@ impl SourceId { } pub fn alt_registry(config: &Config, key: &str) -> CargoResult { - if let Some(index) = config.get_string(&format!("registries.{}.index", key))? { - let url = index.val.to_url()?; - Ok(SourceId { - inner: Arc::new(SourceIdInner { - kind: Kind::Registry, - canonical_url: git::canonicalize_url(&url)?, - url: url, - precise: None, - }), - }) - } else { Err(format!("No index found for registry: `{}`", key).into()) } + let index = config.get_registry_index(key)?; + let url = index.to_url()?; + Ok(SourceId { + inner: Arc::new(SourceIdInner { + kind: Kind::Registry, + canonical_url: git::canonicalize_url(&url)?, + url: url, + precise: None, + }), + }) } /// Get this source URL diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 8119116f6..bb3d5dda8 100755 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -16,7 +16,7 @@ use core::dependency::Kind; use core::manifest::ManifestMetadata; use ops; use sources::{RegistrySource}; -use util::config::{self, Config, Value, Definition}; +use util::config::{self, Config, ConfigValue}; use util::paths; use util::ToUrl; use util::errors::{CargoError, CargoResult, CargoResultExt}; @@ -36,6 +36,7 @@ pub struct PublishOpts<'cfg> { pub jobs: Option, pub target: Option<&'cfg str>, pub dry_run: bool, + pub registry: Option, } pub fn publish(ws: &Workspace, opts: &PublishOpts) -> CargoResult<()> { @@ -51,7 +52,8 @@ pub fn publish(ws: &Workspace, opts: &PublishOpts) -> CargoResult<()> { let (mut registry, reg_id) = registry(opts.config, opts.token.clone(), - opts.index.clone())?; + opts.index.clone(), + opts.registry.clone())?; verify_dependencies(pkg, ®_id)?; // Prepare a tarball, with a non-surpressable warning if metadata @@ -190,37 +192,47 @@ fn transmit(config: &Config, } pub fn registry_configuration(config: &Config, - host: Option) -> CargoResult { - let (index, token) = match host { - Some(host) => { - (Some(Value { val: host.clone(), definition: Definition::Environment }), - config.get_string(&format!("registry.{}.token", host))?) + registry: Option) -> CargoResult { + + let (index, token) = match registry { + Some(registry) => { + let index = Some(config.get_registry_index(®istry)?); + let table = config.get_table(&format!("registry.{}", registry))?.map(|t| t.val); + let token = table.and_then(|table| { + match table.get("token".into()) { + Some(&ConfigValue::String(ref i, _)) => Some(i.to_string()), + _ => None, + } + }); + + (index, token) } None => { // Checking out for default index and token - (config.get_string("registry.index")?, - config.get_string("registry.token")?) + (config.get_string("registry.index")?.map(|p| p.val), + config.get_string("registry.token")?.map(|p| p.val)) } }; Ok(RegistryConfig { - index: index.map(|p| p.val), - token: token.map(|p| p.val) + index: index, + token: token }) } pub fn registry(config: &Config, token: Option, - index: Option) -> CargoResult<(Registry, SourceId)> { + index: Option, + registry: Option) -> CargoResult<(Registry, SourceId)> { // Parse all configuration options let RegistryConfig { token: token_config, - index: _index_config, - } = registry_configuration(config, index.clone())?; + index: index_config, + } = registry_configuration(config, registry.clone())?; let token = token.or(token_config); - let sid = match index { - Some(index) => SourceId::for_registry(&index.to_url()?)?, - None => SourceId::crates_io(config)?, + let sid = match (index_config, index) { + (Some(index), _) | (None, Some(index)) => SourceId::for_registry(&index.to_url()?)?, + (None, None) => SourceId::crates_io(config)?, }; let api_host = { let mut src = RegistrySource::remote(&sid, config); @@ -309,11 +321,11 @@ pub fn http_timeout(config: &Config) -> CargoResult> { pub fn registry_login(config: &Config, token: String, - host: Option) -> CargoResult<()> { + registry: Option) -> CargoResult<()> { let RegistryConfig { token: old_token, .. - } = registry_configuration(config, host.clone())?; + } = registry_configuration(config, registry.clone())?; if let Some(old_token) = old_token { if old_token == token { @@ -321,7 +333,7 @@ pub fn registry_login(config: &Config, } } - config::save_credentials(config, token, host) + config::save_credentials(config, token, registry) } pub struct OwnersOptions { @@ -331,6 +343,7 @@ pub struct OwnersOptions { pub to_add: Option>, pub to_remove: Option>, pub list: bool, + pub registry: Option, } pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> { @@ -343,8 +356,10 @@ pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> { } }; - let (mut registry, _) = registry(config, opts.token.clone(), - opts.index.clone())?; + let (mut registry, _) = registry(config, + opts.token.clone(), + opts.index.clone(), + opts.registry.clone())?; if let Some(ref v) = opts.to_add { let v = v.iter().map(|s| &s[..]).collect::>(); @@ -387,7 +402,8 @@ pub fn yank(config: &Config, version: Option, token: Option, index: Option, - undo: bool) -> CargoResult<()> { + undo: bool, + reg: Option) -> CargoResult<()> { let name = match krate { Some(name) => name, None => { @@ -401,7 +417,7 @@ pub fn yank(config: &Config, None => bail!("a version must be specified to yank") }; - let (mut registry, _) = registry(config, token, index)?; + let (mut registry, _) = registry(config, token, index, reg)?; if undo { config.shell().status("Unyank", format!("{}:{}", name, version))?; @@ -421,7 +437,8 @@ pub fn yank(config: &Config, pub fn search(query: &str, config: &Config, index: Option, - limit: u8) -> CargoResult<()> { + limit: u8, + reg: Option) -> CargoResult<()> { fn truncate_with_ellipsis(s: &str, max_length: usize) -> String { if s.len() < max_length { s.to_string() @@ -430,7 +447,7 @@ pub fn search(query: &str, } } - let (mut registry, _) = registry(config, None, index)?; + let (mut registry, _) = registry(config, None, index, reg)?; let (crates, total_crates) = registry.search(query, limit).map_err(|e| { CargoError::from(format!("failed to retrieve search results from the registry: {}", e)) })?; diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index ba1f21ce4..47e79baab 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -545,6 +545,14 @@ impl Config { } } + /// Gets the index for a registry. + pub fn get_registry_index(&self, registry: &str) -> CargoResult { + Ok(match self.get_string(&format!("registries.{}.index", registry))? { + Some(index) => index.val, + None => return Err(CargoError::from(format!("No index found for registry: `{}`", registry)).into()), + }) + } + /// Loads credentials config from the credentials file into the ConfigValue object, if present. fn load_credentials(&self, cfg: &mut ConfigValue) -> CargoResult<()> { let home_path = self.home_path.clone().into_path_unlocked(); @@ -886,7 +894,7 @@ fn walk_tree(pwd: &Path, mut walk: F) -> CargoResult<()> pub fn save_credentials(cfg: &Config, token: String, - host: Option) -> CargoResult<()> { + registry: Option) -> CargoResult<()> { let mut file = { cfg.home_path.create_dir()?; cfg.home_path.open_rw(Path::new("credentials"), cfg, @@ -897,10 +905,10 @@ pub fn save_credentials(cfg: &Config, let key = "token".to_string(); let value = ConfigValue::String(token, file.path().to_path_buf()); - if let Some(host) = host { + if let Some(registry) = registry { let mut map = HashMap::new(); map.insert(key, value); - (host, CV::Table(map, file.path().to_path_buf())) + (registry, CV::Table(map, file.path().to_path_buf())) } else { (key, value) } diff --git a/tests/alt-registry.rs b/tests/alt-registry.rs index 9c1072fd7..c6c6b3ee7 100755 --- a/tests/alt-registry.rs +++ b/tests/alt-registry.rs @@ -262,3 +262,50 @@ fn alt_registry_and_crates_io_deps() { [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs")) } + +#[test] +fn block_publish_due_to_no_token() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + "#) + .file("src/main.rs", "fn main() {}") + .build(); + + // Setup the registry by publishing a package + Package::new("bar", "0.0.1").alternative(true).publish(); + + // Now perform the actual publish + assert_that(p.cargo("publish").masquerade_as_nightly_cargo() + .arg("--registry").arg("alternative"), + execs().with_status(101)); +} + +#[test] +fn publish_to_alt_registry() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + "#) + .file("src/main.rs", "fn main() {}") + .build(); + + // Setup the registry by publishing a package + Package::new("bar", "0.0.1").alternative(true).publish(); + + // Login so that we have the token available + assert_that(p.cargo("login") + .arg("--registry").arg("alternative").arg("TOKEN"), + execs().with_status(0)); + + // Now perform the actual publish + assert_that(p.cargo("publish").masquerade_as_nightly_cargo() + .arg("--registry").arg("alternative"), + execs().with_status(0)); +} diff --git a/tests/login.rs b/tests/login.rs index 7153002e6..5e6f56192 100755 --- a/tests/login.rs +++ b/tests/login.rs @@ -16,9 +16,16 @@ use cargo::core::Shell; use hamcrest::{assert_that, existing_file, is_not}; const TOKEN: &str = "test-token"; +const ORIGINAL_TOKEN: &str = "api-token"; const CONFIG_FILE: &str = r#" [registry] token = "api-token" + + [registries.test-reg] + index = "dummy_index" + + [registries.test.reg] + index = "dummy_index" "#; fn setup_old_credentials() { @@ -31,35 +38,47 @@ fn setup_new_credentials() { let config = cargo_home().join("credentials"); t!(fs::create_dir_all(config.parent().unwrap())); t!(t!(File::create(&config)).write_all(format!(r#" - token = "api-token" - - ["{registry}"] - token = "api-token" - "#, registry = registry().to_string()) + token = "{token}" + "#, token = ORIGINAL_TOKEN) .as_bytes())); } -fn check_host_token(mut toml: toml::Value, host_key: &str) -> bool { - for &key in [host_key, "token"].into_iter() { - if key.is_empty() { - continue - } +fn check_token(expected_token: &str, registry: Option<&str>) -> bool { + + let credentials = cargo_home().join("credentials"); + assert_that(&credentials, existing_file()); - match toml { - toml::Value::Table(table) => { - if let Some(v) = table.get(key) { - toml = v.clone(); - } else { - return false; + let mut contents = String::new(); + File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap(); + let toml: toml::Value = contents.parse().unwrap(); + + let token = match (registry, toml) { + // A registry has been provided, so check that the token exists in a + // table for the registry. + (Some(registry), toml::Value::Table(table)) => { + table.get(registry).and_then(|registry_table| { + match registry_table.get("token") { + Some(&toml::Value::String(ref token)) => Some(token.as_str().to_string()), + _ => None, + } + }) + }, + // There is no registry provided, so check the global token instead. + (None, toml::Value::Table(table)) => { + table.get("token").and_then(|v| { + match v { + &toml::Value::String(ref token) => Some(token.as_str().to_string()), + _ => None, } - } - _ => break, + }) } - } + _ => None + }; - match toml { - toml::Value::String(token) => (&token == TOKEN), - _ => false, + if let Some(token_val) = token { + token_val == expected_token + } else { + false } } @@ -78,12 +97,8 @@ fn login_with_old_credentials() { File::open(&config).unwrap().read_to_string(&mut contents).unwrap(); assert_eq!(CONFIG_FILE, contents); - let credentials = cargo_home().join("credentials"); - assert_that(&credentials, existing_file()); - - contents.clear(); - File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap(); - assert!(check_host_token(contents.parse().unwrap(), ®istry().to_string())); + // Ensure that we get the new token for the registry + assert!(check_token(TOKEN, None)); } #[test] @@ -97,12 +112,8 @@ fn login_with_new_credentials() { let config = cargo_home().join("config"); assert_that(&config, is_not(existing_file())); - let credentials = cargo_home().join("credentials"); - assert_that(&credentials, existing_file()); - - let mut contents = String::new(); - File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap(); - assert!(check_host_token(contents.parse().unwrap(), ®istry().to_string())); + // Ensure that we get the new token for the registry + assert!(check_token(TOKEN, None)); } #[test] @@ -116,20 +127,12 @@ fn login_without_credentials() { assert_that(cargo_process().arg("login") .arg("--host").arg(registry().to_string()).arg(TOKEN), execs().with_status(0)); - assert_that(cargo_process().arg("login").arg(TOKEN), - execs().with_status(0)); let config = cargo_home().join("config"); assert_that(&config, is_not(existing_file())); - let credentials = cargo_home().join("credentials"); - assert_that(&credentials, existing_file()); - - let mut contents = String::new(); - File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap(); - let toml: toml::Value = contents.parse().unwrap(); - assert!(check_host_token(toml.clone(), ®istry().to_string())); - assert!(check_host_token(toml, "")); + // Ensure that we get the new token for the registry + assert!(check_token(TOKEN, None)); } #[test] @@ -137,9 +140,6 @@ fn new_credentials_is_used_instead_old() { setup_old_credentials(); setup_new_credentials(); - assert_that(cargo_process().arg("login").arg(TOKEN), - execs().with_status(0)); - assert_that(cargo_process().arg("login") .arg("--host").arg(registry().to_string()).arg(TOKEN), execs().with_status(0)); @@ -148,8 +148,40 @@ fn new_credentials_is_used_instead_old() { let token = config.get_string("registry.token").unwrap().map(|p| p.val); assert_eq!(token.unwrap(), TOKEN); +} + +#[test] +fn registry_credentials() { + setup_old_credentials(); + setup_new_credentials(); + + let reg = "test-reg"; + + assert_that(cargo_process().arg("login") + .arg("--registry").arg(reg).arg(TOKEN), + execs().with_status(0)); + + // Ensure that we have not updated the default token + assert!(check_token(ORIGINAL_TOKEN, None)); + + // Also ensure that we get the new token for the registry + assert!(check_token(TOKEN, Some(reg))); +} + +#[test] +fn registry_credentials_with_dots() { + setup_old_credentials(); + setup_new_credentials(); + + let reg = "test.reg"; + + assert_that(cargo_process().arg("login") + .arg("--registry").arg(reg).arg(TOKEN), + execs().with_status(0)); + + // Ensure that we have not updated the default token + assert!(check_token(ORIGINAL_TOKEN, None)); - let token_host = config.get_string(&format!(r#"registry.{}.token"#, registry().to_string())) - .unwrap().map(|p| p.val); - assert_eq!(token_host.unwrap(), TOKEN); + // Also ensure that we get the new token for the registry + assert!(check_token(TOKEN, Some(reg))); } -- 2.30.2