Credentials for multiple hosts.
authorEvgen Druzhynin <evgen.druzhynin@gmail.com>
Thu, 22 Jun 2017 14:13:35 +0000 (17:13 +0300)
committerEvgen Druzhynin <evgen.druzhynin@gmail.com>
Thu, 22 Jun 2017 14:21:33 +0000 (17:21 +0300)
Now `cargo login` stores a token per host.
If the host parameter is omitted cargo stores a token as default, i.e.
as a token for crates.io.

src/bin/login.rs
src/cargo/core/source.rs
src/cargo/ops/registry.rs
src/cargo/util/config.rs
tests/login.rs
tests/publish.rs

index a9d6b1a710ae5b1922cc48376c7ea7c8e3b36274..468c8453c79d04a12d16721b77726c4449890a8a 100644 (file)
@@ -40,26 +40,29 @@ pub fn execute(options: Options, config: &Config) -> CliResult {
                      &options.flag_color,
                      options.flag_frozen,
                      options.flag_locked)?;
-    let token = match options.arg_token.clone() {
+    let token = match options.arg_token {
         Some(token) => token,
         None => {
-            let src = SourceId::crates_io(config)?;
-            let mut src = RegistrySource::remote(&src, config);
-            src.update()?;
-            let config = src.config()?.unwrap();
-            let host = options.flag_host.clone().unwrap_or(config.api);
+            let host = match options.flag_host {
+                Some(ref host) => host.clone(),
+                None => {
+                    let src = SourceId::crates_io(config)?;
+                    let mut src = RegistrySource::remote(&src, config);
+                    src.update()?;
+                    src.config()?.unwrap().api
+                }
+            };
+
             println!("please visit {}me and paste the API Token below", host);
             let mut line = String::new();
             let input = io::stdin();
             input.lock().read_line(&mut line).chain_err(|| {
                 "failed to read stdin"
             })?;
-            line
+            line.trim().to_string()
         }
     };
 
-    let token = token.trim().to_string();
-    ops::registry_login(config, token)?;
+    ops::registry_login(config, token, options.flag_host)?;
     Ok(())
 }
-
index e7a3ce4a2e84bc6f7cdfb7922ce9520570443466..7487b8e9c9b8a379b87e3a3abe6fd3758d58a1c5 100644 (file)
@@ -206,7 +206,7 @@ impl SourceId {
     /// This is the main cargo registry by default, but it can be overridden in
     /// a `.cargo/config`.
     pub fn crates_io(config: &Config) -> CargoResult<SourceId> {
-        let cfg = ops::registry_configuration(config)?;
+        let cfg = ops::registry_configuration(config, None)?;
         let url = if let Some(ref index) = cfg.index {
             static WARNED: AtomicBool = ATOMIC_BOOL_INIT;
             if !WARNED.swap(true, SeqCst) {
index 35ec30257c223cc2071a366b84c49a303ae99a14..cc63cb7530c66d4601fcfce8a7d240b89b65476d 100644 (file)
@@ -16,7 +16,7 @@ use core::dependency::Kind;
 use core::manifest::ManifestMetadata;
 use ops;
 use sources::{RegistrySource};
-use util::config::{self, Config};
+use util::config::{self, Config, Value, Definition};
 use util::paths;
 use util::ToUrl;
 use util::errors::{CargoError, CargoResult, CargoResultExt};
@@ -176,10 +176,24 @@ fn transmit(config: &Config,
     }
 }
 
-pub fn registry_configuration(config: &Config) -> CargoResult<RegistryConfig> {
-    let index = config.get_string("registry.index")?.map(|p| p.val);
-    let token = config.get_string("registry.token")?.map(|p| p.val);
-    Ok(RegistryConfig { index: index, token: token })
+pub fn registry_configuration(config: &Config,
+                              host: Option<String>) -> CargoResult<RegistryConfig> {
+    let (index, token) = match host {
+        Some(host) => {
+            (Some(Value { val: host.clone(), definition: Definition::Environment }),
+             config.get_string(&format!("registry.{}.token", host))?)
+        }
+        None => {
+            // Checking out for default index and token
+            (config.get_string("registry.index")?,
+             config.get_string("registry.token")?)
+        }
+    };
+
+    Ok(RegistryConfig {
+        index: index.map(|p| p.val),
+        token: token.map(|p| p.val)
+    })
 }
 
 pub fn registry(config: &Config,
@@ -189,7 +203,7 @@ pub fn registry(config: &Config,
     let RegistryConfig {
         token: token_config,
         index: _index_config,
-    } = registry_configuration(config)?;
+    } = registry_configuration(config, index.clone())?;
     let token = token.or(token_config);
     let sid = match index {
         Some(index) => SourceId::for_registry(&index.to_url()?),
@@ -280,15 +294,21 @@ pub fn http_timeout(config: &Config) -> CargoResult<Option<i64>> {
     Ok(env::var("HTTP_TIMEOUT").ok().and_then(|s| s.parse().ok()))
 }
 
-pub fn registry_login(config: &Config, token: String) -> CargoResult<()> {
-    let RegistryConfig { index: _, token: old_token } = registry_configuration(config)?;
+pub fn registry_login(config: &Config,
+                      token: String,
+                      host: Option<String>) -> CargoResult<()> {
+    let RegistryConfig {
+        index: _,
+        token: old_token
+    } = registry_configuration(config, host.clone())?;
+
     if let Some(old_token) = old_token {
         if old_token == token {
             return Ok(());
         }
     }
 
-    config::save_credentials(config, token)
+    config::save_credentials(config, token, host)
 }
 
 pub struct OwnersOptions {
index 2df174740df483a0109c7305201225a8eae1afe2..dde70544ec566dddcf2c62d194d0e91e778daec4 100644 (file)
@@ -835,23 +835,36 @@ fn walk_tree<F>(pwd: &Path, mut walk: F) -> CargoResult<()>
 }
 
 pub fn save_credentials(cfg: &Config,
-                       token: String) -> CargoResult<()> {
+                        token: String,
+                        host: Option<String>) -> CargoResult<()> {
     let mut file = {
         cfg.home_path.create_dir()?;
         cfg.home_path.open_rw(Path::new("credentials"), cfg,
-                                   "credentials' config file")?
+                              "credentials' config file")?
+    };
+
+    let (key, value) = {
+        let key = "token".to_string();
+        let value = ConfigValue::String(token, file.path().to_path_buf());
+
+        if let Some(host) = host {
+            let mut map = HashMap::new();
+            map.insert(key, value);
+            (host, CV::Table(map, file.path().to_path_buf()))
+        } else {
+            (key, value)
+        }
     };
 
     let mut contents = String::new();
     file.read_to_string(&mut contents).chain_err(|| {
-        format!("failed to read configuration file `{}`",
-                      file.path().display())
+        format!("failed to read configuration file `{}`", file.path().display())
     })?;
+
     let mut toml = cargo_toml::parse(&contents, file.path(), cfg)?;
     toml.as_table_mut()
         .unwrap()
-        .insert("token".to_string(),
-                ConfigValue::String(token, file.path().to_path_buf()).into_toml());
+        .insert(key, value.into_toml());
 
     let contents = toml.to_string();
     file.seek(SeekFrom::Start(0))?;
index d3b9b601b2feb8327a1a8228976472ce295e348c..7907da157636efb431e89804f38fbe0ab148241b 100644 (file)
@@ -30,20 +30,35 @@ fn setup_old_credentials() {
 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(br#"
+    t!(t!(File::create(&config)).write_all(format!(r#"
         token = "api-token"
-    "#));
+
+        ["{registry}"]
+        token = "api-token"
+    "#, registry = registry().to_string())
+    .as_bytes()));
 }
 
-fn check_host_token(toml: toml::Value) -> bool {
+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
+        }
+
+        match toml {
+            toml::Value::Table(table) => {
+                if let Some(v) = table.get(key) {
+                    toml = v.clone();
+                } else {
+                    return false;
+                }
+            }
+            _ => break,
+        }
+    }
+
     match toml {
-        toml::Value::Table(table) => match table.get("token") {
-            Some(v) => match v {
-                &toml::Value::String(ref token) => (token.as_str() == TOKEN),
-                _ => false,
-            },
-            None => false,
-        },
+        toml::Value::String(token) => (&token == TOKEN),
         _ => false,
     }
 }
@@ -68,7 +83,7 @@ fn login_with_old_credentials() {
 
     contents.clear();
     File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap();
-    assert!(check_host_token(contents.parse().unwrap()));
+    assert!(check_host_token(contents.parse().unwrap(), &registry().to_string()));
 }
 
 #[test]
@@ -87,7 +102,7 @@ fn login_with_new_credentials() {
 
     let mut contents = String::new();
     File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap();
-    assert!(check_host_token(contents.parse().unwrap()));
+    assert!(check_host_token(contents.parse().unwrap(), &registry().to_string()));
 }
 
 #[test]
@@ -101,6 +116,8 @@ 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()));
@@ -110,7 +127,9 @@ fn login_without_credentials() {
 
     let mut contents = String::new();
     File::open(&credentials).unwrap().read_to_string(&mut contents).unwrap();
-    assert!(check_host_token(contents.parse().unwrap()));
+    let toml: toml::Value = contents.parse().unwrap();
+    assert!(check_host_token(toml.clone(), &registry().to_string()));
+    assert!(check_host_token(toml, ""));
 }
 
 #[test]
@@ -118,11 +137,19 @@ 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));
 
     let config = Config::new(Shell::new(), cargo_home(), cargo_home());
+
     let token = config.get_string("registry.token").unwrap().map(|p| p.val);
     assert!(token.unwrap() == TOKEN);
+
+    let token_host = config.get_string(&format!(r#"registry.{}.token"#, registry().to_string()))
+                       .unwrap().map(|p| p.val);
+    assert!(token_host.unwrap() == TOKEN);
 }
index 968281f6219a486c8c1f6d6829f24c7d32f75914..67ab05e09855056597a203c336a8f1a28be954d9 100644 (file)
@@ -13,6 +13,7 @@ use std::path::PathBuf;
 use cargotest::support::git::repo;
 use cargotest::support::paths;
 use cargotest::support::{project, execs};
+use cargotest::install::cargo_home;
 use flate2::read::GzDecoder;
 use hamcrest::assert_that;
 use tar::Archive;
@@ -24,14 +25,21 @@ fn upload_path() -> PathBuf { paths::root().join("upload") }
 fn upload() -> Url { Url::from_file_path(&*upload_path()).ok().unwrap() }
 
 fn setup() {
-    let config = paths::root().join(".cargo/config");
+    let config = cargo_home().join("config");
     t!(fs::create_dir_all(config.parent().unwrap()));
     t!(t!(File::create(&config)).write_all(br#"
         [registry]
-            token = "api-token"
+        token = "api-token"
     "#));
     t!(fs::create_dir_all(&upload_path().join("api/v1/crates")));
 
+    let credentials = cargo_home().join("credentials");
+    t!(t!(File::create(&credentials)).write_all(format!(r#"
+        ["{registry}"]
+        token = "api-token"
+    "#, registry = registry().to_string())
+    .as_bytes()));
+
     repo(&registry_path())
         .file("config.json", &format!(r#"{{
             "dl": "{0}",