Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move API token into the separate file. #3978

Merged
merged 6 commits into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
75 changes: 53 additions & 22 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::iter::repeat;
use std::path::PathBuf;
use std::time::Duration;

use curl::easy::{Easy, SslOpt};
Expand All @@ -19,10 +17,9 @@ use core::dependency::Kind;
use core::manifest::ManifestMetadata;
use ops;
use sources::{RegistrySource};
use util::config;
use util::config::{self, Config};
use util::paths;
use util::ToUrl;
use util::config::{Config, ConfigValue, Location};
use util::errors::{CargoError, CargoResult, CargoResultExt};
use util::important_paths::find_root_manifest_for_wd;

Expand Down Expand Up @@ -180,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 @@ -284,17 +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: _ } = registry_configuration(config)?;
let mut map = HashMap::new();
let p = config.cwd().to_path_buf();
if let Some(index) = index {
map.insert("index".to_string(), ConfigValue::String(index, p.clone()));
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(());
}
}
map.insert("token".to_string(), ConfigValue::String(token, p));

config::set_config(config, Location::Global, "registry",
ConfigValue::Table(map, PathBuf::from(".")))
config::save_credentials(config, token, host)
}

pub struct OwnersOptions {
Expand Down
143 changes: 99 additions & 44 deletions src/cargo/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,12 @@ impl Config {
pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
let mut cfg = CV::Table(HashMap::new(), PathBuf::from("."));

walk_tree(&self.cwd, |mut file, path| {
walk_tree(&self.cwd, |path| {
let mut contents = String::new();
let mut file = File::open(&path)?;
file.read_to_string(&mut contents).chain_err(|| {
format!("failed to read configuration file `{}`",
path.display())
path.display())
})?;
let toml = cargo_toml::parse(&contents,
&path,
Expand All @@ -429,13 +430,54 @@ impl Config {
Ok(())
}).chain_err(|| "Couldn't load Cargo configuration")?;


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 credentials = home_path.join("credentials");
if !fs::metadata(&credentials).is_ok() {
return Ok(());
}

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`
/// if it's not present.
fn maybe_get_tool(&self, tool: &str) -> CargoResult<Option<PathBuf>> {
Expand Down Expand Up @@ -550,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 @@ -651,21 +708,6 @@ impl ConfigValue {
wanted, self.desc(), key,
self.definition_path().display()).into())
}

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()),
}
}
}

impl Definition {
Expand Down Expand Up @@ -737,17 +779,14 @@ pub fn homedir(cwd: &Path) -> Option<PathBuf> {
}

fn walk_tree<F>(pwd: &Path, mut walk: F) -> CargoResult<()>
where F: FnMut(File, &Path) -> CargoResult<()>
where F: FnMut(&Path) -> CargoResult<()>
{
let mut stash: HashSet<PathBuf> = HashSet::new();

for current in paths::ancestors(pwd) {
let possible = current.join(".cargo").join("config");
if fs::metadata(&possible).is_ok() {
let file = File::open(&possible)?;

walk(file, &possible)?;

walk(&possible)?;
stash.insert(possible);
}
}
Expand All @@ -761,40 +800,56 @@ fn walk_tree<F>(pwd: &Path, mut walk: F) -> CargoResult<()>
})?;
let config = home.join("config");
if !stash.contains(&config) && fs::metadata(&config).is_ok() {
let file = File::open(&config)?;
walk(file, &config)?;
walk(&config)?;
}

Ok(())
}

pub fn set_config(cfg: &Config,
loc: Location,
key: &str,
value: ConfigValue) -> CargoResult<()> {
// TODO: There are a number of drawbacks here
//
// 1. Project is unimplemented
// 2. This blows away all comments in a file
// 3. This blows away the previous ordering of a file.
let mut file = match loc {
Location::Global => {
cfg.home_path.create_dir()?;
cfg.home_path.open_rw(Path::new("config"), cfg,
"the global config file")?
}
Location::Project => unimplemented!(),
pub fn save_credentials(cfg: &Config,
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")?
};

let mut map = HashMap::new();
map.insert("token".to_string(),
ConfigValue::String(token, file.path().to_path_buf()));

let mut contents = String::new();
let _ = file.read_to_string(&mut contents);
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(key.to_string(), value.into_toml());
.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)?;
Ok(())
set_permissions(file.file(), 0o600)?;

return Ok(());

#[cfg(unix)]
fn set_permissions(file: & File, mode: u32) -> CargoResult<()> {
use std::os::unix::fs::PermissionsExt;

let mut perms = file.metadata()?.permissions();
perms.set_mode(mode);
file.set_permissions(perms)?;
Ok(())
}

#[cfg(not(unix))]
#[allow(unused)]
fn set_permissions(file: & File, mode: u32) -> CargoResult<()> {
Ok(())
}
}
3 changes: 2 additions & 1 deletion src/doc/crates-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ $ cargo login abcdefghijklmnopqrstuvwxyz012345
```

This command will inform Cargo of your API token and store it locally in your
`~/.cargo/config`. Note that this token is a **secret** and should not be shared
`~/.cargo/credentials` (previously it was `~/.cargo/config`).
Note that this token is a **secret** and should not be shared
with anyone else. If it leaks for any reason, you should regenerate it
immediately.

Expand Down
Loading