use super::*;
use axum::{body::Body, http::Request, routing::get, Router};
use tower::ServiceExt;
fn guarded() -> Router {
Router::new()
.route("/api/daemon/info", get(|| async { "reached" }))
.layer(axum::middleware::from_fn(not_a_browser))
}
async fn status_of(request: Request<Body>) -> axum::http::StatusCode {
guarded()
.oneshot(request)
.await
.expect("the router answers")
.status()
}
#[tokio::test]
async fn test9880_a_command_line_reaches_the_control_api() {
let request = Request::builder()
.uri("/api/daemon/info")
.header("user-agent", "wsb")
.body(Body::empty())
.expect("a request");
assert_eq!(status_of(request).await, StatusCode::OK);
}
#[tokio::test]
async fn test9881_a_browser_is_refused_whatever_it_claims() {
for (header, value) in [
("sec-fetch-site", "cross-site"),
("sec-fetch-site", "same-origin"),
("sec-fetch-site", "none"),
] {
let request = Request::builder()
.uri("/api/daemon/info")
.header(header, value)
.body(Body::empty())
.expect("a request");
assert_eq!(
status_of(request).await,
StatusCode::FORBIDDEN,
"a request carrying {header}: {value} came from a browser"
);
}
}
#[tokio::test]
async fn test9882_a_request_carrying_an_origin_is_refused() {
let request = Request::builder()
.uri("/api/daemon/info")
.header("origin", "http://127.0.0.1:3000")
.body(Body::empty())
.expect("a request");
assert_eq!(status_of(request).await, StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn test9883_a_refusal_is_stated_as_one() {
let response = guarded()
.oneshot(
Request::builder()
.uri("/api/daemon/info")
.header("origin", "http://evil.example")
.body(Body::empty())
.expect("a request"),
)
.await
.expect("the router answers");
let body = hyper::body::to_bytes(response.into_body())
.await
.expect("the body is readable");
let refusal: wsb::daemon::ApiError =
serde_json::from_slice(&body).expect("a refusal is stated as one");
assert!(
refusal.error.contains("command line"),
"the refusal must say who this API is for: {}",
refusal.error
);
}
async fn machine_with_a_workspace() -> (tempfile::TempDir, tempfile::TempDir, Arc<MachineState>) {
let machine_dir = tempfile::tempdir().expect("a scratch directory");
let repository = tempfile::tempdir().expect("a scratch repository");
let root = wsb::platform::canonicalize(&repository.path()).expect("the path resolves");
for arguments in [
vec!["init", "--quiet"],
vec!["-c", "user.email=t@t", "-c", "user.name=t", "commit", "--allow-empty", "-qm", "first"],
] {
let done = wsb::git_backend::git_command()
.arg("-C")
.arg(&root)
.args(&arguments)
.status()
.expect("git runs");
assert!(done.success(), "git {arguments:?} failed");
}
wsb::workspace::exclude_state_dir(&root).expect("the state dir is excluded from git");
wsb::workspace_state::WorkspaceState::initialize(&root, None).expect("wsb is set up here, as `wsb init` does");
let machine = MachineState::new(
machine_dir.path().to_path_buf(),
std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
0,
std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
0,
)
.expect("a machine");
machine.resolve(&root).await.expect("the workspace opens");
(machine_dir, repository, machine)
}
#[tokio::test]
async fn test11496_stopping_takes_the_control_secret() {
let (_machine_dir, _repository, machine) = machine_with_a_workspace().await;
assert!(
!presents_control_secret(&machine, &HeaderMap::new()),
"a caller with nothing to show must not be able to stop this daemon"
);
assert!(!machine.control_token.is_empty(), "a daemon minted no control secret");
let mut presented = HeaderMap::new();
presented.insert(
wsb::daemon::protocol::route::CONTROL_HEADER,
machine.control_token.parse().expect("a header value"),
);
assert!(
presents_control_secret(&machine, &presented),
"the operator's own command line, which read the rendezvous file to \
find the port, could not stop the daemon"
);
for wrong in ["", " ", "0", &machine.control_token[..machine.control_token.len() - 1],
&format!("{}0", machine.control_token)] {
let mut headers = HeaderMap::new();
if let Ok(value) = wrong.parse() {
headers.insert(wsb::daemon::protocol::route::CONTROL_HEADER, value);
}
assert!(
!presents_control_secret(&machine, &headers),
"a caller presenting {wrong:?} was allowed to stop this daemon"
);
}
}
#[tokio::test]
async fn test11497_each_daemon_mints_its_own_control_secret() {
let (_one_dir, _one_repo, one) = machine_with_a_workspace().await;
let (_two_dir, _two_repo, two) = machine_with_a_workspace().await;
assert_ne!(
one.control_token, two.control_token,
"two daemons minted the same control secret, so either can stop the other"
);
assert_eq!(one.control_token.len(), 64, "32 bytes, hex");
assert!(one.control_token.chars().all(|c| c.is_ascii_hexdigit()));
let mut headers = HeaderMap::new();
headers.insert(
wsb::daemon::protocol::route::CONTROL_HEADER,
one.control_token.parse().expect("a header value"),
);
assert!(
!presents_control_secret(&two, &headers),
"one daemon's control secret stopped another"
);
}