Files
codex/codex-rs/model-provider/src/shared_state_test_support.rs
T
smurching-oai 09ec2451bf Add request-scoped OAuth credentials and session lifecycle APIs (#56036)
## What changed

- Add `OAuthSessionRequest` to pair access tokens with their authorization identity and retain rejected-token recovery across transient failures and retries. New sign-ins receive an identity that survives token refresh.
- Expose explicit login, logout, and authorization identity lookup, plus a per-manager option to disable automatic browser login while allowing credential reuse and refresh.
- Move serializable `OAuthSessionConfig` into `codex-model-provider-info`, add an optional `device_authorization_url` field, and share endpoint URL validation through `codex-http-client`.
- Distinguish permission failures, transient token endpoint failures, and invalid responses using `io::ErrorKind`.

## Testing

Add coverage for sign-in identity preservation through refresh retries, removal of saved and cached credentials on logout, and suppression of automatic browser login. Extend token endpoint error classification coverage.

GitOrigin-RevId: ae9f72e787171e179fe139a3b840a9f7e1e46506
2026-10-22 01:07:13 +00:00

131 lines
4.0 KiB
Rust

//! Seeds encrypted gateway credentials for provider and agent integration tests.
#![allow(clippy::expect_used)] // Fixture setup failures should fail the calling test immediately.
use std::sync::Arc;
use std::sync::Mutex;
use codex_keyring_store::CredentialStoreError;
use codex_keyring_store::KeyringStore;
use codex_login::AuthManager;
use codex_login::GatewayAuthConfig;
use codex_login::GatewayAuthManager;
use codex_model_provider_info::ModelProviderInfo;
use codex_secrets::LocalSecretsNamespace;
use codex_secrets::SecretName;
use codex_secrets::SecretScope;
use codex_secrets::SecretsBackendKind;
use codex_secrets::SecretsManager;
use sha2::Digest;
use sha2::Sha256;
use super::process_shared_state;
#[derive(Debug)]
struct TestKeyring(Mutex<Option<String>>);
impl KeyringStore for TestKeyring {
fn load(
&self,
_service: &str,
_account: &str,
) -> std::result::Result<Option<String>, CredentialStoreError> {
Ok(self
.0
.lock()
.expect("gateway test token store lock")
.clone())
}
fn save(
&self,
_service: &str,
_account: &str,
value: &str,
) -> std::result::Result<(), CredentialStoreError> {
*self.0.lock().expect("gateway test token store lock") = Some(value.to_string());
Ok(())
}
fn delete(
&self,
_service: &str,
_account: &str,
) -> std::result::Result<bool, CredentialStoreError> {
Ok(self
.0
.lock()
.expect("gateway test token store lock")
.take()
.is_some())
}
}
/// Seeds a provider's encrypted gateway store with a test-only keyring passphrase.
/// Keep the returned manager alive while requests use this fixture.
pub fn seed_gateway_auth(
info: &ModelProviderInfo,
primary: &AuthManager,
token: serde_json::Value,
) -> Arc<GatewayAuthManager> {
let config = info
.gateway_oauth
.clone()
.expect("gateway test configuration");
let runtime = primary.runtime_config();
let oauth = GatewayAuthConfig {
authorization_url: config.authorization_url,
device_authorization_url: None,
token_url: config.token_url,
client_id: config.client_id,
resource: config.resource,
scopes: config.scopes,
redirect_port: config.redirect_port,
};
// Repeated fixtures sharing a Codex home must use the same encryption key.
let keyring = Arc::new(TestKeyring(Mutex::new(Some(
"gateway-test-passphrase".to_string(),
))));
let secrets = SecretsManager::new_with_keyring_store_and_namespace(
runtime.codex_home.clone(),
SecretsBackendKind::Local,
keyring.clone(),
LocalSecretsNamespace::GatewayOAuth,
);
// Match the credential identity used by GatewayAuthManager's encrypted store.
let mut digest = Sha256::new();
digest.update(runtime.codex_home.to_string_lossy().as_bytes());
digest.update([0]);
for value in [
oauth.authorization_url.as_str(),
oauth.token_url.as_str(),
oauth.client_id.as_str(),
oauth.resource.as_deref().unwrap_or_default(),
] {
digest.update(value.as_bytes());
digest.update([0]);
}
for scope in &oauth.scopes {
digest.update(scope.as_bytes());
digest.update([0]);
}
let name = SecretName::new(&format!("PROVIDER_OAUTH_{:X}", digest.finalize()))
.expect("gateway test credential name");
secrets
.set(&SecretScope::Global, &name, &token.to_string())
.expect("seed encrypted gateway credentials");
let manager = Arc::new(
GatewayAuthManager::new(
oauth.clone(),
runtime.codex_home.clone(),
runtime.auth_route_config.http_client_factory(),
keyring,
)
.expect("gateway test HTTP client"),
);
process_shared_state()
.gateway_managers
.lock()
.expect("gateway test manager registry lock")
.push((oauth, runtime, Arc::downgrade(&manager)));
manager
}