mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
tui: add named permission profile picker (#21559)
## Why Users who opt into named permission profiles through `default_permissions` or `[permissions.*]` should stay in named-profile semantics when they open `/permissions`. The legacy picker rewrites those users into anonymous preset state, which loses the active profile identity and hides custom configured profiles. ## What changed - Switch `/permissions` to a profile-aware picker when profile mode is active. - Show friendly built-in labels instead of raw `:` profile syntax. - Include configured custom profiles and their descriptions in the picker. - Route selections through the split TUI profile-selection flow below this PR. - Add TUI snapshots and regression coverage for built-ins, custom profiles, and conflicting legacy runtime overrides. ## Stack 1. [#22931](https://github.com/openai/codex/pull/22931): runtime/session/network propagation for active permission profiles. 2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection plumbing and guardrail flow. 3. **This PR**: profile-aware `/permissions` menu and custom profile display. ## UX impact In profile mode, `/permissions` shows the same human-facing built-ins users already know: ```text Default Auto-review Full Access Read Only locked-down web-enabled ``` Selecting `locked-down` keeps `active_permission_profile = Some("locked-down")`; selecting a built-in keeps the friendly label while switching to its named built-in profile. ## Screenshots Live `$test-tui` smoke screenshots uploaded through GitHub attachments: **Profile mode with built-ins and custom profiles** <img width="832" alt="Profile mode permissions picker with custom profiles" src="https://github.com/user-attachments/assets/58b72431-418c-4839-9e39-575076db4c8f" /> **Legacy mode remains anonymous preset picker** <img width="1232" alt="Legacy permissions picker" src="https://github.com/user-attachments/assets/95f413ab-4cee-411c-9afb-92580a885c97" /> <img width="1296" height="906" alt="image" src="https://github.com/user-attachments/assets/ea381a78-9904-4aa2-828f-b7f2e43f60f2" /> <img width="705" height="207" alt="Screenshot 2026-05-18 at 2 58 00 PM" src="https://github.com/user-attachments/assets/2fa6dd71-0296-449e-a6de-a72d78a1cb70" /> ## Validation - `git diff --cached --check` before commit. - Full test run skipped at the user request while pushing the split stack.
This commit is contained in:
committed by
GitHub
Unverified
parent
ef6528c6c7
commit
f6fd753039
@@ -2078,7 +2078,7 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
|
||||
.await?;
|
||||
|
||||
assert!(config.explicit_permission_profile_mode);
|
||||
assert!(config.custom_permission_profile_ids.is_empty());
|
||||
assert!(config.custom_permission_profiles.is_empty());
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config
|
||||
@@ -2726,7 +2726,7 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
|
||||
entries: BTreeMap::from([(
|
||||
"dev".to_string(),
|
||||
PermissionProfileToml {
|
||||
description: None,
|
||||
description: Some("Workspace access.".to_string()),
|
||||
extends: None,
|
||||
workspace_roots: None,
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
@@ -2751,8 +2751,11 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.custom_permission_profile_ids,
|
||||
vec!["dev".to_string()]
|
||||
config.custom_permission_profiles,
|
||||
vec![CustomPermissionProfileSummary {
|
||||
id: "dev".to_string(),
|
||||
description: Some("Workspace access.".to_string()),
|
||||
}]
|
||||
);
|
||||
let memories_root = AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(
|
||||
codex_home.path().join("memories"),
|
||||
|
||||
@@ -586,8 +586,8 @@ pub struct Config {
|
||||
/// of the legacy `sandbox_mode` syntax.
|
||||
pub explicit_permission_profile_mode: bool,
|
||||
|
||||
/// User-defined permission profile IDs available from effective config.
|
||||
pub custom_permission_profile_ids: Vec<String>,
|
||||
/// User-defined permission profiles available from effective config.
|
||||
pub custom_permission_profiles: Vec<CustomPermissionProfileSummary>,
|
||||
|
||||
/// Configures who approval requests are routed to for review once they have
|
||||
/// been escalated. This does not disable separate safety checks such as
|
||||
@@ -1902,6 +1902,12 @@ pub struct AgentRoleConfig {
|
||||
pub nickname_candidates: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CustomPermissionProfileSummary {
|
||||
pub id: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
fn resolve_tool_suggest_config(
|
||||
config_toml: &ConfigToml,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
@@ -2638,11 +2644,18 @@ impl Config {
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Profiles)
|
||||
);
|
||||
let custom_permission_profile_ids = cfg
|
||||
let custom_permission_profiles = cfg
|
||||
.permissions
|
||||
.as_ref()
|
||||
.map_or_else(Vec::new, |permissions| {
|
||||
permissions.entries.keys().cloned().collect()
|
||||
permissions
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(id, profile)| CustomPermissionProfileSummary {
|
||||
id: id.clone(),
|
||||
description: profile.description.clone(),
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
let using_implicit_builtin_profile = permission_config_syntax.is_none()
|
||||
&& effective_permission_selection.selected_profile_id.is_none();
|
||||
@@ -3377,7 +3390,7 @@ impl Config {
|
||||
windows_sandbox_private_desktop,
|
||||
},
|
||||
explicit_permission_profile_mode,
|
||||
custom_permission_profile_ids,
|
||||
custom_permission_profiles,
|
||||
approvals_reviewer: constrained_approvals_reviewer.value(),
|
||||
enforce_residency: enforce_residency.value,
|
||||
notify: cfg.notify,
|
||||
|
||||
@@ -179,7 +179,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
||||
Constrained::allow_any(PermissionProfile::read_only()),
|
||||
)?,
|
||||
explicit_permission_profile_mode: false,
|
||||
custom_permission_profile_ids: Vec::new(),
|
||||
custom_permission_profiles: Vec::new(),
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
|
||||
hide_agent_reasoning: false,
|
||||
|
||||
@@ -372,6 +372,7 @@ mod model_popups;
|
||||
mod notifications;
|
||||
use self::notifications::Notification;
|
||||
mod permission_popups;
|
||||
mod permissions_menu;
|
||||
mod protocol;
|
||||
mod protocol_requests;
|
||||
mod rate_limits;
|
||||
|
||||
@@ -14,6 +14,11 @@ impl ChatWidget {
|
||||
|
||||
/// Open a popup to choose the permissions mode.
|
||||
pub(crate) fn open_permissions_popup(&mut self) {
|
||||
if self.config.explicit_permission_profile_mode {
|
||||
self.open_permission_profiles_popup();
|
||||
return;
|
||||
}
|
||||
|
||||
let include_read_only = cfg!(target_os = "windows");
|
||||
let current_approval =
|
||||
AskForApproval::from(self.config.permissions.approval_policy.value());
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
use super::*;
|
||||
|
||||
impl ChatWidget {
|
||||
pub(super) fn open_permission_profiles_popup(&mut self) {
|
||||
let active_profile_id = self
|
||||
.config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.map(|profile| profile.id);
|
||||
let presets = builtin_approval_presets();
|
||||
let Some(read_only) = presets.iter().find(|preset| preset.id == "read-only") else {
|
||||
self.add_error_message(
|
||||
"Internal error: missing the 'read-only' approval preset.".to_string(),
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(default) = presets.iter().find(|preset| preset.id == "auto") else {
|
||||
self.add_error_message(
|
||||
"Internal error: missing the 'auto' approval preset.".to_string(),
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(full_access) = presets.iter().find(|preset| preset.id == "full-access") else {
|
||||
self.add_error_message(
|
||||
"Internal error: missing the 'full-access' approval preset.".to_string(),
|
||||
);
|
||||
return;
|
||||
};
|
||||
let mut items = vec![
|
||||
self.builtin_permission_mode_selection_item(
|
||||
default,
|
||||
":workspace",
|
||||
default
|
||||
.description
|
||||
.replace(" (Identical to Agent mode)", ""),
|
||||
AskForApproval::from(default.approval),
|
||||
ApprovalsReviewer::User,
|
||||
),
|
||||
];
|
||||
if self.config.features.enabled(Feature::GuardianApproval) {
|
||||
items.push(self.builtin_permission_mode_selection_item(
|
||||
default,
|
||||
":workspace",
|
||||
AUTO_REVIEW_DESCRIPTION.to_string(),
|
||||
AskForApproval::OnRequest,
|
||||
ApprovalsReviewer::AutoReview,
|
||||
));
|
||||
}
|
||||
items.push(self.builtin_permission_mode_selection_item(
|
||||
full_access,
|
||||
":danger-no-sandbox",
|
||||
full_access.description.to_string(),
|
||||
AskForApproval::from(full_access.approval),
|
||||
ApprovalsReviewer::User,
|
||||
));
|
||||
items.push(self.builtin_permission_mode_selection_item(
|
||||
read_only,
|
||||
":read-only",
|
||||
read_only.description.to_string(),
|
||||
AskForApproval::from(read_only.approval),
|
||||
ApprovalsReviewer::User,
|
||||
));
|
||||
items.extend(
|
||||
self.config
|
||||
.custom_permission_profiles
|
||||
.iter()
|
||||
.map(|profile| {
|
||||
Self::permission_profile_selection_item(
|
||||
&profile.id,
|
||||
&profile.id,
|
||||
profile
|
||||
.description
|
||||
.as_deref()
|
||||
.unwrap_or("Configured permission profile."),
|
||||
active_profile_id.as_deref(),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Update Model Permissions".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(()),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn builtin_permission_mode_selection_item(
|
||||
&self,
|
||||
preset: &ApprovalPreset,
|
||||
id: &str,
|
||||
description: String,
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
) -> SelectionItem {
|
||||
let label = if approvals_reviewer == ApprovalsReviewer::AutoReview {
|
||||
"Auto-review"
|
||||
} else {
|
||||
preset.label
|
||||
};
|
||||
let active_profile_id = self
|
||||
.config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.map(|profile| profile.id);
|
||||
let current_approval =
|
||||
AskForApproval::from(self.config.permissions.approval_policy.value());
|
||||
let current_reviewer = self.config.approvals_reviewer;
|
||||
let profile_id = id.to_string();
|
||||
let selection = PermissionProfileSelection {
|
||||
profile_id,
|
||||
approval_policy: Some(approval_policy),
|
||||
approvals_reviewer: Some(approvals_reviewer),
|
||||
display_label: label.to_string(),
|
||||
};
|
||||
SelectionItem {
|
||||
name: label.to_string(),
|
||||
description: Some(description),
|
||||
is_current: active_profile_id.as_deref() == Some(id)
|
||||
&& current_approval == approval_policy
|
||||
&& current_reviewer == approvals_reviewer,
|
||||
actions: self.permission_mode_actions(
|
||||
preset,
|
||||
label.to_string(),
|
||||
approvals_reviewer,
|
||||
Some(selection),
|
||||
/*return_to_permissions*/ true,
|
||||
),
|
||||
dismiss_on_select: true,
|
||||
disabled_reason: self
|
||||
.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.can_set(&approval_policy.to_core())
|
||||
.err()
|
||||
.map(|err| err.to_string())
|
||||
.or_else(|| {
|
||||
self.config
|
||||
.permissions
|
||||
.can_set_permission_profile(&preset.permission_profile)
|
||||
.err()
|
||||
.map(|err| err.to_string())
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn permission_profile_selection_item(
|
||||
label: &str,
|
||||
id: &str,
|
||||
description: &str,
|
||||
active_profile_id: Option<&str>,
|
||||
) -> SelectionItem {
|
||||
let id_for_action = id.to_string();
|
||||
let selection = PermissionProfileSelection {
|
||||
profile_id: id_for_action.clone(),
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
display_label: id_for_action,
|
||||
};
|
||||
SelectionItem {
|
||||
name: label.to_string(),
|
||||
description: Some(description.to_string()),
|
||||
is_current: active_profile_id == Some(id),
|
||||
actions: Self::permission_profile_selection_actions(selection),
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/permissions.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Update Model Permissions
|
||||
|
||||
› 1. Default (current) Codex can read and edit files in the current
|
||||
workspace, and run commands. Approval is required to
|
||||
access the internet or edit other files.
|
||||
2. Auto-review Same workspace-write permissions as Default, but
|
||||
eligible `on-request` approvals are routed through the
|
||||
auto-reviewer subagent.
|
||||
3. Full Access Codex can edit files outside this workspace and access
|
||||
the internet without asking for approval. Exercise
|
||||
caution when using.
|
||||
4. Read Only Codex can read files in the current workspace.
|
||||
Approval is required to edit files or access the
|
||||
internet.
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/permissions.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Update Model Permissions
|
||||
|
||||
1. Default Codex can read and edit files in the current
|
||||
workspace, and run commands. Approval is required
|
||||
to access the internet or edit other files.
|
||||
2. Auto-review Same workspace-write permissions as Default, but
|
||||
eligible `on-request` approvals are routed through
|
||||
the auto-reviewer subagent.
|
||||
3. Full Access Codex can edit files outside this workspace and
|
||||
access the internet without asking for approval.
|
||||
Exercise caution when using.
|
||||
4. Read Only Codex can read files in the current workspace.
|
||||
Approval is required to edit files or access the
|
||||
internet.
|
||||
› 5. locked-down (current) Inspect and patch only approved workspace files.
|
||||
6. web-enabled Workspace profile with network access.
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use crate::legacy_core::config::CustomPermissionProfileSummary;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -63,6 +65,183 @@ async fn approvals_selection_popup_snapshot() {
|
||||
assert_chatwidget_snapshot!("approvals_selection_popup", popup);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_selection_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new(":workspace"),
|
||||
))
|
||||
.expect("set active profile");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"profile_permissions_selection_popup",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_selection_popup_with_custom_profiles_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.config.custom_permission_profiles = vec![
|
||||
CustomPermissionProfileSummary {
|
||||
id: "locked-down".to_string(),
|
||||
description: Some("Inspect and patch only approved workspace files.".to_string()),
|
||||
},
|
||||
CustomPermissionProfileSummary {
|
||||
id: "web-enabled".to_string(),
|
||||
description: Some("Workspace profile with network access.".to_string()),
|
||||
},
|
||||
];
|
||||
chat.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new("locked-down"),
|
||||
))
|
||||
.expect("set active profile");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"profile_permissions_selection_popup_with_custom_profiles",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_selection_emits_named_profile_event_only() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new(":workspace"),
|
||||
))
|
||||
.expect("set active profile");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(
|
||||
&events[0],
|
||||
AppEvent::SelectPermissionProfile(PermissionProfileSelection {
|
||||
profile_id,
|
||||
approval_policy: Some(AskForApproval::OnRequest),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
display_label,
|
||||
}) if profile_id == ":workspace" && display_label == "Default"
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_selection_emits_active_custom_profile() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.config.custom_permission_profiles = vec![CustomPermissionProfileSummary {
|
||||
id: "locked-down".to_string(),
|
||||
description: None,
|
||||
}];
|
||||
chat.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new("locked-down"),
|
||||
))
|
||||
.expect("set active profile");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(
|
||||
&events[0],
|
||||
AppEvent::SelectPermissionProfile(PermissionProfileSelection {
|
||||
profile_id,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
display_label,
|
||||
}) if profile_id == "locked-down" && display_label == "locked-down"
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_selection_emits_auto_review_mode_event() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new(":workspace"),
|
||||
))
|
||||
.expect("set active profile");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(
|
||||
&events[0],
|
||||
AppEvent::SelectPermissionProfile(PermissionProfileSelection {
|
||||
profile_id,
|
||||
approval_policy: Some(AskForApproval::OnRequest),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||
display_label,
|
||||
}) if profile_id == ":workspace" && display_label == "Auto-review"
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_permissions_full_access_opens_confirmation() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.explicit_permission_profile_mode = true;
|
||||
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
|
||||
chat.config.notices.hide_full_access_warning = None;
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Up));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(
|
||||
&events[0],
|
||||
AppEvent::OpenFullAccessConfirmation {
|
||||
preset,
|
||||
return_to_permissions: true,
|
||||
profile_selection: Some(PermissionProfileSelection {
|
||||
profile_id,
|
||||
approval_policy: Some(AskForApproval::Never),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
display_label,
|
||||
}),
|
||||
} if preset.id == "full-access"
|
||||
&& profile_id == ":danger-no-sandbox"
|
||||
&& display_label == "Full Access"
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
||||
Reference in New Issue
Block a user