#![allow(dead_code)]
use super::filesystem::to_posix_path;
use super::permission_rule_parser::{
permission_rule_value_from_string, permission_rule_value_to_string,
};
use crate::types::permissions::{
AdditionalWorkingDirectory, PermissionBehavior, PermissionRuleValue,
PermissionUpdate as PermissionUpdateType, PermissionUpdateDestination, ToolPermissionContext,
ToolPermissionRulesBySource,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub use crate::types::permissions::PermissionUpdate;
pub fn extract_rules(updates: &[PermissionUpdateType]) -> Vec<PermissionRuleValue> {
updates
.iter()
.flat_map(|update| {
if let PermissionUpdateType::AddRules { rules, .. } = update {
rules.clone()
} else {
vec![]
}
})
.collect()
}
pub fn has_rules(updates: &[PermissionUpdateType]) -> bool {
!extract_rules(updates).is_empty()
}
pub fn apply_permission_update(
context: ToolPermissionContext,
update: &PermissionUpdateType,
) -> ToolPermissionContext {
match update {
PermissionUpdateType::SetMode { mode, .. } => {
log::debug!("Applying permission update: Setting mode to '{}'", mode);
ToolPermissionContext {
mode: mode.clone(),
..context
}
}
PermissionUpdateType::AddRules {
rules,
behavior,
destination,
} => {
let rule_strings: Vec<String> =
rules.iter().map(permission_rule_value_to_string).collect();
log::debug!(
"Applying permission update: Adding {} {:?} rule(s) to destination '{:?}': {:?}",
rules.len(),
behavior,
destination,
rule_strings,
);
let mut ctx = context.clone();
add_rules_to_context(&mut ctx, behavior, destination, &rule_strings);
ctx
}
PermissionUpdateType::ReplaceRules {
rules,
behavior,
destination,
} => {
let rule_strings: Vec<String> =
rules.iter().map(permission_rule_value_to_string).collect();
log::debug!(
"Replacing all {:?} rules for destination '{:?}' with {} rule(s): {:?}",
behavior,
destination,
rules.len(),
rule_strings,
);
let mut ctx = context.clone();
replace_rules_in_context(&mut ctx, behavior, destination, &rule_strings);
ctx
}
PermissionUpdateType::AddDirectories {
directories,
destination,
} => {
log::debug!(
"Applying permission update: Adding {} director{} with destination '{:?}'",
directories.len(),
if directories.len() == 1 { "y" } else { "ies" },
destination,
);
let mut new_dirs = context.additional_working_directories.clone();
for dir in directories {
new_dirs.insert(
dir.clone(),
AdditionalWorkingDirectory {
path: dir.clone(),
source: crate::types::permissions::PermissionRuleSource::Session,
},
);
}
ToolPermissionContext {
additional_working_directories: new_dirs,
..context
}
}
PermissionUpdateType::RemoveRules {
rules,
behavior,
destination,
} => {
let rule_strings: Vec<String> =
rules.iter().map(permission_rule_value_to_string).collect();
log::debug!(
"Applying permission update: Removing {} {:?} rule(s) from source '{:?}': {:?}",
rules.len(),
behavior,
destination,
rule_strings,
);
let mut ctx = context.clone();
remove_rules_from_context(&mut ctx, behavior, destination, &rule_strings);
ctx
}
PermissionUpdateType::RemoveDirectories { directories, .. } => {
log::debug!(
"Applying permission update: Removing {} director{}",
directories.len(),
if directories.len() == 1 { "y" } else { "ies" },
);
let mut new_dirs = context.additional_working_directories.clone();
for dir in directories {
new_dirs.remove(dir);
}
ToolPermissionContext {
additional_working_directories: new_dirs,
..context
}
}
}
}
fn add_rules_to_context(
ctx: &mut ToolPermissionContext,
behavior: &PermissionBehavior,
destination: &PermissionUpdateDestination,
rule_strings: &[String],
) {
let rules = get_rules_mut(ctx, behavior, destination);
rules.extend(rule_strings.iter().cloned());
}
fn replace_rules_in_context(
ctx: &mut ToolPermissionContext,
behavior: &PermissionBehavior,
destination: &PermissionUpdateDestination,
rule_strings: &[String],
) {
let rules = get_rules_mut(ctx, behavior, destination);
rules.clear();
rules.extend(rule_strings.iter().cloned());
}
fn remove_rules_from_context(
ctx: &mut ToolPermissionContext,
behavior: &PermissionBehavior,
destination: &PermissionUpdateDestination,
rule_strings: &[String],
) {
let rules = get_rules_mut(ctx, behavior, destination);
let to_remove: std::collections::HashSet<String> = rule_strings.iter().cloned().collect();
rules.retain(|r| !to_remove.contains(r));
}
fn get_rules_mut<'a>(
ctx: &'a mut ToolPermissionContext,
behavior: &PermissionBehavior,
destination: &PermissionUpdateDestination,
) -> &'a mut Vec<String> {
let rules_by_source = match behavior {
PermissionBehavior::Allow => &mut ctx.always_allow_rules,
PermissionBehavior::Deny => &mut ctx.always_deny_rules,
PermissionBehavior::Ask => &mut ctx.always_ask_rules,
};
match destination {
PermissionUpdateDestination::UserSettings => &mut rules_by_source.user_settings,
PermissionUpdateDestination::ProjectSettings => &mut rules_by_source.project_settings,
PermissionUpdateDestination::LocalSettings => &mut rules_by_source.local_settings,
PermissionUpdateDestination::Session => &mut rules_by_source.session,
PermissionUpdateDestination::CliArg => &mut rules_by_source.cli_arg,
}
.get_or_insert_with(Vec::new)
}
pub fn apply_permission_updates(
context: ToolPermissionContext,
updates: &[PermissionUpdateType],
) -> ToolPermissionContext {
let mut updated_context = context;
for update in updates {
updated_context = apply_permission_update(updated_context, update);
}
updated_context
}
pub fn supports_persistence(destination: &PermissionUpdateDestination) -> bool {
matches!(
destination,
PermissionUpdateDestination::LocalSettings
| PermissionUpdateDestination::UserSettings
| PermissionUpdateDestination::ProjectSettings
)
}
fn to_editable_source(dest: &PermissionUpdateDestination) -> Option<crate::utils::settings::EditableSettingSource> {
match dest {
PermissionUpdateDestination::LocalSettings => {
Some(crate::utils::settings::EditableSettingSource::LocalSettings)
}
PermissionUpdateDestination::UserSettings => {
Some(crate::utils::settings::EditableSettingSource::UserSettings)
}
PermissionUpdateDestination::ProjectSettings => {
Some(crate::utils::settings::EditableSettingSource::ProjectSettings)
}
_ => None,
}
}
pub fn persist_permission_update(update: &PermissionUpdateType) {
if !supports_persistence(&match_destination(update)) {
return;
}
let dest = match_destination(update);
let source = match to_editable_source(&dest) {
Some(s) => s,
None => return,
};
log::debug!("Persisting permission update: {:?} to {:?}", update.type_name(), source);
let result = match update {
PermissionUpdateType::AddRules { rules, behavior, .. } => {
let rule_strings: Vec<String> = rules
.iter()
.map(|r| permission_rule_value_to_string(r))
.collect();
crate::utils::settings::add_permission_rules_to_settings(
&rule_strings,
behavior.as_str(),
&source,
)
}
PermissionUpdateType::RemoveRules { rules, behavior, .. } => {
let rule_strings: Vec<String> = rules
.iter()
.map(|r| permission_rule_value_to_string(r))
.collect();
crate::utils::settings::remove_permission_rules_from_settings(
&rule_strings,
behavior.as_str(),
&source,
)
}
PermissionUpdateType::ReplaceRules { rules, behavior, .. } => {
let rule_strings: Vec<String> = rules
.iter()
.map(|r| permission_rule_value_to_string(r))
.collect();
crate::utils::settings::replace_permission_rules_in_settings(
&rule_strings,
behavior.as_str(),
&source,
)
}
PermissionUpdateType::SetMode { mode, .. } => {
crate::utils::settings::set_permission_mode_in_settings(mode.as_str(), &source)
}
PermissionUpdateType::AddDirectories { directories, .. } => {
crate::utils::settings::add_directories_to_settings(directories, &source)
}
PermissionUpdateType::RemoveDirectories { directories, .. } => {
crate::utils::settings::remove_directories_from_settings(directories, &source)
}
};
if let Err(e) = result {
log::error!("Failed to persist permission update: {}", e);
}
}
fn match_destination(update: &PermissionUpdateType) -> PermissionUpdateDestination {
match update {
PermissionUpdateType::SetMode { destination, .. }
| PermissionUpdateType::AddRules { destination, .. }
| PermissionUpdateType::ReplaceRules { destination, .. }
| PermissionUpdateType::RemoveRules { destination, .. }
| PermissionUpdateType::AddDirectories { destination, .. }
| PermissionUpdateType::RemoveDirectories { destination, .. } => destination.clone(),
}
}
pub fn persist_permission_updates(updates: &[PermissionUpdateType]) {
for update in updates {
persist_permission_update(update);
}
}
pub fn convert_rules_to_updates(
rules: &[crate::types::permissions::PermissionRule],
update_type: &str,
) -> Vec<PermissionUpdateType> {
let mut grouped: HashMap<String, Vec<PermissionRuleValue>> = HashMap::new();
for rule in rules {
let key = format!("{}:{:?}", rule.source.as_str(), rule.rule_behavior);
grouped
.entry(key)
.or_default()
.push(rule.rule_value.clone());
}
let mut updates = Vec::new();
for (key, rule_values) in grouped {
let parts: Vec<&str> = key.splitn(2, ':').collect();
if parts.len() != 2 {
continue;
}
let behavior = match parts[1] {
"Allow" => PermissionBehavior::Allow,
"Deny" => PermissionBehavior::Deny,
"Ask" => PermissionBehavior::Ask,
_ => continue,
};
updates.push(match update_type {
"addRules" => PermissionUpdateType::AddRules {
rules: rule_values,
behavior,
destination: PermissionUpdateDestination::Session,
},
"replaceRules" => PermissionUpdateType::ReplaceRules {
rules: rule_values,
behavior,
destination: PermissionUpdateDestination::Session,
},
_ => continue,
});
}
updates
}
pub fn create_read_rule_suggestion(
dir_path: &str,
destination: Option<PermissionUpdateDestination>,
) -> Option<PermissionUpdateType> {
let path_for_pattern = to_posix_path(dir_path);
if path_for_pattern == "/" {
return None;
}
let rule_content = if path_for_pattern.starts_with('/') {
format!("/{}/**", path_for_pattern)
} else {
format!("{}/**", path_for_pattern)
};
Some(PermissionUpdateType::AddRules {
rules: vec![PermissionRuleValue {
tool_name: "Read".to_string(),
rule_content: Some(rule_content),
}],
behavior: PermissionBehavior::Allow,
destination: destination.unwrap_or(PermissionUpdateDestination::Session),
})
}