#![deny(rust_2018_idioms)]
#![cfg(feature = "dist-client")]
use fs_err as fs;
use std::io::{self, Read, Write};
use std::net::TcpStream;
use std::path::Path;
use std::process::{Command, Output, Stdio};
use std::thread;
use std::time::{Duration, Instant};
use thirtyfour_sync::prelude::*;
const LOCAL_AUTH_BASE_URL: &str = "http://localhost:12731/";
const USERNAME_SELECTOR: &str = ".auth0-lock-input-email .auth0-lock-input";
const PASSWORD_SELECTOR: &str = ".auth0-lock-input-password .auth0-lock-input";
const LOGIN_SELECTOR: &str = ".auth0-lock-submit";
const BROWSER_RETRY_WAIT: Duration = Duration::from_secs(1);
const BROWSER_MAX_WAIT: Duration = Duration::from_secs(10);
const TEST_USERNAME: &str = "test@example.com";
const TEST_PASSWORD: &str = "test1234";
fn generate_code_grant_pkce_auth_config() -> sccache::config::DistAuth {
sccache::config::DistAuth::Oauth2CodeGrantPKCE {
client_id: "Xmbl6zRW1o1tJ5LQOz0p65NwY47aMO7A".to_owned(),
auth_url:
"https://sccache-test.auth0.com/authorize?audience=https://sccache-dist-test-api/"
.to_owned(),
token_url: "https://sccache-test.auth0.com/oauth/token".to_owned(),
}
}
fn generate_implicit_auth_config() -> sccache::config::DistAuth {
sccache::config::DistAuth::Oauth2Implicit {
client_id: "TTborSAyjBnSi1W11201ZzNu9gSg63bq".to_owned(),
auth_url:
"https://sccache-test.auth0.com/authorize?audience=https://sccache-dist-test-api/"
.to_owned(),
}
}
fn config_with_dist_auth(
tmpdir: &Path,
auth_config: sccache::config::DistAuth,
) -> sccache::config::FileConfig {
sccache::config::FileConfig {
cache: Default::default(),
dist: sccache::config::DistConfig {
auth: auth_config,
scheduler_url: None,
cache_dir: tmpdir.join("unused-cache"),
toolchains: vec![],
toolchain_cache_size: 0,
rewrite_includes_only: true,
},
server_startup_timeout_ms: None,
basedirs: vec![],
}
}
fn sccache_command() -> Command {
Command::new(env!("CARGO_BIN_EXE_sccache"))
}
fn retry<F: FnMut() -> Option<T>, T>(interval: Duration, until: Duration, mut f: F) -> Option<T> {
let start = Instant::now();
while start.elapsed() < until {
if let Some(res) = f() {
return Some(res);
} else {
thread::sleep(interval)
}
}
None
}
trait DriverExt {
fn wait_for_element(&self, selector: &str) -> Result<(), ()>;
fn wait_on_url<F: Fn(&str) -> bool>(&self, condition: F) -> Result<(), ()>;
}
impl DriverExt for WebDriver {
fn wait_for_element(&self, selector: &str) -> Result<(), ()> {
retry(BROWSER_RETRY_WAIT, BROWSER_MAX_WAIT, || {
self.find_element(By::Css(selector)).ok()
})
.map(|_| ())
.ok_or(())
}
fn wait_on_url<F: Fn(&str) -> bool>(&self, condition: F) -> Result<(), ()> {
let start = Instant::now();
while start.elapsed() < BROWSER_MAX_WAIT {
match self.current_url() {
Ok(ref url) if condition(url) => return Ok(()),
Ok(_) | Err(_) => thread::sleep(BROWSER_RETRY_WAIT),
}
}
Err(())
}
}
fn auth0_login(driver: &WebDriver, email: &str, password: &str) {
driver.wait_for_element(USERNAME_SELECTOR).unwrap();
thread::sleep(Duration::from_secs(1)); driver
.find_element(By::Css(USERNAME_SELECTOR))
.unwrap()
.send_keys(email)
.unwrap();
driver
.find_element(By::Css(PASSWORD_SELECTOR))
.unwrap()
.send_keys(password)
.unwrap();
driver
.find_element(By::Css(LOGIN_SELECTOR))
.unwrap()
.click()
.unwrap();
}
struct SeleniumContainer {
cid: String,
}
fn check_output(output: &Output) {
if !output.status.success() {
println!(
"===========\n{}\n==========\n\n\n\n=========\n{}\n===============\n\n\n",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
panic!()
}
}
impl SeleniumContainer {
fn new() -> Self {
let cid = {
let args = &[
"run",
"--rm",
"-d",
"-v",
"/dev/shm:/dev/shm",
"--net",
"host",
"-e",
"SE_OPTS=-debug",
"selenium/standalone-chrome-debug:3.14.0",
];
let output = Command::new("docker").args(args).output().unwrap();
check_output(&output);
let stdout = String::from_utf8(output.stdout).unwrap();
stdout.trim().to_owned()
};
SeleniumContainer { cid }
}
}
impl Drop for SeleniumContainer {
fn drop(&mut self) {
let Output { stdout, stderr, .. } = Command::new("docker")
.args(["logs", &self.cid])
.output()
.unwrap();
let output = Command::new("docker")
.args(["kill", &self.cid])
.output()
.unwrap();
println!(
"====\n> selenium container <:\n## STDOUT\n{}\n\n## STDERR\n{}\n====",
String::from_utf8_lossy(&stdout),
String::from_utf8_lossy(&stderr)
);
check_output(&output)
}
}
#[test]
#[cfg_attr(
not(all(target_os = "linux", target_arch = "x86_64", feature = "dist-tests")),
ignore
)]
fn test_auth() {
let client_auth_port = sccache::dist::client_auth::VALID_PORTS[0];
assert_eq!(
TcpStream::connect(("localhost", client_auth_port))
.unwrap_err()
.kind(),
io::ErrorKind::ConnectionRefused
);
assert_eq!(
TcpStream::connect(("localhost", 4444)).unwrap_err().kind(),
io::ErrorKind::ConnectionRefused
);
let _selenium = SeleniumContainer::new();
thread::sleep(Duration::from_secs(3));
println!("Testing code grant pkce auth");
test_auth_with_config(generate_code_grant_pkce_auth_config());
println!("Testing implicit auth");
test_auth_with_config(generate_implicit_auth_config());
}
fn test_auth_with_config(dist_auth: sccache::config::DistAuth) {
let conf_dir = tempfile::Builder::new()
.prefix("sccache-test-conf")
.tempdir()
.unwrap();
let sccache_config = config_with_dist_auth(conf_dir.path(), dist_auth);
assert!(sccache_config.basedirs.is_empty());
let sccache_config_path = conf_dir.path().join("sccache-config.json");
fs::File::create(&sccache_config_path)
.unwrap()
.write_all(&serde_json::to_vec(&sccache_config).unwrap())
.unwrap();
let sccache_cached_config_path = conf_dir.path().join("sccache-cached-config");
let envs = vec![
("SCCACHE_LOG", "sccache=trace".into()),
("SCCACHE_CONF", sccache_config_path.into_os_string()),
(
"SCCACHE_CACHED_CONF",
sccache_cached_config_path.clone().into_os_string(),
),
];
println!("Starting sccache --dist-auth");
let mut sccache_process = sccache_command()
.arg("--dist-auth")
.envs(envs)
.stdin(Stdio::null())
.spawn()
.unwrap();
thread::sleep(Duration::from_secs(1)); println!("Beginning in-browser auth");
login();
let status = retry(Duration::from_secs(1), Duration::from_secs(10), || {
sccache_process.try_wait().unwrap()
});
match status {
Some(s) => assert!(s.success()),
None => {
sccache_process.kill().unwrap();
panic!("Waited too long for process to exit")
}
}
println!("Validating cached config");
let mut cached_config_string = String::new();
fs::File::open(sccache_cached_config_path)
.unwrap()
.read_to_string(&mut cached_config_string)
.unwrap();
let cached_config: sccache::config::CachedFileConfig =
toml::from_str(&cached_config_string).unwrap();
assert_eq!(cached_config.dist.auth_tokens.len(), 1);
}
fn login() {
let caps = DesiredCapabilities::chrome();
let driver = WebDriver::new("http://localhost:4444/wd/hub", &caps).unwrap();
println!("Started browser session");
driver.get(LOCAL_AUTH_BASE_URL).unwrap();
driver
.wait_on_url(|url| url != LOCAL_AUTH_BASE_URL)
.unwrap();
auth0_login(&driver, TEST_USERNAME, TEST_PASSWORD);
driver
.wait_on_url(|url| url.starts_with(LOCAL_AUTH_BASE_URL))
.unwrap();
thread::sleep(Duration::from_secs(1));
let _ = driver.quit();
}