mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
## What changed - Classify token-source failures by credential kind and exchange stage, with sign-in or administrator guidance that omits provider error text. Treat resource authorization failures separately from enterprise identity failures when deciding whether authentication is required. - Add policy-bound request authority checks that return `RequestNotSent` when policy is updating or unavailable, while allowing admitted responses to finish. Support child authorities that isolate connection revocation and share response draining. - Recheck account validity after acquiring the credential invalidation lock and check cancellation before deleting a rejected credential. ## Testing Extend regression coverage for policy suspension during token renewal, completion of admitted requests, recovery with fresh authority, and transport retry classification. Expand provider-error tests to cover `unauthorized_client` and `invalid_client` while checking that credential text is not exposed. GitOrigin-RevId: c0097adc35cb5fdffde1466e4bc2b5ec78114a4b
97 lines
4.1 KiB
Rust
97 lines
4.1 KiB
Rust
use anyhow::Error;
|
|
use codex_exec_server::ExecServerError;
|
|
use rmcp::service::ClientInitializeError;
|
|
use rmcp::service::ServiceError;
|
|
use rmcp::transport::DynamicTransportError;
|
|
use rmcp::transport::auth::AuthError;
|
|
use rmcp::transport::streamable_http_client::StreamableHttpError;
|
|
|
|
use crate::ema_auth_policy::EmaAuthFailure;
|
|
use crate::ema_auth_policy::EmaInvalidGrantSource;
|
|
use crate::http_client_adapter::StreamableHttpClientAdapterError;
|
|
use crate::rmcp_client::ClientOperationError;
|
|
|
|
/// Returns whether an RMCP client error indicates that authentication is required.
|
|
///
|
|
/// This does not distinguish first-time login from reauthentication.
|
|
/// Streamable HTTP initialization errors are stored inside RMCP's dynamic
|
|
/// transport error, which is not part of the standard error source chain.
|
|
pub fn is_authentication_required_error(error: &Error) -> bool {
|
|
error.chain().any(|source| {
|
|
if let Some(error) = source.downcast_ref::<crate::EmaTokenSourceError>() {
|
|
return error.is_authentication_required();
|
|
}
|
|
source
|
|
.downcast_ref::<AuthError>()
|
|
.is_some_and(auth_error_requires_authentication)
|
|
|| source
|
|
.downcast_ref::<EmaAuthFailure>()
|
|
.is_some_and(|error| match error {
|
|
EmaAuthFailure::InvalidGrant {
|
|
grant_source: EmaInvalidGrantSource::EnterpriseIdentity,
|
|
}
|
|
| EmaAuthFailure::InsufficientUserAuthentication {
|
|
grant_source: EmaInvalidGrantSource::EnterpriseIdentity,
|
|
}
|
|
| EmaAuthFailure::IdentityClientRejected
|
|
| EmaAuthFailure::ReauthenticationRequired => true,
|
|
// Rejecting an ID-JAG does not invalidate the enterprise login itself.
|
|
EmaAuthFailure::InvalidGrant {
|
|
grant_source: EmaInvalidGrantSource::ResourceAuthorization,
|
|
}
|
|
| EmaAuthFailure::InsufficientUserAuthentication {
|
|
grant_source: EmaInvalidGrantSource::ResourceAuthorization,
|
|
} => false,
|
|
})
|
|
|| source
|
|
.downcast_ref::<ExecServerError>()
|
|
.is_some_and(exec_server_error_requires_authentication)
|
|
|| source
|
|
.downcast_ref::<ClientInitializeError>()
|
|
.is_some_and(|mut error| {
|
|
while let ClientInitializeError::LegacyFallbackFailed { fallback, .. } = error {
|
|
error = fallback;
|
|
}
|
|
matches!(
|
|
error,
|
|
ClientInitializeError::TransportError { error, .. }
|
|
if transport_error_requires_authentication(error)
|
|
)
|
|
})
|
|
|| source
|
|
.downcast_ref::<ClientOperationError>()
|
|
.is_some_and(|error| {
|
|
matches!(
|
|
error,
|
|
ClientOperationError::Service(ServiceError::TransportSend(error))
|
|
if transport_error_requires_authentication(error)
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
fn transport_error_requires_authentication(error: &DynamicTransportError) -> bool {
|
|
error
|
|
.error
|
|
.downcast_ref::<StreamableHttpError<StreamableHttpClientAdapterError>>()
|
|
.is_some_and(|error| match error {
|
|
StreamableHttpError::AuthRequired(_) => true,
|
|
StreamableHttpError::Auth(auth_error) => auth_error_requires_authentication(auth_error),
|
|
StreamableHttpError::Client(StreamableHttpClientAdapterError::HttpRequest(error)) => {
|
|
exec_server_error_requires_authentication(error)
|
|
}
|
|
_ => false,
|
|
})
|
|
}
|
|
|
|
fn exec_server_error_requires_authentication(error: &ExecServerError) -> bool {
|
|
matches!(error, ExecServerError::AuthenticationRequired(_))
|
|
}
|
|
|
|
fn auth_error_requires_authentication(error: &AuthError) -> bool {
|
|
matches!(
|
|
error,
|
|
AuthError::AuthorizationRequired | AuthError::TokenExpired
|
|
)
|
|
}
|