mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
## What changed - Re-run the HTTP headers helper after a same-origin `POST` receives a `401` or `403`, and retry the request once when the effective headers changed. - Share refreshes across concurrent rejected requests, retain the previous headers when refresh fails, and keep the retry within the original timeout. - Allow helpers to supply `Authorization` while preserving explicit OAuth or bearer credentials, OAuth insufficient-scope challenges, and redirect safety checks. ## Testing - Cover rotating helper headers for MCP tool calls and same-origin OAuth token refreshes. - Cover concurrent rejections, failed and unchanged refreshes, retry limits, timeouts, and redirects. GitOrigin-RevId: c662edefdb1cd2876d9d02545a72d5bd6739864f
127 lines
3.9 KiB
Rust
127 lines
3.9 KiB
Rust
use codex_exec_server::HttpHeader;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::BearerInsufficientScope;
|
|
use super::InsufficientScopeChallenge;
|
|
use super::insufficient_scope_challenge;
|
|
use super::parse_bearer_insufficient_scope;
|
|
|
|
#[test]
|
|
fn extracts_scope_from_bearer_insufficient_scope_challenges() {
|
|
let cases = [
|
|
(
|
|
r#"Bearer error="insufficient_scope", scope="files:read files:write""#,
|
|
"files:read files:write",
|
|
),
|
|
(
|
|
r#"Bearer error="insufficient_scope", ScOpE = "files:read""#,
|
|
"files:read",
|
|
),
|
|
(
|
|
r#"Bearer scope="read:data", error="insufficient_scope""#,
|
|
"read:data",
|
|
),
|
|
(r#"Bearer error="insufficient_scope", scope=read"#, "read"),
|
|
(
|
|
r#"Bearer error="insufficient_scope", scope="files:read\ files:write""#,
|
|
"files:read files:write",
|
|
),
|
|
(
|
|
r#"Bearer error="insufficient_scope", error_description="request scope=admin, not \"root\"", scope="files:read""#,
|
|
"files:read",
|
|
),
|
|
(
|
|
r#"Basic realm="example", Bearer error="insufficient_scope", scope="files:read""#,
|
|
"files:read",
|
|
),
|
|
(
|
|
r#"Newauth scope="wrong", Bearer error="insufficient_scope", scope="files:read""#,
|
|
"files:read",
|
|
),
|
|
];
|
|
|
|
for (header, expected_scope) in cases {
|
|
assert_eq!(
|
|
parse_bearer_insufficient_scope(header),
|
|
Some(BearerInsufficientScope {
|
|
required_scope: Some(expected_scope.to_string()),
|
|
}),
|
|
"header: {header}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_not_treat_other_bearer_errors_as_insufficient_scope() {
|
|
assert_eq!(
|
|
parse_bearer_insufficient_scope(r#"Bearer error="invalid_token", scope="files:read""#),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_invalid_or_ambiguous_scope_parameters() {
|
|
let cases = [
|
|
r#"Bearer error="insufficient_scope", scope="#,
|
|
r#"Bearer error="insufficient_scope", scope="read\"write""#,
|
|
r#"Bearer error="insufficient_scope", scope="read\\write""#,
|
|
r#"Bearer error="insufficient_scope", scope="read write""#,
|
|
r#"Bearer error="insufficient_scope", scope=read:data"#,
|
|
r#"Bearer error="insufficient_scope", scope=files:read files:write"#,
|
|
r#"Bearer error="insufficient_scope", scope=read=value"#,
|
|
r#"Bearer error="insufficient_scope", scope="read", scope="write""#,
|
|
];
|
|
|
|
for header in cases {
|
|
assert_eq!(
|
|
parse_bearer_insufficient_scope(header),
|
|
Some(BearerInsufficientScope {
|
|
required_scope: None,
|
|
}),
|
|
"header: {header}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ignores_scope_text_outside_a_scope_parameter() {
|
|
let cases = [
|
|
r#"Bearer error_description="request scope=admin""#,
|
|
r#"Bearer resource_scope="admin""#,
|
|
r#"Bearer "scope=admin""#,
|
|
r#"Bearer error_description="unterminated scope=admin"#,
|
|
];
|
|
|
|
for header in cases {
|
|
assert_eq!(
|
|
parse_bearer_insufficient_scope(header),
|
|
None,
|
|
"header: {header}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn selects_bearer_challenge_from_a_later_www_authenticate_field_value() {
|
|
let headers = vec![
|
|
HttpHeader {
|
|
name: "www-authenticate".to_string(),
|
|
value: r#"Basic realm="example""#.to_string(),
|
|
value_env_var: None,
|
|
},
|
|
HttpHeader {
|
|
name: "WWW-Authenticate".to_string(),
|
|
value: r#"Bearer error="insufficient_scope", scope="files:read""#.to_string(),
|
|
value_env_var: None,
|
|
},
|
|
];
|
|
|
|
assert_eq!(
|
|
insufficient_scope_challenge(&headers),
|
|
Some(InsufficientScopeChallenge {
|
|
www_authenticate_header: headers[1].value.clone(),
|
|
required_scope: Some("files:read".to_string()),
|
|
})
|
|
);
|
|
}
|