Files
codex/codex-rs/config/src/auth_policy.rs
T
Abhinav 2994f545a7 Enforce managed authentication requirements locally (#37132)
## Why

Authentication restrictions must apply before stored or environment-provided credentials can be used, including during bootstrap before cloud requirements are fetched.

## What changed

- Add local `requirements.toml` allowlists for login methods and ChatGPT workspaces. Ignore these fields in cloud-provided requirements.
- Combine managed workspace allowlists with existing workspace restrictions by intersection, and fail closed when the resulting policy permits no usable login method.
- Centralize policy checks in the authentication manager so CLI, TUI, app-server, external-auth, and credential-loading paths consistently reject disallowed authentication before token hydration or network requests.

## Testing

- Cover policy composition, workspace intersection, invalid stored and external credentials, bootstrap enforcement, and login endpoint restrictions.

GitOrigin-RevId: efef22b248f3c3333e9aa55423e539efa2d2dd48
2026-08-05 18:09:26 +00:00

62 lines
2.0 KiB
Rust

use codex_protocol::config_types::ForcedLoginMethod;
/// Authentication restrictions supplied by locally managed requirements.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ManagedAuthPolicy {
pub allowed_login_methods: Option<Vec<ForcedLoginMethod>>,
pub allowed_chatgpt_workspaces: Option<Vec<String>>,
}
impl ManagedAuthPolicy {
pub fn allows_login_method(
&self,
method: ForcedLoginMethod,
forced_login_method: Option<ForcedLoginMethod>,
forced_workspaces: Option<&[String]>,
) -> bool {
forced_login_method.is_none_or(|forced| forced == method)
&& self
.allowed_login_methods
.as_ref()
.is_none_or(|allowed| allowed.contains(&method))
&& (method != ForcedLoginMethod::Chatgpt
|| self
.effective_chatgpt_workspaces(forced_workspaces)
.is_none_or(|workspaces| !workspaces.is_empty()))
}
pub fn allowed_login_methods(
&self,
forced_login_method: Option<ForcedLoginMethod>,
forced_workspaces: Option<&[String]>,
) -> Vec<ForcedLoginMethod> {
[ForcedLoginMethod::Api, ForcedLoginMethod::Chatgpt]
.into_iter()
.filter(|method| {
self.allows_login_method(*method, forced_login_method, forced_workspaces)
})
.collect()
}
pub fn effective_chatgpt_workspaces(
&self,
forced_workspaces: Option<&[String]>,
) -> Option<Vec<String>> {
match (
forced_workspaces,
self.allowed_chatgpt_workspaces.as_deref(),
) {
(Some(forced), Some(allowed)) => Some(
forced
.iter()
.filter(|workspace| allowed.contains(workspace))
.cloned()
.collect(),
),
(Some(forced), None) => Some(forced.to_vec()),
(None, Some(allowed)) => Some(allowed.to_vec()),
(None, None) => None,
}
}
}