mirror of
https://github.com/openai/codex.git
synced 2026-11-11 05:00:10 +00:00
## What changed - Resolve stored enterprise IdP sessions against discovered authorization metadata, requiring the configured issuer, public-client authentication, and supported ID-JAG token exchange capabilities. - Bind OIDC identity claims and MCP resource indicators to their configured issuer, client, and server, and require reauthentication when pinned keyring credentials are removed or replaced. - Hold the credential lock while rereading refresh tokens, and isolate enterprise credentials by reserved namespace and Codex home. ## Testing - Cover metadata and claim validation, resource binding, credential replacement and keyring failures, refresh locking, expired ID tokens, and credential namespace isolation. GitOrigin-RevId: edce3c6159f7d6831edf72e9608b3fc3f5823c83
79 lines
3.0 KiB
Rust
79 lines
3.0 KiB
Rust
//! Reread enterprise credentials without changing the connection's pinned identity.
|
|
|
|
use anyhow::Result;
|
|
use anyhow::bail;
|
|
use codex_keyring_store::DefaultKeyringStore;
|
|
use codex_keyring_store::KeyringStore;
|
|
|
|
use super::ResolvedOAuthCredentialStore;
|
|
use super::StoredOAuthCredentialSnapshot;
|
|
use super::StoredOAuthTokens;
|
|
use crate::ema_auth_policy::ema_reauthentication_required;
|
|
use crate::ema_claims::OidcClaims;
|
|
use crate::ema_claims::oidc_identity;
|
|
|
|
pub(crate) fn stored_oidc_identity(tokens: &StoredOAuthTokens) -> Result<OidcClaims> {
|
|
let assertion = tokens
|
|
.token_response
|
|
.0
|
|
.extra_fields()
|
|
.0
|
|
.get("id_token")
|
|
.and_then(serde_json::Value::as_str)
|
|
.ok_or_else(|| {
|
|
ema_reauthentication_required(
|
|
"enterprise IdP session has no OIDC ID token; sign in again",
|
|
)
|
|
})?;
|
|
// The ID token binds the login identity. Its expiry does not determine
|
|
// whether the IdP will accept the independently valid refresh token.
|
|
oidc_identity(assertion, &tokens.url, &tokens.client_id).map_err(|error| {
|
|
ema_reauthentication_required("stored enterprise IdP identity is invalid; sign in again")
|
|
.context(error.to_string())
|
|
})
|
|
}
|
|
|
|
impl StoredOAuthCredentialSnapshot {
|
|
/// Reject deletion or replacement before using this session's cached resource bearer.
|
|
/// Call from a blocking task: the pinned keyring backend may perform blocking I/O.
|
|
pub fn validate_current_ema_credentials(&self) -> Result<()> {
|
|
self.load_ema_credentials(&DefaultKeyringStore).map(|_| ())
|
|
}
|
|
|
|
/// Reread only the pinned keyring authority; exchange callers hold its credential lock.
|
|
pub(crate) fn load_ema_credentials<K: KeyringStore + Clone + 'static>(
|
|
&self,
|
|
keyring_store: &K,
|
|
) -> Result<StoredOAuthTokens> {
|
|
if !matches!(self.store, ResolvedOAuthCredentialStore::Keyring(_)) {
|
|
bail!("enterprise IdP credentials require keyring storage");
|
|
}
|
|
let previous = &self.credentials;
|
|
let mut latest = self
|
|
.store
|
|
.load(keyring_store, &previous.server_name, &previous.url)?
|
|
.ok_or_else(|| {
|
|
ema_reauthentication_required(
|
|
"enterprise IdP credentials were removed; sign in again",
|
|
)
|
|
})?;
|
|
// There is no refresh-token rotation writer in the supported EMA profile.
|
|
// Pin the whole atomic login record, not just the claims of its ID token.
|
|
latest.token_response.0.set_expires_in(None);
|
|
if latest != *previous
|
|
|| previous.bound_issuer() != Some(previous.url.as_str())
|
|
|| !latest.has_refresh_token()
|
|
{
|
|
return Err(ema_reauthentication_required(
|
|
"enterprise IdP identity changed; sign in again and reconnect",
|
|
));
|
|
}
|
|
stored_oidc_identity(&latest)?;
|
|
Ok(latest)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "ema_identity_tests.rs"]
|
|
mod tests;
|