This commit is contained in:
Mateusz Gruszczyński committed 2026-10-27 12:18:20 +01:00
1 parent 62b7574553
commit e364392659
47 files changed
+2164 -257

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
mod tests {
use super::*;
use crate::auth_provider::{
testing::{context, policy, spawn_mock},
Provider,
};
fn settings(url: &str) -> Settings {
let mut settings = Settings::default();
settings.enabled = true;
settings.url = url.into();
settings.allow_private = true;
settings
}
fn compile(url: &str) -> Compiled {
settings(url).compile(&policy(), &[]).unwrap().unwrap()
}
#[test]
fn legacy_json_and_provider_path_contract_are_preserved() {
let parsed: Settings = serde_json::from_str(
r#"{"enabled":true,"url":"http://127.0.0.1:9300","allow_private":true}"#,
)
.unwrap();
assert_eq!(
serde_json::to_value(&parsed).unwrap()["url"],
"http://127.0.0.1:9300"
);
let auth = compile("http://127.0.0.1:9300");
assert_eq!(auth.id(), "authentik");
assert!(auth.reserved_path("/outpost.goauthentik.io"));
assert!(auth.reserved_path("/outpost.goauthentik.io/start"));
assert!(!auth.reserved_path("/outpost.goauthentik.io-evil"));
assert!(!auth.reserved_path("/other/outpost.goauthentik.io"));
}
#[tokio::test]
async fn unauthorized_redirect_uses_application_domain_not_portal_domain() {
let (url, server) = spawn_mock(
"401 Unauthorized",
"Set-Cookie: authentik_session=abc; Secure; Path=/; HttpOnly\r\n",
)
.await;
let auth = compile(&url);
let incoming = HeaderMap::new();
let response = auth
.authorize(
&incoming,
&Method::POST,
&context(&incoming, "app.other-domain.example"),
&CancellationToken::new(),
&CookieSink::default(),
)
.await
.unwrap_err();
assert_eq!(response.status(), StatusCode::FOUND);
let location = Url::parse(response.headers()[header::LOCATION].to_str().unwrap()).unwrap();
assert_eq!(
location.origin().ascii_serialization(),
"https://app.other-domain.example"
);
assert_eq!(location.path(), "/outpost.goauthentik.io/start");
assert_eq!(
location.query_pairs().find(|(k, _)| k == "rd").unwrap().1,
"https://app.other-domain.example/private?redirect=%2Fhome"
);
assert_eq!(
response.headers()[header::SET_COOKIE],
"authentik_session=abc; Secure; Path=/; HttpOnly"
);
server.await.unwrap();
}
#[tokio::test]
async fn forbidden_response_never_authorizes_backend() {
let (url, server) = spawn_mock("403 Forbidden", "").await;
let auth = compile(&url);
let incoming = HeaderMap::new();
let response = auth
.authorize(
&incoming,
&Method::GET,
&context(&incoming, "app.example.com"),
&CancellationToken::new(),
&CookieSink::default(),
)
.await
.unwrap_err();
assert_eq!(response.status(), StatusCode::FORBIDDEN);
server.await.unwrap();
}
#[tokio::test]
async fn verified_headers_and_refresh_cookie_are_forwarded() {
let (url, server) = spawn_mock("200 OK", "X-Authentik-Username: allowed\r\nSet-Cookie: authentik_session=renew; Path=/; HttpOnly\r\n").await;
let auth = compile(&url);
let incoming = HeaderMap::new();
let cookies = CookieSink::default();
let identity = auth
.authorize(
&incoming,
&Method::GET,
&context(&incoming, "app.example.com"),
&CancellationToken::new(),
&cookies,
)
.await
.unwrap();
let mut headers = HeaderMap::new();
identity.apply(&mut headers);
assert_eq!(headers["x-authentik-username"], "allowed");
let mut response = Response::new(Body::empty());
cookies.apply(&mut response);
assert_eq!(
response.headers()[header::SET_COOKIE],
"authentik_session=renew; Path=/; HttpOnly"
);
server.await.unwrap();
}
#[tokio::test]
async fn outpost_is_proxied_only_on_reserved_path_with_trusted_host() {
let (url, server) = spawn_mock("302 Found", "Location: https://auth.example.com/if/flow/default/\r\nSet-Cookie: authentik=ok; Path=/\r\n").await;
let auth = compile(&url);
let incoming = HeaderMap::new();
let request = Request::builder()
.uri("/outpost.goauthentik.io/start?rd=https%3A%2F%2Fapp.example.com")
.header("X-Forwarded-Host", "spoofed.example")
.header("X-Authentik-Username", "forged")
.body(Body::empty())
.unwrap();
let response = auth
.serve_reserved(
request,
&context(&incoming, "app.example.com"),
&CancellationToken::new(),
)
.unwrap()
.await;
assert_eq!(response.status(), StatusCode::FOUND);
assert_eq!(
response.headers()[header::SET_COOKIE],
"authentik=ok; Path=/"
);
assert_eq!(
response.headers()[header::CACHE_CONTROL],
"private, no-store"
);
let request = server.await.unwrap().to_ascii_lowercase();
assert!(request.starts_with("get /outpost.goauthentik.io/start?rd="));
assert!(request.contains("host: app.example.com"));
assert!(request.contains("x-forwarded-host: app.example.com"));
assert!(!request.contains("spoofed.example"));
assert!(!request.contains("x-authentik-username: forged"));
}
#[tokio::test]
async fn auth_check_uses_provider_specific_endpoint() {
let (url, server) = spawn_mock("200 OK", "X-Authentik-Username: admin\r\n").await;
let auth = compile(&url);
let incoming = HeaderMap::new();
let identity = auth
.authorize(
&incoming,
&Method::GET,
&context(&incoming, "app.example.com"),
&CancellationToken::new(),
&CookieSink::default(),
)
.await
.unwrap();
let mut headers = HeaderMap::new();
identity.apply(&mut headers);
assert_eq!(headers["x-authentik-username"], "admin");
assert!(server
.await
.unwrap()
.to_ascii_lowercase()
.contains("get /outpost.goauthentik.io/auth/nginx"));
}
}