mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Expose permission profile resolution in the core API (#39632)
## What changed - Add `Config::resolve_permission_profile` to resolve named profiles from the effective configuration and managed requirements. - Make active-profile network proxy lookup public and re-export `CodexThreadSettingsOverrides` from `codex-core-api`. - Cover configured and managed profile inheritance, duplicate-name rejection, and preservation of managed network settings. GitOrigin-RevId: ffb5f54a932ef1a4b624677b5f051ecdcd1134b8
This commit is contained in:
@@ -27,6 +27,7 @@ pub use codex_config::types::TuiPetAnchor;
|
||||
pub use codex_config::types::UriBasedFileOpener;
|
||||
pub use codex_core::CodexAppsToolsCache;
|
||||
pub use codex_core::CodexThread;
|
||||
pub use codex_core::CodexThreadSettingsOverrides;
|
||||
pub use codex_core::EnvironmentConfig;
|
||||
pub use codex_core::EnvironmentMcpPolicy;
|
||||
pub use codex_core::EnvironmentNetworkPolicy;
|
||||
|
||||
@@ -29,6 +29,7 @@ use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::config_toml::ProjectConfig;
|
||||
use codex_config::loader::load_config_layers_state;
|
||||
use codex_config::loader::load_requirements_toml;
|
||||
use codex_config::permissions_toml::PermissionProfileToml;
|
||||
use codex_config::test_support::CloudConfigBundleFixture;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::Feature;
|
||||
@@ -1761,6 +1762,48 @@ async fn load_config_layers_includes_cloud_config_bundle() -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_permission_profile_from_effective_configuration() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
tokio::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
default_permissions = "configured-profile"
|
||||
|
||||
[permissions.configured-profile]
|
||||
description = "Configured profile"
|
||||
extends = ":read-only"
|
||||
|
||||
[permissions.configured-profile.network]
|
||||
enabled = true
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.resolve_permission_profile("configured-profile")?,
|
||||
toml::from_str::<PermissionProfileToml>(
|
||||
r#"
|
||||
description = "Configured profile"
|
||||
extends = ":read-only"
|
||||
|
||||
[filesystem]
|
||||
":root" = "read"
|
||||
|
||||
[network]
|
||||
enabled = true
|
||||
"#
|
||||
)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn system_requirements_define_managed_permission_profiles() -> anyhow::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
@@ -1813,6 +1856,27 @@ enable_socks5 = false
|
||||
.allowed_permission_profiles,
|
||||
Some(BTreeMap::from([("managed-standard".to_string(), true)]))
|
||||
);
|
||||
assert_eq!(
|
||||
config.resolve_permission_profile("managed-standard")?,
|
||||
toml::from_str::<PermissionProfileToml>(
|
||||
r#"
|
||||
extends = ":workspace"
|
||||
|
||||
[filesystem]
|
||||
":root" = "read"
|
||||
":slash_tmp" = "write"
|
||||
":tmpdir" = "write"
|
||||
|
||||
[filesystem.":workspace_roots"]
|
||||
"." = "write"
|
||||
|
||||
[network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43128"
|
||||
enable_socks5 = false
|
||||
"#
|
||||
)?
|
||||
);
|
||||
let active_permission_profile = config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
@@ -1825,11 +1889,162 @@ enable_socks5 = false
|
||||
config.permissions.permission_profile(),
|
||||
)?
|
||||
.expect("managed profile should retain its network proxy configuration");
|
||||
assert_eq!(Some(&network), config.permissions.network.as_ref());
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
|
||||
assert!(!network.socks_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_permission_profile_inherits_across_configured_and_managed_profiles()
|
||||
-> anyhow::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
tokio::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
default_permissions = "configured-child"
|
||||
|
||||
[permissions.configured-parent]
|
||||
extends = ":read-only"
|
||||
|
||||
[permissions.configured-parent.network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43128"
|
||||
|
||||
[permissions.configured-child]
|
||||
description = "Configured child"
|
||||
extends = "managed-parent"
|
||||
|
||||
[permissions.configured-child.network]
|
||||
enable_socks5 = false
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
let requirements_path = tmp.path().join("requirements.toml");
|
||||
tokio::fs::write(
|
||||
&requirements_path,
|
||||
r#"
|
||||
[permissions.managed-parent]
|
||||
extends = ":read-only"
|
||||
|
||||
[permissions.managed-parent.network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43129"
|
||||
|
||||
[permissions.managed-child]
|
||||
description = "Managed child"
|
||||
extends = "configured-parent"
|
||||
|
||||
[permissions.managed-child.network]
|
||||
enable_socks5 = false
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
overrides.system_requirements_path = Some(requirements_path);
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home)
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(overrides)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
for (profile_name, expected) in [
|
||||
(
|
||||
"configured-child",
|
||||
r#"
|
||||
description = "Configured child"
|
||||
extends = "managed-parent"
|
||||
|
||||
[filesystem]
|
||||
":root" = "read"
|
||||
|
||||
[network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43129"
|
||||
enable_socks5 = false
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"managed-child",
|
||||
r#"
|
||||
description = "Managed child"
|
||||
extends = "configured-parent"
|
||||
|
||||
[filesystem]
|
||||
":root" = "read"
|
||||
|
||||
[network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43128"
|
||||
enable_socks5 = false
|
||||
"#,
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
config.resolve_permission_profile(profile_name)?,
|
||||
toml::from_str::<PermissionProfileToml>(expected)?,
|
||||
"{profile_name}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_permission_profile_rejects_duplicate_configured_and_managed_profiles()
|
||||
-> anyhow::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
tokio::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
"default_permissions = \":read-only\"\n",
|
||||
)
|
||||
.await?;
|
||||
let requirements_path = tmp.path().join("requirements.toml");
|
||||
tokio::fs::write(
|
||||
&requirements_path,
|
||||
r#"
|
||||
[permissions.duplicate-profile]
|
||||
extends = ":read-only"
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
overrides.system_requirements_path = Some(requirements_path);
|
||||
let mut config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.clone())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(overrides)
|
||||
.build()
|
||||
.await?;
|
||||
let config_toml = AbsolutePathBuf::from_absolute_path(codex_home.join(CONFIG_TOML_FILE))?;
|
||||
config.config_layer_stack = config.config_layer_stack.with_user_config(
|
||||
&config_toml,
|
||||
toml::from_str::<TomlValue>(
|
||||
r#"
|
||||
default_permissions = ":read-only"
|
||||
|
||||
[permissions.duplicate-profile]
|
||||
extends = ":workspace"
|
||||
"#,
|
||||
)?,
|
||||
)?;
|
||||
|
||||
let error = config
|
||||
.resolve_permission_profile("duplicate-profile")
|
||||
.expect_err("duplicate configured and managed profiles should be rejected");
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"requirements.toml permissions profile `duplicate-profile` conflicts with a config-defined profile of the same name"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn system_allowed_permission_profiles_select_managed_default_without_local_default()
|
||||
-> anyhow::Result<()> {
|
||||
|
||||
@@ -31,6 +31,7 @@ use codex_config::config_toml::ThreadStoreToml;
|
||||
use codex_config::config_toml::validate_model_providers;
|
||||
use codex_config::loader::load_config_layers_state;
|
||||
use codex_config::loader::project_trust_key;
|
||||
use codex_config::permissions_toml::PermissionProfileToml;
|
||||
use codex_config::permissions_toml::PermissionsToml;
|
||||
use codex_config::sandbox_mode_requirement_for_permission_profile;
|
||||
use codex_config::types::ApprovalsReviewer;
|
||||
@@ -4315,7 +4316,33 @@ impl Config {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn network_proxy_spec_for_active_permission_profile(
|
||||
/// Resolves a named permission profile from effective config and managed requirements.
|
||||
pub fn resolve_permission_profile(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
) -> std::io::Result<PermissionProfileToml> {
|
||||
let cfg: ConfigToml = self
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.try_into()
|
||||
.map_err(|err| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"failed to read effective config for selected permission profile: {err}"
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let permissions = merge_managed_permission_profiles(
|
||||
cfg.permissions.as_ref(),
|
||||
self.config_layer_stack.requirements_toml(),
|
||||
)?
|
||||
.unwrap_or_default();
|
||||
|
||||
permissions::resolve_permission_profile(&permissions, profile_name)
|
||||
}
|
||||
|
||||
pub fn network_proxy_spec_for_active_permission_profile(
|
||||
&self,
|
||||
active_permission_profile: &ActivePermissionProfile,
|
||||
permission_profile: &PermissionProfile,
|
||||
|
||||
Reference in New Issue
Block a user