mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat: improve OAuth provider configuration
This commit is contained in:
@@ -41,5 +41,9 @@ fn git_describe_version() -> Option<String> {
|
||||
}
|
||||
|
||||
fn normalize_version(value: &str) -> String {
|
||||
value.trim().strip_prefix('v').unwrap_or(value.trim()).to_string()
|
||||
value
|
||||
.trim()
|
||||
.strip_prefix('v')
|
||||
.unwrap_or(value.trim())
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -87,11 +87,36 @@ pub(crate) fn admin_oauth_test_provider_type_from_path(request_path: &str) -> Op
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn admin_oauth_is_supported_provider(provider_type: &str) -> bool {
|
||||
matches!(
|
||||
provider_type.to_ascii_lowercase().as_str(),
|
||||
"linuxdo" | "custom_oidc"
|
||||
)
|
||||
pub(super) fn admin_oauth_normalized_provider_type(provider_type: &str) -> Option<String> {
|
||||
let normalized = provider_type.trim().to_ascii_lowercase();
|
||||
if !(3..=64).contains(&normalized.len()) {
|
||||
return None;
|
||||
}
|
||||
let mut chars = normalized.chars();
|
||||
let first = chars.next()?;
|
||||
if !first.is_ascii_lowercase() {
|
||||
return None;
|
||||
}
|
||||
if !chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-') {
|
||||
return None;
|
||||
}
|
||||
Some(normalized)
|
||||
}
|
||||
|
||||
pub(super) fn admin_oauth_is_custom_provider_type(provider_type: &str) -> bool {
|
||||
let Some(provider_type) = admin_oauth_normalized_provider_type(provider_type) else {
|
||||
return false;
|
||||
};
|
||||
provider_type == "custom_oidc"
|
||||
|| provider_type.starts_with("custom_oidc_")
|
||||
|| provider_type.starts_with("custom_")
|
||||
|| provider_type.starts_with("oidc_")
|
||||
}
|
||||
|
||||
pub(super) fn admin_oauth_is_supported_provider(provider_type: &str) -> bool {
|
||||
admin_oauth_normalized_provider_type(provider_type).is_some_and(|provider_type| {
|
||||
provider_type == "linuxdo" || admin_oauth_is_custom_provider_type(&provider_type)
|
||||
})
|
||||
}
|
||||
|
||||
fn admin_oauth_builtin_allowed_domains(provider_type: &str) -> Option<&'static [&'static str]> {
|
||||
@@ -184,7 +209,10 @@ pub(super) fn build_admin_oauth_upsert_record(
|
||||
provider_type: &str,
|
||||
payload: AdminOAuthProviderUpsertRequest,
|
||||
) -> Result<UpsertOAuthProviderConfigRecord, String> {
|
||||
if !admin_oauth_is_supported_provider(provider_type) {
|
||||
let Some(provider_type) = admin_oauth_normalized_provider_type(provider_type) else {
|
||||
return Err("provider_type 只能包含小写字母、数字、下划线和中划线".to_string());
|
||||
};
|
||||
if !admin_oauth_is_supported_provider(&provider_type) {
|
||||
return Err("不支持的 provider_type".to_string());
|
||||
}
|
||||
|
||||
@@ -208,19 +236,19 @@ pub(super) fn build_admin_oauth_upsert_record(
|
||||
validate_admin_oauth_frontend_callback_url(frontend_callback_url)?;
|
||||
validate_admin_oauth_redirect_uri(redirect_uri)?;
|
||||
|
||||
let is_custom_oidc = provider_type.eq_ignore_ascii_case("custom_oidc");
|
||||
let is_custom_oidc = admin_oauth_is_custom_provider_type(&provider_type);
|
||||
let custom_allowed_domains = if is_custom_oidc {
|
||||
let domains = admin_oauth_custom_allowed_domains(payload.extra_config.as_ref());
|
||||
if domains.is_empty() {
|
||||
return Err(
|
||||
"custom_oidc 必须在 extra_config.allowed_domains 配置域名白名单".to_string(),
|
||||
);
|
||||
return Err(format!(
|
||||
"{provider_type} 必须在 extra_config.allowed_domains 配置域名白名单"
|
||||
));
|
||||
}
|
||||
domains
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let builtin_allowed_domains = admin_oauth_builtin_allowed_domains(provider_type);
|
||||
let builtin_allowed_domains = admin_oauth_builtin_allowed_domains(&provider_type);
|
||||
|
||||
if is_custom_oidc {
|
||||
for (field_name, value) in [
|
||||
@@ -309,7 +337,7 @@ pub(super) fn build_admin_oauth_upsert_record(
|
||||
};
|
||||
|
||||
Ok(UpsertOAuthProviderConfigRecord {
|
||||
provider_type: provider_type.to_string(),
|
||||
provider_type,
|
||||
display_name: display_name.to_string(),
|
||||
client_id: client_id.to_string(),
|
||||
client_secret_encrypted,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::oauth_config::{
|
||||
admin_oauth_provider_type_from_path, admin_oauth_test_provider_type_from_path,
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record, AdminOAuthProviderUpsertRequest,
|
||||
admin_oauth_is_supported_provider, admin_oauth_provider_type_from_path,
|
||||
admin_oauth_test_provider_type_from_path, build_admin_oauth_provider_payload,
|
||||
build_admin_oauth_supported_types_payload, build_admin_oauth_upsert_record,
|
||||
AdminOAuthProviderUpsertRequest,
|
||||
};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::{attach_admin_audit_response, build_proxy_error_response};
|
||||
@@ -13,6 +14,143 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::time::Duration;
|
||||
|
||||
const ADMIN_OAUTH_TEST_TIMEOUT_SECS: u64 = 5;
|
||||
const LINUXDO_AUTHORIZATION_URL: &str = "https://connect.linux.do/oauth2/authorize";
|
||||
const LINUXDO_TOKEN_URL: &str = "https://connect.linux.do/oauth2/token";
|
||||
|
||||
fn admin_oauth_payload_string<'a>(payload: &'a serde_json::Value, field: &str) -> Option<&'a str> {
|
||||
payload
|
||||
.get(field)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn admin_oauth_secret_status(has_secret: bool) -> &'static str {
|
||||
if has_secret {
|
||||
"configured"
|
||||
} else {
|
||||
"not_provided"
|
||||
}
|
||||
}
|
||||
|
||||
async fn admin_oauth_endpoint_reachable(client: &reqwest::Client, url: &str) -> bool {
|
||||
let Ok(parsed) = reqwest::Url::parse(url) else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(parsed.scheme(), "http" | "https") || parsed.host_str().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match client
|
||||
.get(parsed)
|
||||
.header(reqwest::header::ACCEPT, "*/*")
|
||||
.header(
|
||||
reqwest::header::USER_AGENT,
|
||||
"Aether OAuth configuration tester",
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
status != reqwest::StatusCode::NOT_FOUND && status.as_u16() < 500
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_admin_oauth_test_payload(
|
||||
state: &AdminAppState<'_>,
|
||||
provider_type: &str,
|
||||
payload: &serde_json::Value,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
if !admin_oauth_is_supported_provider(provider_type) {
|
||||
return Ok(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
"secret_status": "unknown",
|
||||
"details": "provider 未安装/不可用",
|
||||
}));
|
||||
}
|
||||
|
||||
let provided_secret = admin_oauth_payload_string(payload, "client_secret");
|
||||
let persisted_config = state.get_oauth_provider_config(provider_type).await?;
|
||||
let persisted_secret_configured = persisted_config
|
||||
.as_ref()
|
||||
.and_then(|provider| provider.client_secret_encrypted.as_ref())
|
||||
.is_some();
|
||||
let has_secret = provided_secret.is_some() || persisted_secret_configured;
|
||||
|
||||
let builtin_defaults = provider_type
|
||||
.eq_ignore_ascii_case("linuxdo")
|
||||
.then_some((LINUXDO_AUTHORIZATION_URL, LINUXDO_TOKEN_URL));
|
||||
let authorization_url = admin_oauth_payload_string(payload, "authorization_url_override")
|
||||
.map(ToOwned::to_owned)
|
||||
.or_else(|| {
|
||||
persisted_config
|
||||
.as_ref()
|
||||
.and_then(|provider| provider.authorization_url_override.clone())
|
||||
})
|
||||
.or_else(|| builtin_defaults.map(|defaults| defaults.0.to_string()));
|
||||
let token_url = admin_oauth_payload_string(payload, "token_url_override")
|
||||
.map(ToOwned::to_owned)
|
||||
.or_else(|| {
|
||||
persisted_config
|
||||
.as_ref()
|
||||
.and_then(|provider| provider.token_url_override.clone())
|
||||
})
|
||||
.or_else(|| builtin_defaults.map(|defaults| defaults.1.to_string()));
|
||||
let Some(authorization_url) = authorization_url else {
|
||||
return Ok(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
"secret_status": admin_oauth_secret_status(has_secret),
|
||||
"details": "Authorization URL 未配置",
|
||||
}));
|
||||
};
|
||||
let Some(token_url) = token_url else {
|
||||
return Ok(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
"secret_status": admin_oauth_secret_status(has_secret),
|
||||
"details": "Token URL 未配置",
|
||||
}));
|
||||
};
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(ADMIN_OAUTH_TEST_TIMEOUT_SECS))
|
||||
.redirect(reqwest::redirect::Policy::limited(3))
|
||||
.build();
|
||||
let Ok(client) = client else {
|
||||
return Ok(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
"secret_status": admin_oauth_secret_status(has_secret),
|
||||
"details": "OAuth 配置测试 HTTP client 初始化失败",
|
||||
}));
|
||||
};
|
||||
|
||||
let (authorization_url_reachable, token_url_reachable) = tokio::join!(
|
||||
admin_oauth_endpoint_reachable(&client, &authorization_url),
|
||||
admin_oauth_endpoint_reachable(&client, &token_url),
|
||||
);
|
||||
|
||||
let details = if authorization_url_reachable && token_url_reachable {
|
||||
"OAuth 端点可达;client_secret 仅在授权回调兑换 code 时校验"
|
||||
} else {
|
||||
"OAuth 端点不可达或返回不可用状态;请检查端点 URL、网络和代理配置"
|
||||
};
|
||||
|
||||
Ok(json!({
|
||||
"authorization_url_reachable": authorization_url_reachable,
|
||||
"token_url_reachable": token_url_reachable,
|
||||
"secret_status": admin_oauth_secret_status(has_secret),
|
||||
"details": details,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
state: &AdminAppState<'_>,
|
||||
@@ -259,16 +397,8 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
));
|
||||
}
|
||||
};
|
||||
let client_id = payload
|
||||
.get("client_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty());
|
||||
let redirect_uri = payload
|
||||
.get("redirect_uri")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty());
|
||||
let client_id = admin_oauth_payload_string(&payload, "client_id");
|
||||
let redirect_uri = admin_oauth_payload_string(&payload, "redirect_uri");
|
||||
if client_id.is_none() || redirect_uri.is_none() {
|
||||
return Ok(Some(
|
||||
(
|
||||
@@ -278,38 +408,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
let provided_secret = payload
|
||||
.get("client_secret")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty());
|
||||
let persisted_secret = state
|
||||
.get_oauth_provider_config(&provider_type)
|
||||
.await?
|
||||
.and_then(|provider| provider.client_secret_encrypted);
|
||||
let supported_provider = provider_type.eq_ignore_ascii_case("linuxdo");
|
||||
let secret_status = if supported_provider {
|
||||
if provided_secret.is_some() || persisted_secret.is_some() {
|
||||
"unsupported"
|
||||
} else {
|
||||
"not_provided"
|
||||
}
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
let details = if supported_provider {
|
||||
"OAuth 配置测试仅支持 Rust execution runtime"
|
||||
} else {
|
||||
"provider 未安装/不可用"
|
||||
};
|
||||
let test_payload = build_admin_oauth_test_payload(state, &provider_type, &payload).await?;
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
"secret_status": secret_status,
|
||||
"details": details,
|
||||
}))
|
||||
.into_response(),
|
||||
Json(test_payload).into_response(),
|
||||
"admin_oauth_provider_tested",
|
||||
"test_oauth_provider_config",
|
||||
"oauth_provider",
|
||||
|
||||
@@ -6197,6 +6197,232 @@ async fn gateway_upserts_custom_oidc_with_allowed_domains() {
|
||||
upstream_handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_upserts_multiple_custom_oidc_configs() {
|
||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let upstream_hits_clone = Arc::clone(&upstream_hits);
|
||||
let upstream = Router::new().fallback(any(move |_request: Request| {
|
||||
let upstream_hits_inner = Arc::clone(&upstream_hits_clone);
|
||||
async move {
|
||||
*upstream_hits_inner.lock().expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Body::from("unexpected upstream hit"))
|
||||
}
|
||||
}));
|
||||
|
||||
let repository = Arc::new(InMemoryOAuthProviderRepository::default());
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new()
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(GatewayDataState::with_oauth_provider_repository_for_tests(
|
||||
repository.clone(),
|
||||
)),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
for (provider_type, display_name, host) in [
|
||||
("custom_oidc_work", "Work OIDC", "work-idp.example.com"),
|
||||
(
|
||||
"custom_oidc_personal",
|
||||
"Personal OIDC",
|
||||
"personal-idp.example.com",
|
||||
),
|
||||
] {
|
||||
let response = reqwest::Client::new()
|
||||
.put(format!(
|
||||
"{gateway_url}/api/admin/oauth/providers/{provider_type}"
|
||||
))
|
||||
.header(crate::constants::GATEWAY_HEADER, "rust-phase3b")
|
||||
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
|
||||
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
|
||||
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
|
||||
.json(&json!({
|
||||
"display_name": display_name,
|
||||
"client_id": format!("{provider_type}-client"),
|
||||
"authorization_url_override": format!("https://{host}/oauth/authorize"),
|
||||
"token_url_override": format!("https://{host}/oauth/token"),
|
||||
"userinfo_url_override": format!("https://{host}/oauth/userinfo"),
|
||||
"scopes": ["openid", "profile", "email"],
|
||||
"redirect_uri": format!("https://backend.example.com/api/oauth/{provider_type}/callback"),
|
||||
"frontend_callback_url": "https://frontend.example.com/auth/callback",
|
||||
"attribute_mapping": {"sub": "id", "email": "profile.email"},
|
||||
"extra_config": {"allowed_domains": [host]},
|
||||
"is_enabled": true
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["provider_type"], provider_type);
|
||||
assert_eq!(payload["display_name"], display_name);
|
||||
}
|
||||
|
||||
let stored = repository
|
||||
.list_oauth_provider_configs()
|
||||
.await
|
||||
.expect("list should succeed");
|
||||
assert_eq!(stored.len(), 2);
|
||||
assert!(stored
|
||||
.iter()
|
||||
.any(|provider| provider.provider_type == "custom_oidc_work"));
|
||||
assert!(stored
|
||||
.iter()
|
||||
.any(|provider| provider.provider_type == "custom_oidc_personal"));
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
upstream_handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_tests_admin_oauth_linuxdo_endpoints_locally_with_configured_secret() {
|
||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let upstream_hits_clone = Arc::clone(&upstream_hits);
|
||||
let upstream = Router::new().route(
|
||||
"/api/admin/oauth/providers/linuxdo/test",
|
||||
any(move |_request: Request| {
|
||||
let upstream_hits_inner = Arc::clone(&upstream_hits_clone);
|
||||
async move {
|
||||
*upstream_hits_inner.lock().expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Body::from("unexpected upstream hit"))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let authorization_hits = Arc::new(Mutex::new(0usize));
|
||||
let authorization_hits_clone = Arc::clone(&authorization_hits);
|
||||
let token_hits = Arc::new(Mutex::new(0usize));
|
||||
let token_hits_clone = Arc::clone(&token_hits);
|
||||
let oauth_endpoints = Router::new()
|
||||
.route(
|
||||
"/oauth2/authorize",
|
||||
any(move |_request: Request| {
|
||||
let authorization_hits_inner = Arc::clone(&authorization_hits_clone);
|
||||
async move {
|
||||
*authorization_hits_inner.lock().expect("mutex should lock") += 1;
|
||||
(StatusCode::BAD_REQUEST, Body::from("missing query"))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/oauth2/token",
|
||||
any(move |_request: Request| {
|
||||
let token_hits_inner = Arc::clone(&token_hits_clone);
|
||||
async move {
|
||||
*token_hits_inner.lock().expect("mutex should lock") += 1;
|
||||
(
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
Body::from("method not allowed"),
|
||||
)
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let repository = Arc::new(InMemoryOAuthProviderRepository::seed(vec![
|
||||
sample_oauth_provider_config("linuxdo"),
|
||||
]));
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (oauth_url, oauth_handle) = start_server(oauth_endpoints).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new()
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(GatewayDataState::with_oauth_provider_repository_for_tests(
|
||||
repository,
|
||||
)),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!(
|
||||
"{gateway_url}/api/admin/oauth/providers/linuxdo/test"
|
||||
))
|
||||
.header(crate::constants::GATEWAY_HEADER, "rust-phase3b")
|
||||
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
|
||||
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
|
||||
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
|
||||
.json(&json!({
|
||||
"client_id": "client-id",
|
||||
"authorization_url_override": format!("{oauth_url}/oauth2/authorize"),
|
||||
"token_url_override": format!("{oauth_url}/oauth2/token"),
|
||||
"redirect_uri": "http://localhost:8084/api/oauth/linuxdo/callback"
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["authorization_url_reachable"], true);
|
||||
assert_eq!(payload["token_url_reachable"], true);
|
||||
assert_eq!(payload["secret_status"], "configured");
|
||||
assert_eq!(*authorization_hits.lock().expect("mutex should lock"), 1);
|
||||
assert_eq!(*token_hits.lock().expect("mutex should lock"), 1);
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
upstream_handle.abort();
|
||||
oauth_handle.abort();
|
||||
let _ = upstream_url;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_tests_admin_oauth_linuxdo_reports_invalid_endpoint_urls() {
|
||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let upstream_hits_clone = Arc::clone(&upstream_hits);
|
||||
let upstream = Router::new().route(
|
||||
"/api/admin/oauth/providers/linuxdo/test",
|
||||
any(move |_request: Request| {
|
||||
let upstream_hits_inner = Arc::clone(&upstream_hits_clone);
|
||||
async move {
|
||||
*upstream_hits_inner.lock().expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Body::from("unexpected upstream hit"))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let repository = Arc::new(InMemoryOAuthProviderRepository::default());
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new()
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(GatewayDataState::with_oauth_provider_repository_for_tests(
|
||||
repository,
|
||||
)),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!(
|
||||
"{gateway_url}/api/admin/oauth/providers/linuxdo/test"
|
||||
))
|
||||
.header(crate::constants::GATEWAY_HEADER, "rust-phase3b")
|
||||
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
|
||||
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
|
||||
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
|
||||
.json(&json!({
|
||||
"client_id": "client-id",
|
||||
"authorization_url_override": "not-a-url",
|
||||
"token_url_override": "not-a-url",
|
||||
"redirect_uri": "http://localhost:8084/api/oauth/linuxdo/callback"
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["authorization_url_reachable"], false);
|
||||
assert_eq!(payload["token_url_reachable"], false);
|
||||
assert_eq!(payload["secret_status"], "not_provided");
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
upstream_handle.abort();
|
||||
let _ = upstream_url;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_deletes_admin_oauth_provider_locally_with_trusted_admin_principal() {
|
||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
|
||||
Reference in New Issue
Block a user