mod support; use support::*; use ecamo::test; macro_rules! assert_response_header { ($v:expr, $l:literal, $r:expr) => { assert_eq!($v.headers().get($l).unwrap().to_str().unwrap(), $r); }; ($v:expr, $l:literal) => { assert_eq!($v.headers().get($l).is_none(), true); }; } fn make_proxy_token(env: &Environment, url: String) -> (String, String) { test::encode_proxy_token( &env.test_config.private_key, ecamo::token::UrlToken { ecamo_url: url::Url::parse(&url).unwrap(), ecamo_send_token: false, }, "https://service1.test.invalid", Some(60), true, ) } #[actix_rt::test] async fn test_proxy_canonical_host() { let env = init_and_spawn().await; let http = build_reqwest_client(); let resp = http .get(env.url.join("/.ecamo/v1/p/foobar").unwrap()) .header("host", "service1.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_proxy_invalid_token_key() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = test::encode_proxy_token( &env.test_config.service_key_1, ecamo::token::UrlToken { ecamo_url: "http://upstream.test.invalid/test".try_into().unwrap(), ecamo_send_token: false, }, "https://service1.test.invalid", Some(60), true, ); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::UNAUTHORIZED); } #[actix_rt::test] async fn test_proxy_invalid_token_iss() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = test::encode_proxy_token( &env.test_config.private_key, ecamo::token::UrlToken { ecamo_url: "http://upstream.test.invalid/test".try_into().unwrap(), ecamo_send_token: false, }, "https://service1.test.invalid", Some(60), false, ); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::UNAUTHORIZED); } #[actix_rt::test] async fn test_proxy_invalid_token_exp() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = test::encode_proxy_token( &env.test_config.private_key, ecamo::token::UrlToken { ecamo_url: "http://upstream.test.invalid/test".try_into().unwrap(), ecamo_send_token: false, }, "https://service1.test.invalid", None, true, ); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::UNAUTHORIZED); } #[actix_rt::test] async fn test_proxy_invalid_digest() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (_dgst, token) = make_proxy_token(&env, "http://upstream.test.invalid/test".to_owned()); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/PkjrjXJ83cN0SaYKJJlqfr7dUPHNl6Y3wxP5RYnXpsDUMN4zO2jLkOpzHTeejO9F?t={}", token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::UNAUTHORIZED); } #[actix_rt::test] async fn test_proxy_invalid_digest2() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (_dgst, token) = make_proxy_token(&env, "http://upstream.test.invalid/test".to_owned()); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/not-valid-base64url?t={}", token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::BAD_REQUEST); } #[actix_rt::test] async fn test_proxy_invalid_source() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, "http://invalid-upstream.test.invalid/test".to_owned()); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::FORBIDDEN); } #[actix_rt::test] async fn test_proxy_200() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/test.gif", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_response_header!(resp, "content-type", "image/gif"); assert_response_header!(resp, "etag", "W/\"deadbeef\""); assert_response_header!(resp, "expires", "60"); assert_response_header!(resp, "cache-control", "max-age=60, public"); assert_response_header!(resp, "vary", "accept"); let bytes = resp.bytes().await.unwrap(); assert_eq!(bytes, TEST_GIF.clone()); } #[actix_rt::test] async fn test_proxy_200_chunked() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/chunked.gif", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_response_header!(resp, "content-type", "image/gif"); let bytes = resp.bytes().await.unwrap(); assert_eq!(bytes, TEST_GIF.clone()); } #[actix_rt::test] async fn test_proxy_40x() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/404", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::NOT_FOUND); assert_response_header!(resp, "x-ecamo-error-origin", "source"); assert_response_header!(resp, "content-type", "text/plain"); assert_response_header!(resp, "etag"); assert_response_header!(resp, "expires", "60"); assert_response_header!(resp, "cache-control", "max-age=60, public"); assert_response_header!(resp, "vary", "accept"); } #[actix_rt::test] async fn test_proxy_inallowed_private_source() { let env = init_and_spawn().await; let http = build_reqwest_client(); let orig_url = format!("{}/test.gif", mockito::server_url()); let url = orig_url.replace("127.0.0.1", "localhost"); assert_ne!(orig_url, url); let (dgst, token) = make_proxy_token(&env, url); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::FORBIDDEN); assert_response_header!(resp, "x-ecamo-error-origin"); } #[actix_rt::test] async fn test_proxy_too_long() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/large.gif", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::FORBIDDEN); assert_response_header!(resp, "x-ecamo-error-origin"); } #[actix_rt::test] async fn test_proxy_inallowed_content_type() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/text", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::FORBIDDEN); assert_response_header!(resp, "x-ecamo-error-origin"); } #[actix_rt::test] async fn test_proxy_connect_error() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, "http://127.0.0.1:0/invalid".to_owned()); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::BAD_GATEWAY); assert_response_header!(resp, "x-ecamo-error-origin"); } #[actix_rt::test] async fn test_proxy_too_long_chunked() { let env = init_and_spawn().await; let http = build_reqwest_client(); let (dgst, token) = make_proxy_token(&env, format!("{}/chunked-large.gif", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::OK); let bytes = resp.bytes().await; let err = bytes.err().unwrap(); assert!(err.is_body()); } #[actix_rt::test] async fn test_proxy_send_token() { let env = init_and_spawn().await; let http = build_reqwest_client(); let mut mockserv = httptest::Server::run(); let (dgst, token) = test::encode_proxy_token( &env.test_config.private_key, ecamo::token::UrlToken { ecamo_url: url::Url::parse(mockserv.url("/guarded.gif").to_string().as_ref()).unwrap(), ecamo_send_token: true, }, "https://service1.test.invalid", Some(60), true, ); log::debug!("{}", mockserv.url("/")); mockserv.expect( httptest::Expectation::matching(httptest::matchers::all_of![ httptest::matchers::request::method_path("GET", "/guarded.gif"), httptest::matchers::request::headers(HttptestAnonymousIDTokenMatcher { aud: url::Url::parse(&mockserv.url("").to_string()) .unwrap() .origin() .ascii_serialization(), svc: "https://service1.test.invalid".to_owned(), key: env.test_config.private_key_decoding.clone(), }), ]) .respond_with(httptest::responders::status_code(200)), ); let _resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .send() .await .unwrap(); mockserv.verify_and_clear(); } #[actix_rt::test] async fn test_proxy_request_headers() { let env = init_and_spawn().await; let http = build_reqwest_client(); let _mock = mockito::mock("GET", "/headers.gif") .match_header("accept", "image/gif") .with_body(TEST_GIF.clone()) .with_header("content-type", "image/gif") .with_header("etag", "W/\"headers\"") .create(); let (dgst, token) = make_proxy_token(&env, format!("{}/headers.gif", mockito::server_url())); let resp = http .get( env.url .join(&format!("/.ecamo/v1/p/{}?t={}", dgst, token)) .unwrap(), ) .header("host", "ecamo.test.invalid") .header("accept", "image/gif") .send() .await .unwrap(); assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_response_header!(resp, "etag", "W/\"headers\""); let bytes = resp.bytes().await.unwrap(); assert_eq!(bytes, TEST_GIF.clone()); }