Skip to content

Commit

Permalink
Credentials for multiple hosts.
Browse files Browse the repository at this point in the history
Now cargo looks for credentials and stores them in $CARGO_HOME/credentials.
If credentials for requested host are not found cargo will try to get them
from $CARGO_HOME/config, but it's temporary behavior. It should be disabled
after users start to use a new config file).
  • Loading branch information
fa7ca7 committed Jun 1, 2017
1 parent 903be77 commit 29960d2
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 62 deletions.
22 changes: 13 additions & 9 deletions src/bin/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,30 @@ pub fn execute(options: Options, config: &Config) -> CliResult {
&options.flag_color,
options.flag_frozen,
options.flag_locked)?;
let token = match options.arg_token.clone() {
Some(token) => token,

let host = match options.flag_host {
Some(host) => host,
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);
println!("please visit {}me and paste the API Token below", host);
src.config()?.unwrap().api
}
};

let token = match options.arg_token {
Some(token) => token,
None => {
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, host)?;
Ok(())
}

2 changes: 1 addition & 1 deletion src/cargo/core/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,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, "https://crates.io")?;
let url = if let Some(ref index) = cfg.index {
static WARNED: AtomicBool = ATOMIC_BOOL_INIT;
if !WARNED.swap(true, SeqCst) {
Expand Down
60 changes: 48 additions & 12 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,32 +177,52 @@ 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: &str) -> CargoResult<RegistryConfig> {
let mut index = None;
let mut token = None;

if !host.is_empty() {
index = config.get_string(&format!("registry.{}.index", host))?;
token = config.get_string(&format!("registry.{}.token", host))?;
}

// FIXME: Checking out for the values which were picked up from
// $CARGO_HOME/config. This section should be removed after all the users
// start to use $CARGO_HOME/credentials for token configuration.
if index.is_none() && token.is_none() {
index = config.get_string("registry.index")?;
token = config.get_string("registry.token")?;
}

Ok(RegistryConfig {
index: index.map(|p| p.val),
token: token.map(|p| p.val)
})
}

pub fn registry(config: &Config,
token: Option<String>,
index: Option<String>) -> CargoResult<(Registry, SourceId)> {
// Parse all configuration options
let RegistryConfig {
token: token_config,
index: _index_config,
} = registry_configuration(config)?;
let token = token.or(token_config);
let sid = match index {
Some(index) => SourceId::for_registry(&index.to_url()?),
None => SourceId::crates_io(config)?,
};

let api_host = {
let mut src = RegistrySource::remote(&sid, config);
src.update().chain_err(|| {
format!("failed to update {}", sid)
})?;
(src.config()?).unwrap().api
};

let RegistryConfig {
token: token_config,
index: _index_config,
} = registry_configuration(config, &api_host)?;
let token = token.or(token_config);
let handle = http_handle(config)?;
Ok((Registry::new_handle(api_host, token, handle), sid))
}
Expand Down Expand Up @@ -281,15 +301,31 @@ 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: String) -> CargoResult<()> {
let host = match host.to_url()?.host_str() {
Some(h) => h.to_string(),
None => host,
};
let host: String = host.chars()
.map(|x| match x {
'\\'|'/'|':'|'.'|'-' => '_',
_ => x,
}).collect();

let RegistryConfig {
index: _,
token: old_token
} = registry_configuration(config, &host)?;

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 {
Expand Down
113 changes: 77 additions & 36 deletions src/cargo/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::collections::HashSet;
use std::env;
use std::fmt;
use std::fs::{self, File};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::mem;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -429,28 +430,52 @@ impl Config {
Ok(())
}).chain_err(|| "Couldn't load Cargo configuration")?;

let mut map = match cfg {
CV::Table(map, _) => map,
self.load_credentials(&mut cfg)?;
match cfg {
CV::Table(map, _) => Ok(map),
_ => unreachable!(),
};
}
}

fn load_credentials(&self, cfg: &mut ConfigValue) -> CargoResult<()> {
let home_path = self.home_path.clone().into_path_unlocked();
let token = load_credentials(&home_path)?;
if let Some(t) = token {
if !t.is_empty() {
let mut registry = map.entry("registry".into())
.or_insert(CV::Table(HashMap::new(), PathBuf::from(".")));
match *registry {
CV::Table(ref mut m, _) => {
m.insert("token".into(),
CV::String(t, home_path.join("credentials")));
}
_ => unreachable!(),
}
}
let credentials = home_path.join("credentials");
if !fs::metadata(&credentials).is_ok() {
return Ok(());
}

Ok(map)
let mut contents = String::new();
let mut file = File::open(&credentials)?;
file.read_to_string(&mut contents).chain_err(|| {
format!("failed to read configuration file `{}`",
credentials.display())
})?;

let toml = cargo_toml::parse(&contents,
&credentials,
self).chain_err(|| {
format!("could not parse TOML configuration in `{}`",
credentials.display())
})?;
let value = CV::from_toml(&credentials, toml).chain_err(|| {
format!("failed to load TOML configuration from `{}`",
credentials.display())
})?;

let mut cfg = match *cfg {
CV::Table(ref mut map, _) => map,
_ => unreachable!(),
};

let mut registry = cfg.entry("registry".into())
.or_insert(CV::Table(HashMap::new(),
PathBuf::from(".")));
registry.merge(value).chain_err(|| {
format!("failed to merge configuration at `{}`",
credentials.display())
})?;

Ok(())
}

/// Look for a path for `tool` in an environment variable or config path, but return `None`
Expand Down Expand Up @@ -567,6 +592,21 @@ impl ConfigValue {
}
}

fn into_toml(self) -> toml::Value {
match self {
CV::Boolean(s, _) => toml::Value::Boolean(s),
CV::String(s, _) => toml::Value::String(s),
CV::Integer(i, _) => toml::Value::Integer(i),
CV::List(l, _) => toml::Value::Array(l
.into_iter()
.map(|(s, _)| toml::Value::String(s))
.collect()),
CV::Table(l, _) => toml::Value::Table(l.into_iter()
.map(|(k, v)| (k, v.into_toml()))
.collect()),
}
}

fn merge(&mut self, from: ConfigValue) -> CargoResult<()> {
match (self, from) {
(&mut CV::String(..), CV::String(..)) |
Expand Down Expand Up @@ -767,15 +807,32 @@ fn walk_tree<F>(pwd: &Path, mut walk: F) -> CargoResult<()>
}

pub fn save_credentials(cfg: &Config,
token: String) -> CargoResult<()> {
token: String,
host: String) -> CargoResult<()> {
let mut file = {
cfg.home_path.create_dir()?;
cfg.home_path.open_rw(Path::new("credentials"), cfg,
"credentials' config file")?
};

file.write_all(token.as_bytes())?;
file.file().set_len(token.len() as u64)?;
let mut map = HashMap::new();
map.insert("token".to_string(),
ConfigValue::String(token, file.path().to_path_buf()));

let mut contents = String::new();
file.read_to_string(&mut contents).chain_err(|| {
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(host, CV::Table(map, file.path().to_path_buf()).into_toml());

let contents = toml.to_string();
file.seek(SeekFrom::Start(0))?;
file.write_all(contents.as_bytes())?;
file.file().set_len(contents.len() as u64)?;
set_permissions(file.file(), 0o600)?;

return Ok(());
Expand All @@ -796,19 +853,3 @@ pub fn save_credentials(cfg: &Config,
Ok(())
}
}

fn load_credentials(home: &PathBuf) -> CargoResult<Option<String>> {
let credentials = home.join("credentials");
if !fs::metadata(&credentials).is_ok() {
return Ok(None);
}

let mut token = String::new();
let mut file = File::open(&credentials)?;
file.read_to_string(&mut token).chain_err(|| {
format!("failed to read configuration file `{}`",
credentials.display())
})?;

Ok(Some(token.trim().into()))
}
Loading

0 comments on commit 29960d2

Please sign in to comment.