1use super::condition::check_conditions;
16use super::merger::merge_permissions;
17use super::pattern::match_pattern;
18use super::policy::ToolPolicyManager;
19use super::restriction::check_parameter_restrictions;
20use super::types::{
21 PermissionContext, PermissionInheritance, PermissionResult, PermissionScope, RestrictionType,
22 ToolPermission,
23};
24use anyhow::{Context, Result};
25use serde::{Deserialize, Serialize};
26use serde_json::Value;
27use std::collections::HashMap;
28use std::fs::{self, File};
29use std::io::{BufReader, BufWriter};
30use std::path::PathBuf;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct PermissionConfig {
37 pub version: String,
39 pub inheritance: PermissionInheritance,
41 pub permissions: Vec<ToolPermission>,
43}
44
45impl Default for PermissionConfig {
46 fn default() -> Self {
47 Self {
48 version: "1.0.0".to_string(),
49 inheritance: PermissionInheritance::default(),
50 permissions: Vec::new(),
51 }
52 }
53}
54
55const GLOBAL_PERMISSIONS_FILE: &str = "global_permissions.json";
57const PROJECT_PERMISSIONS_FILE: &str = "project_permissions.json";
58
59pub struct ToolPermissionManager {
66 global_permissions: HashMap<String, ToolPermission>,
68 project_permissions: HashMap<String, ToolPermission>,
70 session_permissions: HashMap<String, ToolPermission>,
72 inheritance: PermissionInheritance,
74 config_dir: Option<PathBuf>,
76 template_registry: HashMap<String, Vec<ToolPermission>>,
79 policy_manager: Option<ToolPolicyManager>,
82}
83
84impl ToolPermissionManager {
85 pub fn new(config_dir: Option<PathBuf>) -> Self {
95 Self {
96 global_permissions: HashMap::new(),
97 project_permissions: HashMap::new(),
98 session_permissions: HashMap::new(),
99 inheritance: PermissionInheritance::default(),
100 config_dir,
101 template_registry: HashMap::new(),
102 policy_manager: None,
103 }
104 }
105
106 pub fn with_policy_manager(mut self, policy_manager: ToolPolicyManager) -> Self {
113 self.policy_manager = Some(policy_manager);
114 self
115 }
116
117 pub fn set_policy_manager(&mut self, policy_manager: ToolPolicyManager) {
121 self.policy_manager = Some(policy_manager);
122 }
123
124 pub fn policy_manager(&self) -> Option<&ToolPolicyManager> {
126 self.policy_manager.as_ref()
127 }
128
129 pub fn policy_manager_mut(&mut self) -> Option<&mut ToolPolicyManager> {
131 self.policy_manager.as_mut()
132 }
133
134 pub fn config_dir(&self) -> Option<&PathBuf> {
136 self.config_dir.as_ref()
137 }
138
139 pub fn inheritance(&self) -> &PermissionInheritance {
141 &self.inheritance
142 }
143
144 pub fn get_inheritance(&self) -> PermissionInheritance {
151 self.inheritance.clone()
152 }
153
154 pub fn set_inheritance(&mut self, inheritance: PermissionInheritance) {
164 self.inheritance = inheritance;
165 }
166
167 pub fn is_allowed(
191 &self,
192 tool: &str,
193 params: &HashMap<String, Value>,
194 context: &PermissionContext,
195 ) -> PermissionResult {
196 if let Some(policy_manager) = &self.policy_manager {
198 let decision = policy_manager.is_allowed(tool);
199 if !decision.allowed {
200 return PermissionResult {
201 allowed: false,
202 reason: Some(decision.reason),
203 restricted: false,
204 suggestions: vec![format!(
205 "Tool denied by policy layer: {:?}",
206 decision.source_layer
207 )],
208 matched_rule: None,
209 violations: Vec::new(),
210 };
211 }
212 }
213
214 let global_perms: Vec<ToolPermission> = self.global_permissions.values().cloned().collect();
216 let project_perms: Vec<ToolPermission> =
217 self.project_permissions.values().cloned().collect();
218 let session_perms: Vec<ToolPermission> =
219 self.session_permissions.values().cloned().collect();
220
221 let merged = merge_permissions(
222 &global_perms,
223 &project_perms,
224 &session_perms,
225 &self.inheritance,
226 );
227
228 let mut matching_rules: Vec<&ToolPermission> = merged
230 .iter()
231 .filter(|perm| match_pattern(tool, &perm.tool))
232 .collect();
233
234 matching_rules.sort_by(|a, b| b.priority.cmp(&a.priority));
237
238 for rule in matching_rules {
240 if let Some(expires_at) = rule.expires_at {
242 if context.timestamp > expires_at {
243 continue;
244 }
245 }
246
247 if !check_conditions(&rule.conditions, context) {
249 continue;
250 }
251
252 let restriction_result =
255 check_parameter_restrictions(&rule.parameter_restrictions, params);
256
257 match restriction_result {
258 Ok(()) => {
259 if rule.allowed {
261 return PermissionResult {
262 allowed: true,
263 reason: rule.reason.clone(),
264 restricted: !rule.parameter_restrictions.is_empty(),
265 suggestions: Vec::new(),
266 matched_rule: Some(rule.clone()),
267 violations: Vec::new(),
268 };
269 } else {
270 let suggestions = Self::generate_suggestions(rule, &[]);
272 return PermissionResult {
273 allowed: false,
274 reason: rule.reason.clone().or_else(|| {
275 Some(format!("Tool '{}' is denied by permission rule", tool))
276 }),
277 restricted: false,
278 suggestions,
279 matched_rule: Some(rule.clone()),
280 violations: Vec::new(),
281 };
282 }
283 }
284 Err(violations) => {
285 let suggestions = Self::generate_suggestions(rule, &violations);
287 return PermissionResult {
288 allowed: false,
289 reason: Some(format!(
290 "Parameter restrictions violated for tool '{}'",
291 tool
292 )),
293 restricted: true,
294 suggestions,
295 matched_rule: Some(rule.clone()),
296 violations,
297 };
298 }
299 }
300 }
301
302 PermissionResult {
304 allowed: true,
305 reason: None,
306 restricted: false,
307 suggestions: Vec::new(),
308 matched_rule: None,
309 violations: Vec::new(),
310 }
311 }
312
313 pub fn generate_suggestions(rule: &ToolPermission, violations: &[String]) -> Vec<String> {
324 let mut suggestions = Vec::new();
325
326 if !rule.allowed {
328 if let Some(ref reason) = rule.reason {
329 suggestions.push(format!("Denial reason: {}", reason));
330 }
331
332 if !rule.conditions.is_empty() {
334 suggestions.push(
335 "This tool may be allowed under different conditions. \
336 Check the permission conditions."
337 .to_string(),
338 );
339 }
340
341 match rule.scope {
343 PermissionScope::Session => {
344 suggestions.push(
345 "This is a session-level restriction. \
346 It will be reset when the session ends."
347 .to_string(),
348 );
349 }
350 PermissionScope::Project => {
351 suggestions.push(
352 "This is a project-level restriction. \
353 Check project permission configuration."
354 .to_string(),
355 );
356 }
357 PermissionScope::Global => {
358 suggestions.push(
359 "This is a global restriction. \
360 Contact administrator to modify global permissions."
361 .to_string(),
362 );
363 }
364 }
365 }
366
367 for violation in violations {
369 if violation.contains("whitelist") {
370 suggestions.push(format!(
371 "Parameter value not in allowed list. {}",
372 violation
373 ));
374 } else if violation.contains("blacklist") {
375 suggestions.push(format!(
376 "Parameter value is blocked. Try a different value. {}",
377 violation
378 ));
379 } else if violation.contains("pattern") {
380 suggestions.push(format!(
381 "Parameter value doesn't match required format. {}",
382 violation
383 ));
384 } else if violation.contains("range") {
385 suggestions.push(format!(
386 "Parameter value is out of allowed range. {}",
387 violation
388 ));
389 } else if violation.contains("Required") {
390 suggestions.push(format!("Missing required parameter. {}", violation));
391 } else {
392 suggestions.push(violation.clone());
393 }
394 }
395
396 if !rule.parameter_restrictions.is_empty() && violations.is_empty() {
398 suggestions.push(
399 "This tool has parameter restrictions. \
400 Ensure all parameters meet the requirements."
401 .to_string(),
402 );
403 }
404
405 suggestions
406 }
407
408 pub fn add_permission(&mut self, permission: ToolPermission, scope: PermissionScope) {
414 let key = permission.tool.clone();
415 let mut perm = permission;
416 perm.scope = scope;
417
418 match scope {
419 PermissionScope::Global => {
420 self.global_permissions.insert(key, perm);
421 }
422 PermissionScope::Project => {
423 self.project_permissions.insert(key, perm);
424 }
425 PermissionScope::Session => {
426 self.session_permissions.insert(key, perm);
427 }
428 }
429 }
430
431 pub fn remove_permission(&mut self, tool: &str, scope: Option<PermissionScope>) {
437 match scope {
438 Some(PermissionScope::Global) => {
439 self.global_permissions.remove(tool);
440 }
441 Some(PermissionScope::Project) => {
442 self.project_permissions.remove(tool);
443 }
444 Some(PermissionScope::Session) => {
445 self.session_permissions.remove(tool);
446 }
447 None => {
448 self.global_permissions.remove(tool);
449 self.project_permissions.remove(tool);
450 self.session_permissions.remove(tool);
451 }
452 }
453 }
454
455 pub fn update_permission(
467 &mut self,
468 tool: &str,
469 updates: super::types::ToolPermissionUpdate,
470 scope: PermissionScope,
471 ) -> bool {
472 let permissions = match scope {
473 PermissionScope::Global => &mut self.global_permissions,
474 PermissionScope::Project => &mut self.project_permissions,
475 PermissionScope::Session => &mut self.session_permissions,
476 };
477
478 if let Some(perm) = permissions.get_mut(tool) {
479 if let Some(allowed) = updates.allowed {
481 perm.allowed = allowed;
482 }
483 if let Some(priority) = updates.priority {
484 perm.priority = priority;
485 }
486 if let Some(conditions) = updates.conditions {
487 perm.conditions = conditions;
488 }
489 if let Some(restrictions) = updates.parameter_restrictions {
490 perm.parameter_restrictions = restrictions;
491 }
492 if let Some(reason) = updates.reason {
493 perm.reason = reason;
494 }
495 if let Some(expires_at) = updates.expires_at {
496 perm.expires_at = expires_at;
497 }
498 if let Some(metadata) = updates.metadata {
499 perm.metadata = metadata;
500 }
501 true
502 } else {
503 false
504 }
505 }
506
507 pub fn get_permissions(&self, scope: Option<PermissionScope>) -> Vec<ToolPermission> {
515 match scope {
516 Some(PermissionScope::Global) => self.global_permissions.values().cloned().collect(),
517 Some(PermissionScope::Project) => self.project_permissions.values().cloned().collect(),
518 Some(PermissionScope::Session) => self.session_permissions.values().cloned().collect(),
519 None => {
520 let mut all = Vec::new();
521 all.extend(self.global_permissions.values().cloned());
522 all.extend(self.project_permissions.values().cloned());
523 all.extend(self.session_permissions.values().cloned());
524 all
525 }
526 }
527 }
528
529 pub fn get_tool_permission(&self, tool: &str) -> Option<ToolPermission> {
537 if let Some(perm) = self.session_permissions.get(tool) {
539 return Some(perm.clone());
540 }
541
542 if let Some(perm) = self.project_permissions.get(tool) {
544 return Some(perm.clone());
545 }
546
547 if let Some(perm) = self.global_permissions.get(tool) {
549 return Some(perm.clone());
550 }
551
552 None
553 }
554
555 pub fn permission_counts(&self) -> (usize, usize, usize) {
557 (
558 self.global_permissions.len(),
559 self.project_permissions.len(),
560 self.session_permissions.len(),
561 )
562 }
563
564 pub fn clear_scope(&mut self, scope: PermissionScope) {
566 match scope {
567 PermissionScope::Global => self.global_permissions.clear(),
568 PermissionScope::Project => self.project_permissions.clear(),
569 PermissionScope::Session => self.session_permissions.clear(),
570 }
571 }
572
573 pub fn clear_all(&mut self) {
575 self.global_permissions.clear();
576 self.project_permissions.clear();
577 self.session_permissions.clear();
578 }
579
580 pub fn register_template(&mut self, name: &str, template: Vec<ToolPermission>) {
608 self.template_registry.insert(name.to_string(), template);
609 }
610
611 pub fn apply_template(&mut self, name: &str, scope: PermissionScope) -> bool {
632 let Some(template) = self.template_registry.get(name).cloned() else {
633 return false;
634 };
635
636 for mut perm in template {
637 perm.scope = scope;
638 self.add_permission(perm, scope);
639 }
640
641 true
642 }
643
644 pub fn get_template(&self, name: &str) -> Option<&Vec<ToolPermission>> {
654 self.template_registry.get(name)
655 }
656
657 pub fn remove_template(&mut self, name: &str) -> Option<Vec<ToolPermission>> {
667 self.template_registry.remove(name)
668 }
669
670 pub fn list_templates(&self) -> Vec<&String> {
677 self.template_registry.keys().collect()
678 }
679
680 pub fn has_template(&self, name: &str) -> bool {
690 self.template_registry.contains_key(name)
691 }
692
693 pub fn get_stats(&self) -> super::types::PermissionStats {
711 let all_permissions = self.get_permissions(None);
712
713 let total_permissions = all_permissions.len();
714 let allowed_tools = all_permissions.iter().filter(|p| p.allowed).count();
715 let denied_tools = all_permissions.iter().filter(|p| !p.allowed).count();
716 let conditional_tools = all_permissions
717 .iter()
718 .filter(|p| !p.conditions.is_empty())
719 .count();
720 let restricted_parameters = all_permissions
721 .iter()
722 .filter(|p| !p.parameter_restrictions.is_empty())
723 .count();
724
725 super::types::PermissionStats {
726 total_permissions,
727 allowed_tools,
728 denied_tools,
729 conditional_tools,
730 restricted_parameters,
731 }
732 }
733
734 pub fn query_permissions(&self, filter: super::types::PermissionFilter) -> Vec<ToolPermission> {
754 let all_permissions = self.get_permissions(filter.scope);
755
756 all_permissions
757 .into_iter()
758 .filter(|perm| {
759 if let Some(allowed) = filter.allowed {
761 if perm.allowed != allowed {
762 return false;
763 }
764 }
765
766 if let Some(has_conditions) = filter.has_conditions {
768 let perm_has_conditions = !perm.conditions.is_empty();
769 if perm_has_conditions != has_conditions {
770 return false;
771 }
772 }
773
774 if let Some(has_restrictions) = filter.has_restrictions {
776 let perm_has_restrictions = !perm.parameter_restrictions.is_empty();
777 if perm_has_restrictions != has_restrictions {
778 return false;
779 }
780 }
781
782 if let Some(ref pattern) = filter.tool_pattern {
784 if !match_pattern(&perm.tool, pattern) {
785 return false;
786 }
787 }
788
789 true
790 })
791 .collect()
792 }
793
794 pub fn load_permissions(&mut self) {
811 let Some(config_dir) = &self.config_dir else {
812 return;
813 };
814
815 let global_path = config_dir.join(GLOBAL_PERMISSIONS_FILE);
817 if global_path.exists() {
818 match Self::load_config_file(&global_path) {
819 Ok(config) => {
820 self.inheritance = config.inheritance;
821 for perm in config.permissions {
822 let key = perm.tool.clone();
823 self.global_permissions.insert(key, perm);
824 }
825 }
826 Err(e) => {
827 tracing::warn!(
828 "Failed to load global permissions from {:?}: {}",
829 global_path,
830 e
831 );
832 }
833 }
834 }
835
836 let project_path = config_dir.join(PROJECT_PERMISSIONS_FILE);
838 if project_path.exists() {
839 match Self::load_config_file(&project_path) {
840 Ok(config) => {
841 for perm in config.permissions {
842 let key = perm.tool.clone();
843 self.project_permissions.insert(key, perm);
844 }
845 }
846 Err(e) => {
847 tracing::warn!(
848 "Failed to load project permissions from {:?}: {}",
849 project_path,
850 e
851 );
852 }
853 }
854 }
855
856 }
858
859 fn load_config_file(path: &PathBuf) -> Result<PermissionConfig> {
861 let file = File::open(path)
862 .with_context(|| format!("Failed to open permission config file: {:?}", path))?;
863 let reader = BufReader::new(file);
864 let config: PermissionConfig = serde_json::from_reader(reader)
865 .with_context(|| format!("Failed to parse permission config file: {:?}", path))?;
866 Ok(config)
867 }
868
869 pub fn save_permissions(&self, scope: PermissionScope) -> Result<()> {
888 if scope == PermissionScope::Session {
890 anyhow::bail!("Session permissions cannot be persisted - they are memory-only");
891 }
892
893 let Some(config_dir) = &self.config_dir else {
894 anyhow::bail!("No config directory configured for persistence");
895 };
896
897 fs::create_dir_all(config_dir)
899 .with_context(|| format!("Failed to create config directory: {:?}", config_dir))?;
900
901 let (file_name, permissions) = match scope {
902 PermissionScope::Global => (GLOBAL_PERMISSIONS_FILE, &self.global_permissions),
903 PermissionScope::Project => (PROJECT_PERMISSIONS_FILE, &self.project_permissions),
904 PermissionScope::Session => unreachable!(), };
906
907 let config = PermissionConfig {
908 version: "1.0.0".to_string(),
909 inheritance: self.inheritance.clone(),
910 permissions: permissions.values().cloned().collect(),
911 };
912
913 let file_path = config_dir.join(file_name);
914 let temp_path = file_path.with_extension("tmp");
915
916 let file = File::create(&temp_path)
918 .with_context(|| format!("Failed to create temp file: {:?}", temp_path))?;
919 let writer = BufWriter::new(file);
920 serde_json::to_writer_pretty(writer, &config)
921 .with_context(|| format!("Failed to write permission config: {:?}", temp_path))?;
922
923 fs::rename(&temp_path, &file_path)
925 .with_context(|| format!("Failed to rename temp file to: {:?}", file_path))?;
926
927 Ok(())
928 }
929
930 pub fn get_config_path(&self, scope: PermissionScope) -> Option<PathBuf> {
932 self.config_dir.as_ref().map(|dir| {
933 let file_name = match scope {
934 PermissionScope::Global => GLOBAL_PERMISSIONS_FILE,
935 PermissionScope::Project => PROJECT_PERMISSIONS_FILE,
936 PermissionScope::Session => return dir.join("session_permissions.json"), };
938 dir.join(file_name)
939 })
940 }
941
942 pub fn config_exists(&self, scope: PermissionScope) -> bool {
944 self.get_config_path(scope)
945 .map(|p| p.exists())
946 .unwrap_or(false)
947 }
948
949 pub fn export(&self, scope: Option<PermissionScope>) -> Result<String> {
977 let permissions = self.get_permissions(scope);
978
979 let config = PermissionConfig {
980 version: "1.0.0".to_string(),
981 inheritance: self.inheritance.clone(),
982 permissions,
983 };
984
985 serde_json::to_string_pretty(&config).context("Failed to serialize permissions to JSON")
986 }
987
988 pub fn import(&mut self, config_json: &str, scope: PermissionScope) -> Result<()> {
1010 let config: PermissionConfig = serde_json::from_str(config_json)
1012 .context("Failed to parse permission configuration JSON")?;
1013
1014 Self::validate_config_version(&config.version)?;
1016
1017 for perm in &config.permissions {
1019 Self::validate_permission(perm)?;
1020 }
1021
1022 self.clear_scope(scope);
1025
1026 for mut perm in config.permissions {
1028 perm.scope = scope;
1029 let key = perm.tool.clone();
1030 match scope {
1031 PermissionScope::Global => {
1032 self.global_permissions.insert(key, perm);
1033 }
1034 PermissionScope::Project => {
1035 self.project_permissions.insert(key, perm);
1036 }
1037 PermissionScope::Session => {
1038 self.session_permissions.insert(key, perm);
1039 }
1040 }
1041 }
1042
1043 self.inheritance = config.inheritance;
1045
1046 Ok(())
1047 }
1048
1049 fn validate_config_version(version: &str) -> Result<()> {
1054 match version {
1057 "1.0.0" => Ok(()),
1058 _ => anyhow::bail!(
1059 "Unsupported configuration version: {}. Supported versions: 1.0.0",
1060 version
1061 ),
1062 }
1063 }
1064
1065 fn validate_permission(perm: &ToolPermission) -> Result<()> {
1069 if perm.tool.is_empty() {
1071 anyhow::bail!("Permission tool name cannot be empty");
1072 }
1073
1074 for restriction in &perm.parameter_restrictions {
1076 if restriction.parameter.is_empty() {
1077 anyhow::bail!("Parameter restriction parameter name cannot be empty");
1078 }
1079
1080 if restriction.restriction_type == RestrictionType::Range
1082 && restriction.min.is_none()
1083 && restriction.max.is_none()
1084 {
1085 anyhow::bail!(
1086 "Range restriction for parameter '{}' must have at least min or max",
1087 restriction.parameter
1088 );
1089 }
1090
1091 if restriction.restriction_type == RestrictionType::Pattern
1093 && restriction.pattern.is_none()
1094 {
1095 anyhow::bail!(
1096 "Pattern restriction for parameter '{}' must have a pattern",
1097 restriction.parameter
1098 );
1099 }
1100
1101 if (restriction.restriction_type == RestrictionType::Whitelist
1103 || restriction.restriction_type == RestrictionType::Blacklist)
1104 && restriction.values.is_none()
1105 {
1106 anyhow::bail!(
1107 "{:?} restriction for parameter '{}' must have values",
1108 restriction.restriction_type,
1109 restriction.parameter
1110 );
1111 }
1112 }
1113
1114 Ok(())
1115 }
1116}
1117
1118impl Default for ToolPermissionManager {
1119 fn default() -> Self {
1120 Self::new(None)
1121 }
1122}
1123
1124#[cfg(test)]
1125mod tests {
1126 use super::*;
1127 use crate::permission::types::{
1128 ConditionOperator, ConditionType, ParameterRestriction, PermissionCondition,
1129 RestrictionType,
1130 };
1131
1132 fn create_test_context() -> PermissionContext {
1133 PermissionContext {
1134 working_directory: PathBuf::from("/home/user/project"),
1135 session_id: "test-session".to_string(),
1136 timestamp: 1700000000,
1137 user: Some("testuser".to_string()),
1138 environment: HashMap::new(),
1139 metadata: HashMap::new(),
1140 }
1141 }
1142
1143 fn create_simple_permission(
1144 tool: &str,
1145 allowed: bool,
1146 scope: PermissionScope,
1147 ) -> ToolPermission {
1148 ToolPermission {
1149 tool: tool.to_string(),
1150 allowed,
1151 priority: 0,
1152 conditions: Vec::new(),
1153 parameter_restrictions: Vec::new(),
1154 scope,
1155 reason: None,
1156 expires_at: None,
1157 metadata: HashMap::new(),
1158 }
1159 }
1160
1161 #[test]
1162 fn test_new_manager() {
1163 let manager = ToolPermissionManager::new(None);
1164 assert!(manager.config_dir().is_none());
1165 assert_eq!(manager.permission_counts(), (0, 0, 0));
1166 }
1167
1168 #[test]
1169 fn test_new_manager_with_config_dir() {
1170 let config_dir = PathBuf::from("/tmp/config");
1171 let manager = ToolPermissionManager::new(Some(config_dir.clone()));
1172 assert_eq!(manager.config_dir(), Some(&config_dir));
1173 }
1174
1175 #[test]
1176 fn test_add_permission() {
1177 let mut manager = ToolPermissionManager::new(None);
1178 let perm = create_simple_permission("bash", true, PermissionScope::Global);
1179
1180 manager.add_permission(perm, PermissionScope::Global);
1181
1182 assert_eq!(manager.permission_counts(), (1, 0, 0));
1183 assert!(manager.get_tool_permission("bash").is_some());
1184 }
1185
1186 #[test]
1187 fn test_add_permission_different_scopes() {
1188 let mut manager = ToolPermissionManager::new(None);
1189
1190 manager.add_permission(
1191 create_simple_permission("bash", true, PermissionScope::Global),
1192 PermissionScope::Global,
1193 );
1194 manager.add_permission(
1195 create_simple_permission("file_read", true, PermissionScope::Project),
1196 PermissionScope::Project,
1197 );
1198 manager.add_permission(
1199 create_simple_permission("http_get", true, PermissionScope::Session),
1200 PermissionScope::Session,
1201 );
1202
1203 assert_eq!(manager.permission_counts(), (1, 1, 1));
1204 }
1205
1206 #[test]
1207 fn test_remove_permission_specific_scope() {
1208 let mut manager = ToolPermissionManager::new(None);
1209 manager.add_permission(
1210 create_simple_permission("bash", true, PermissionScope::Global),
1211 PermissionScope::Global,
1212 );
1213 manager.add_permission(
1214 create_simple_permission("bash", false, PermissionScope::Session),
1215 PermissionScope::Session,
1216 );
1217
1218 manager.remove_permission("bash", Some(PermissionScope::Global));
1219
1220 assert_eq!(manager.permission_counts(), (0, 0, 1));
1221 }
1222
1223 #[test]
1224 fn test_remove_permission_all_scopes() {
1225 let mut manager = ToolPermissionManager::new(None);
1226 manager.add_permission(
1227 create_simple_permission("bash", true, PermissionScope::Global),
1228 PermissionScope::Global,
1229 );
1230 manager.add_permission(
1231 create_simple_permission("bash", false, PermissionScope::Session),
1232 PermissionScope::Session,
1233 );
1234
1235 manager.remove_permission("bash", None);
1236
1237 assert_eq!(manager.permission_counts(), (0, 0, 0));
1238 }
1239
1240 #[test]
1241 fn test_get_permissions_by_scope() {
1242 let mut manager = ToolPermissionManager::new(None);
1243 manager.add_permission(
1244 create_simple_permission("bash", true, PermissionScope::Global),
1245 PermissionScope::Global,
1246 );
1247 manager.add_permission(
1248 create_simple_permission("file_read", true, PermissionScope::Project),
1249 PermissionScope::Project,
1250 );
1251
1252 let global = manager.get_permissions(Some(PermissionScope::Global));
1253 let project = manager.get_permissions(Some(PermissionScope::Project));
1254 let all = manager.get_permissions(None);
1255
1256 assert_eq!(global.len(), 1);
1257 assert_eq!(project.len(), 1);
1258 assert_eq!(all.len(), 2);
1259 }
1260
1261 #[test]
1262 fn test_get_tool_permission_priority() {
1263 let mut manager = ToolPermissionManager::new(None);
1264 manager.add_permission(
1265 create_simple_permission("bash", true, PermissionScope::Global),
1266 PermissionScope::Global,
1267 );
1268 manager.add_permission(
1269 create_simple_permission("bash", false, PermissionScope::Session),
1270 PermissionScope::Session,
1271 );
1272
1273 let perm = manager.get_tool_permission("bash").unwrap();
1274 assert!(!perm.allowed);
1276 assert_eq!(perm.scope, PermissionScope::Session);
1277 }
1278
1279 #[test]
1280 fn test_is_allowed_no_rules() {
1281 let manager = ToolPermissionManager::new(None);
1282 let context = create_test_context();
1283 let params = HashMap::new();
1284
1285 let result = manager.is_allowed("any_tool", ¶ms, &context);
1286
1287 assert!(result.allowed);
1288 assert!(result.matched_rule.is_none());
1289 }
1290
1291 #[test]
1292 fn test_is_allowed_explicit_allow() {
1293 let mut manager = ToolPermissionManager::new(None);
1294 manager.add_permission(
1295 create_simple_permission("bash", true, PermissionScope::Global),
1296 PermissionScope::Global,
1297 );
1298
1299 let context = create_test_context();
1300 let params = HashMap::new();
1301
1302 let result = manager.is_allowed("bash", ¶ms, &context);
1303
1304 assert!(result.allowed);
1305 assert!(result.matched_rule.is_some());
1306 }
1307
1308 #[test]
1309 fn test_is_allowed_explicit_deny() {
1310 let mut manager = ToolPermissionManager::new(None);
1311 let mut perm = create_simple_permission("bash", false, PermissionScope::Global);
1312 perm.reason = Some("Dangerous command".to_string());
1313 manager.add_permission(perm, PermissionScope::Global);
1314
1315 let context = create_test_context();
1316 let params = HashMap::new();
1317
1318 let result = manager.is_allowed("bash", ¶ms, &context);
1319
1320 assert!(!result.allowed);
1321 assert!(result.matched_rule.is_some());
1322 assert!(result.reason.is_some());
1323 }
1324
1325 #[test]
1326 fn test_is_allowed_wildcard_pattern() {
1327 let mut manager = ToolPermissionManager::new(None);
1328 manager.add_permission(
1329 create_simple_permission("file_*", true, PermissionScope::Global),
1330 PermissionScope::Global,
1331 );
1332
1333 let context = create_test_context();
1334 let params = HashMap::new();
1335
1336 assert!(manager.is_allowed("file_read", ¶ms, &context).allowed);
1337 assert!(manager.is_allowed("file_write", ¶ms, &context).allowed);
1338 assert!(manager.is_allowed("file_delete", ¶ms, &context).allowed);
1339 }
1340
1341 #[test]
1342 fn test_is_allowed_expired_rule() {
1343 let mut manager = ToolPermissionManager::new(None);
1344 let mut perm = create_simple_permission("bash", false, PermissionScope::Global);
1345 perm.expires_at = Some(1600000000); manager.add_permission(perm, PermissionScope::Global);
1347
1348 let context = create_test_context(); let params = HashMap::new();
1351 let result = manager.is_allowed("bash", ¶ms, &context);
1352
1353 assert!(result.allowed);
1355 }
1356
1357 #[test]
1358 fn test_is_allowed_with_conditions() {
1359 let mut manager = ToolPermissionManager::new(None);
1360 let mut perm = create_simple_permission("bash", true, PermissionScope::Global);
1361 perm.conditions = vec![PermissionCondition {
1362 condition_type: ConditionType::Context,
1363 field: Some("working_directory".to_string()),
1364 operator: ConditionOperator::Contains,
1365 value: serde_json::json!("project"),
1366 validator: None,
1367 description: None,
1368 }];
1369 manager.add_permission(perm, PermissionScope::Global);
1370
1371 let context = create_test_context(); let params = HashMap::new();
1373
1374 let result = manager.is_allowed("bash", ¶ms, &context);
1375 assert!(result.allowed);
1376 }
1377
1378 #[test]
1379 fn test_is_allowed_conditions_not_met() {
1380 let mut manager = ToolPermissionManager::new(None);
1381 let mut perm = create_simple_permission("bash", true, PermissionScope::Global);
1382 perm.conditions = vec![PermissionCondition {
1383 condition_type: ConditionType::Context,
1384 field: Some("working_directory".to_string()),
1385 operator: ConditionOperator::Contains,
1386 value: serde_json::json!("safe_directory"),
1387 validator: None,
1388 description: None,
1389 }];
1390 manager.add_permission(perm, PermissionScope::Global);
1391
1392 let context = create_test_context(); let params = HashMap::new();
1394
1395 let result = manager.is_allowed("bash", ¶ms, &context);
1396 assert!(result.allowed);
1398 assert!(result.matched_rule.is_none());
1399 }
1400
1401 #[test]
1402 fn test_is_allowed_parameter_restriction_pass() {
1403 let mut manager = ToolPermissionManager::new(None);
1404 let mut perm = create_simple_permission("bash", true, PermissionScope::Global);
1405 perm.parameter_restrictions = vec![ParameterRestriction {
1406 parameter: "command".to_string(),
1407 restriction_type: RestrictionType::Whitelist,
1408 values: Some(vec![serde_json::json!("ls"), serde_json::json!("cat")]),
1409 pattern: None,
1410 validator: None,
1411 min: None,
1412 max: None,
1413 required: false,
1414 description: None,
1415 }];
1416 manager.add_permission(perm, PermissionScope::Global);
1417
1418 let context = create_test_context();
1419 let mut params = HashMap::new();
1420 params.insert("command".to_string(), serde_json::json!("ls"));
1421
1422 let result = manager.is_allowed("bash", ¶ms, &context);
1423 assert!(result.allowed);
1424 assert!(result.restricted);
1425 }
1426
1427 #[test]
1428 fn test_is_allowed_parameter_restriction_fail() {
1429 let mut manager = ToolPermissionManager::new(None);
1430 let mut perm = create_simple_permission("bash", true, PermissionScope::Global);
1431 perm.parameter_restrictions = vec![ParameterRestriction {
1432 parameter: "command".to_string(),
1433 restriction_type: RestrictionType::Whitelist,
1434 values: Some(vec![serde_json::json!("ls"), serde_json::json!("cat")]),
1435 pattern: None,
1436 validator: None,
1437 min: None,
1438 max: None,
1439 required: false,
1440 description: None,
1441 }];
1442 manager.add_permission(perm, PermissionScope::Global);
1443
1444 let context = create_test_context();
1445 let mut params = HashMap::new();
1446 params.insert("command".to_string(), serde_json::json!("rm -rf"));
1447
1448 let result = manager.is_allowed("bash", ¶ms, &context);
1449 assert!(!result.allowed);
1450 assert!(result.restricted);
1451 assert!(!result.violations.is_empty());
1452 }
1453
1454 #[test]
1455 fn test_is_allowed_priority_order() {
1456 let mut manager = ToolPermissionManager::new(None);
1457
1458 let mut low_perm = create_simple_permission("bash", true, PermissionScope::Global);
1460 low_perm.priority = 1;
1461 manager.add_permission(low_perm, PermissionScope::Global);
1462
1463 let mut high_perm = create_simple_permission("bash", false, PermissionScope::Session);
1465 high_perm.priority = 10;
1466 manager.add_permission(high_perm, PermissionScope::Session);
1467
1468 let context = create_test_context();
1469 let params = HashMap::new();
1470
1471 let result = manager.is_allowed("bash", ¶ms, &context);
1472 assert!(!result.allowed);
1474 }
1475
1476 #[test]
1477 fn test_generate_suggestions_denied() {
1478 let mut perm = create_simple_permission("bash", false, PermissionScope::Global);
1479 perm.reason = Some("Security policy".to_string());
1480
1481 let suggestions = ToolPermissionManager::generate_suggestions(&perm, &[]);
1482
1483 assert!(!suggestions.is_empty());
1484 assert!(suggestions.iter().any(|s| s.contains("Security policy")));
1485 }
1486
1487 #[test]
1488 fn test_generate_suggestions_with_violations() {
1489 let perm = create_simple_permission("bash", true, PermissionScope::Global);
1490 let violations = vec!["Parameter 'command' value \"rm\" is not in whitelist".to_string()];
1491
1492 let suggestions = ToolPermissionManager::generate_suggestions(&perm, &violations);
1493
1494 assert!(!suggestions.is_empty());
1495 assert!(suggestions.iter().any(|s| s.contains("whitelist")));
1496 }
1497
1498 #[test]
1499 fn test_clear_scope() {
1500 let mut manager = ToolPermissionManager::new(None);
1501 manager.add_permission(
1502 create_simple_permission("bash", true, PermissionScope::Global),
1503 PermissionScope::Global,
1504 );
1505 manager.add_permission(
1506 create_simple_permission("file", true, PermissionScope::Session),
1507 PermissionScope::Session,
1508 );
1509
1510 manager.clear_scope(PermissionScope::Global);
1511
1512 assert_eq!(manager.permission_counts(), (0, 0, 1));
1513 }
1514
1515 #[test]
1516 fn test_clear_all() {
1517 let mut manager = ToolPermissionManager::new(None);
1518 manager.add_permission(
1519 create_simple_permission("bash", true, PermissionScope::Global),
1520 PermissionScope::Global,
1521 );
1522 manager.add_permission(
1523 create_simple_permission("file", true, PermissionScope::Session),
1524 PermissionScope::Session,
1525 );
1526
1527 manager.clear_all();
1528
1529 assert_eq!(manager.permission_counts(), (0, 0, 0));
1530 }
1531
1532 #[test]
1533 fn test_set_inheritance() {
1534 let mut manager = ToolPermissionManager::new(None);
1535 let new_inheritance = PermissionInheritance {
1536 inherit_global: false,
1537 inherit_project: true,
1538 override_global: false,
1539 merge_strategy: crate::permission::types::MergeStrategy::Merge,
1540 };
1541
1542 manager.set_inheritance(new_inheritance.clone());
1543
1544 assert_eq!(manager.inheritance(), &new_inheritance);
1545 }
1546
1547 #[test]
1548 fn test_update_permission_allowed() {
1549 use crate::permission::types::ToolPermissionUpdate;
1550
1551 let mut manager = ToolPermissionManager::new(None);
1552 manager.add_permission(
1553 create_simple_permission("bash", true, PermissionScope::Global),
1554 PermissionScope::Global,
1555 );
1556
1557 let update = ToolPermissionUpdate::new().with_allowed(false);
1558 let result = manager.update_permission("bash", update, PermissionScope::Global);
1559
1560 assert!(result);
1561 let perm = manager.get_tool_permission("bash").unwrap();
1562 assert!(!perm.allowed);
1563 }
1564
1565 #[test]
1566 fn test_update_permission_priority() {
1567 use crate::permission::types::ToolPermissionUpdate;
1568
1569 let mut manager = ToolPermissionManager::new(None);
1570 manager.add_permission(
1571 create_simple_permission("bash", true, PermissionScope::Global),
1572 PermissionScope::Global,
1573 );
1574
1575 let update = ToolPermissionUpdate::new().with_priority(100);
1576 let result = manager.update_permission("bash", update, PermissionScope::Global);
1577
1578 assert!(result);
1579 let perm = manager.get_tool_permission("bash").unwrap();
1580 assert_eq!(perm.priority, 100);
1581 }
1582
1583 #[test]
1584 fn test_update_permission_reason() {
1585 use crate::permission::types::ToolPermissionUpdate;
1586
1587 let mut manager = ToolPermissionManager::new(None);
1588 manager.add_permission(
1589 create_simple_permission("bash", true, PermissionScope::Global),
1590 PermissionScope::Global,
1591 );
1592
1593 let update = ToolPermissionUpdate::new().with_reason(Some("Updated reason".to_string()));
1594 let result = manager.update_permission("bash", update, PermissionScope::Global);
1595
1596 assert!(result);
1597 let perm = manager.get_tool_permission("bash").unwrap();
1598 assert_eq!(perm.reason, Some("Updated reason".to_string()));
1599 }
1600
1601 #[test]
1602 fn test_update_permission_not_found() {
1603 use crate::permission::types::ToolPermissionUpdate;
1604
1605 let mut manager = ToolPermissionManager::new(None);
1606
1607 let update = ToolPermissionUpdate::new().with_allowed(false);
1608 let result = manager.update_permission("nonexistent", update, PermissionScope::Global);
1609
1610 assert!(!result);
1611 }
1612
1613 #[test]
1614 fn test_update_permission_wrong_scope() {
1615 use crate::permission::types::ToolPermissionUpdate;
1616
1617 let mut manager = ToolPermissionManager::new(None);
1618 manager.add_permission(
1619 create_simple_permission("bash", true, PermissionScope::Global),
1620 PermissionScope::Global,
1621 );
1622
1623 let update = ToolPermissionUpdate::new().with_allowed(false);
1625 let result = manager.update_permission("bash", update, PermissionScope::Session);
1626
1627 assert!(!result);
1628 let perm = manager.get_tool_permission("bash").unwrap();
1630 assert!(perm.allowed);
1631 }
1632
1633 #[test]
1634 fn test_update_permission_multiple_fields() {
1635 use crate::permission::types::ToolPermissionUpdate;
1636
1637 let mut manager = ToolPermissionManager::new(None);
1638 manager.add_permission(
1639 create_simple_permission("bash", true, PermissionScope::Project),
1640 PermissionScope::Project,
1641 );
1642
1643 let update = ToolPermissionUpdate::new()
1644 .with_allowed(false)
1645 .with_priority(50)
1646 .with_reason(Some("Security update".to_string()))
1647 .with_expires_at(Some(1800000000));
1648
1649 let result = manager.update_permission("bash", update, PermissionScope::Project);
1650
1651 assert!(result);
1652 let perm = manager.get_tool_permission("bash").unwrap();
1653 assert!(!perm.allowed);
1654 assert_eq!(perm.priority, 50);
1655 assert_eq!(perm.reason, Some("Security update".to_string()));
1656 assert_eq!(perm.expires_at, Some(1800000000));
1657 }
1658
1659 #[test]
1660 fn test_update_permission_clear_reason() {
1661 use crate::permission::types::ToolPermissionUpdate;
1662
1663 let mut manager = ToolPermissionManager::new(None);
1664 let mut perm = create_simple_permission("bash", true, PermissionScope::Global);
1665 perm.reason = Some("Initial reason".to_string());
1666 manager.add_permission(perm, PermissionScope::Global);
1667
1668 let update = ToolPermissionUpdate::new().with_reason(None);
1670 let result = manager.update_permission("bash", update, PermissionScope::Global);
1671
1672 assert!(result);
1673 let perm = manager.get_tool_permission("bash").unwrap();
1674 assert!(perm.reason.is_none());
1675 }
1676
1677 #[test]
1682 fn test_export_empty() {
1683 let manager = ToolPermissionManager::new(None);
1684 let result = manager.export(None);
1685
1686 assert!(result.is_ok());
1687 let json = result.unwrap();
1688 assert!(json.contains("\"version\": \"1.0.0\""));
1689 assert!(json.contains("\"permissions\": []"));
1690 }
1691
1692 #[test]
1693 fn test_export_with_permissions() {
1694 let mut manager = ToolPermissionManager::new(None);
1695 manager.add_permission(
1696 create_simple_permission("bash", true, PermissionScope::Global),
1697 PermissionScope::Global,
1698 );
1699 manager.add_permission(
1700 create_simple_permission("file_read", false, PermissionScope::Project),
1701 PermissionScope::Project,
1702 );
1703
1704 let result = manager.export(None);
1705
1706 assert!(result.is_ok());
1707 let json = result.unwrap();
1708 assert!(json.contains("\"version\": \"1.0.0\""));
1709 assert!(json.contains("\"bash\""));
1710 assert!(json.contains("\"file_read\""));
1711 }
1712
1713 #[test]
1714 fn test_export_specific_scope() {
1715 let mut manager = ToolPermissionManager::new(None);
1716 manager.add_permission(
1717 create_simple_permission("bash", true, PermissionScope::Global),
1718 PermissionScope::Global,
1719 );
1720 manager.add_permission(
1721 create_simple_permission("file_read", false, PermissionScope::Project),
1722 PermissionScope::Project,
1723 );
1724
1725 let result = manager.export(Some(PermissionScope::Global));
1726
1727 assert!(result.is_ok());
1728 let json = result.unwrap();
1729 assert!(json.contains("\"bash\""));
1730 assert!(!json.contains("\"file_read\""));
1731 }
1732
1733 #[test]
1734 fn test_import_valid_config() {
1735 let mut manager = ToolPermissionManager::new(None);
1736 let config_json = r#"{
1737 "version": "1.0.0",
1738 "inheritance": {
1739 "inherit_global": true,
1740 "inherit_project": true,
1741 "override_global": true,
1742 "merge_strategy": "Override"
1743 },
1744 "permissions": [
1745 {
1746 "tool": "bash",
1747 "allowed": true,
1748 "priority": 10,
1749 "conditions": [],
1750 "parameter_restrictions": [],
1751 "scope": "Global",
1752 "reason": "Test permission",
1753 "expires_at": null,
1754 "metadata": {}
1755 }
1756 ]
1757 }"#;
1758
1759 let result = manager.import(config_json, PermissionScope::Global);
1760
1761 assert!(result.is_ok());
1762 assert_eq!(manager.permission_counts(), (1, 0, 0));
1763 let perm = manager.get_tool_permission("bash").unwrap();
1764 assert!(perm.allowed);
1765 assert_eq!(perm.priority, 10);
1766 }
1767
1768 #[test]
1769 fn test_import_invalid_json() {
1770 let mut manager = ToolPermissionManager::new(None);
1771 manager.add_permission(
1772 create_simple_permission("existing", true, PermissionScope::Global),
1773 PermissionScope::Global,
1774 );
1775
1776 let result = manager.import("invalid json", PermissionScope::Global);
1777
1778 assert!(result.is_err());
1779 assert_eq!(manager.permission_counts(), (1, 0, 0));
1781 }
1782
1783 #[test]
1784 fn test_import_invalid_version() {
1785 let mut manager = ToolPermissionManager::new(None);
1786 let config_json = r#"{
1787 "version": "99.0.0",
1788 "inheritance": {
1789 "inherit_global": true,
1790 "inherit_project": true,
1791 "override_global": true,
1792 "merge_strategy": "Override"
1793 },
1794 "permissions": []
1795 }"#;
1796
1797 let result = manager.import(config_json, PermissionScope::Global);
1798
1799 assert!(result.is_err());
1800 assert!(result
1801 .unwrap_err()
1802 .to_string()
1803 .contains("Unsupported configuration version"));
1804 }
1805
1806 #[test]
1807 fn test_import_empty_tool_name() {
1808 let mut manager = ToolPermissionManager::new(None);
1809 let config_json = r#"{
1810 "version": "1.0.0",
1811 "inheritance": {
1812 "inherit_global": true,
1813 "inherit_project": true,
1814 "override_global": true,
1815 "merge_strategy": "Override"
1816 },
1817 "permissions": [
1818 {
1819 "tool": "",
1820 "allowed": true,
1821 "priority": 0,
1822 "conditions": [],
1823 "parameter_restrictions": [],
1824 "scope": "Global",
1825 "reason": null,
1826 "expires_at": null,
1827 "metadata": {}
1828 }
1829 ]
1830 }"#;
1831
1832 let result = manager.import(config_json, PermissionScope::Global);
1833
1834 assert!(result.is_err());
1835 assert!(result
1836 .unwrap_err()
1837 .to_string()
1838 .contains("tool name cannot be empty"));
1839 }
1840
1841 #[test]
1842 fn test_import_replaces_existing() {
1843 let mut manager = ToolPermissionManager::new(None);
1844 manager.add_permission(
1845 create_simple_permission("old_tool", true, PermissionScope::Global),
1846 PermissionScope::Global,
1847 );
1848
1849 let config_json = r#"{
1850 "version": "1.0.0",
1851 "inheritance": {
1852 "inherit_global": true,
1853 "inherit_project": true,
1854 "override_global": true,
1855 "merge_strategy": "Override"
1856 },
1857 "permissions": [
1858 {
1859 "tool": "new_tool",
1860 "allowed": false,
1861 "priority": 5,
1862 "conditions": [],
1863 "parameter_restrictions": [],
1864 "scope": "Global",
1865 "reason": null,
1866 "expires_at": null,
1867 "metadata": {}
1868 }
1869 ]
1870 }"#;
1871
1872 let result = manager.import(config_json, PermissionScope::Global);
1873
1874 assert!(result.is_ok());
1875 assert_eq!(manager.permission_counts(), (1, 0, 0));
1876 assert!(manager.get_tool_permission("old_tool").is_none());
1877 assert!(manager.get_tool_permission("new_tool").is_some());
1878 }
1879
1880 #[test]
1881 fn test_import_sets_target_scope() {
1882 let mut manager = ToolPermissionManager::new(None);
1883 let config_json = r#"{
1884 "version": "1.0.0",
1885 "inheritance": {
1886 "inherit_global": true,
1887 "inherit_project": true,
1888 "override_global": true,
1889 "merge_strategy": "Override"
1890 },
1891 "permissions": [
1892 {
1893 "tool": "bash",
1894 "allowed": true,
1895 "priority": 0,
1896 "conditions": [],
1897 "parameter_restrictions": [],
1898 "scope": "Global",
1899 "reason": null,
1900 "expires_at": null,
1901 "metadata": {}
1902 }
1903 ]
1904 }"#;
1905
1906 let result = manager.import(config_json, PermissionScope::Session);
1908
1909 assert!(result.is_ok());
1910 assert_eq!(manager.permission_counts(), (0, 0, 1));
1911 let perm = manager.get_tool_permission("bash").unwrap();
1912 assert_eq!(perm.scope, PermissionScope::Session);
1913 }
1914
1915 #[test]
1916 fn test_export_import_round_trip() {
1917 let mut manager = ToolPermissionManager::new(None);
1918 let mut perm = create_simple_permission("bash_*", true, PermissionScope::Global);
1919 perm.priority = 42;
1920 perm.reason = Some("Test reason".to_string());
1921 manager.add_permission(perm, PermissionScope::Global);
1922
1923 let exported = manager.export(Some(PermissionScope::Global)).unwrap();
1925
1926 let mut new_manager = ToolPermissionManager::new(None);
1928 let result = new_manager.import(&exported, PermissionScope::Global);
1929
1930 assert!(result.is_ok());
1931 let imported_perm = new_manager.get_tool_permission("bash_*").unwrap();
1932 assert_eq!(imported_perm.tool, "bash_*");
1933 assert!(imported_perm.allowed);
1934 assert_eq!(imported_perm.priority, 42);
1935 assert_eq!(imported_perm.reason, Some("Test reason".to_string()));
1936 }
1937
1938 #[test]
1939 fn test_import_invalid_range_restriction() {
1940 let mut manager = ToolPermissionManager::new(None);
1941 let config_json = r#"{
1942 "version": "1.0.0",
1943 "inheritance": {
1944 "inherit_global": true,
1945 "inherit_project": true,
1946 "override_global": true,
1947 "merge_strategy": "Override"
1948 },
1949 "permissions": [
1950 {
1951 "tool": "bash",
1952 "allowed": true,
1953 "priority": 0,
1954 "conditions": [],
1955 "parameter_restrictions": [
1956 {
1957 "parameter": "count",
1958 "restriction_type": "Range",
1959 "values": null,
1960 "pattern": null,
1961 "min": null,
1962 "max": null,
1963 "required": false,
1964 "description": null
1965 }
1966 ],
1967 "scope": "Global",
1968 "reason": null,
1969 "expires_at": null,
1970 "metadata": {}
1971 }
1972 ]
1973 }"#;
1974
1975 let result = manager.import(config_json, PermissionScope::Global);
1976
1977 assert!(result.is_err());
1978 assert!(result
1979 .unwrap_err()
1980 .to_string()
1981 .contains("must have at least min or max"));
1982 }
1983
1984 #[test]
1985 fn test_import_invalid_pattern_restriction() {
1986 let mut manager = ToolPermissionManager::new(None);
1987 let config_json = r#"{
1988 "version": "1.0.0",
1989 "inheritance": {
1990 "inherit_global": true,
1991 "inherit_project": true,
1992 "override_global": true,
1993 "merge_strategy": "Override"
1994 },
1995 "permissions": [
1996 {
1997 "tool": "bash",
1998 "allowed": true,
1999 "priority": 0,
2000 "conditions": [],
2001 "parameter_restrictions": [
2002 {
2003 "parameter": "command",
2004 "restriction_type": "Pattern",
2005 "values": null,
2006 "pattern": null,
2007 "min": null,
2008 "max": null,
2009 "required": false,
2010 "description": null
2011 }
2012 ],
2013 "scope": "Global",
2014 "reason": null,
2015 "expires_at": null,
2016 "metadata": {}
2017 }
2018 ]
2019 }"#;
2020
2021 let result = manager.import(config_json, PermissionScope::Global);
2022
2023 assert!(result.is_err());
2024 assert!(result
2025 .unwrap_err()
2026 .to_string()
2027 .contains("must have a pattern"));
2028 }
2029
2030 #[test]
2031 fn test_import_invalid_whitelist_restriction() {
2032 let mut manager = ToolPermissionManager::new(None);
2033 let config_json = r#"{
2034 "version": "1.0.0",
2035 "inheritance": {
2036 "inherit_global": true,
2037 "inherit_project": true,
2038 "override_global": true,
2039 "merge_strategy": "Override"
2040 },
2041 "permissions": [
2042 {
2043 "tool": "bash",
2044 "allowed": true,
2045 "priority": 0,
2046 "conditions": [],
2047 "parameter_restrictions": [
2048 {
2049 "parameter": "command",
2050 "restriction_type": "Whitelist",
2051 "values": null,
2052 "pattern": null,
2053 "min": null,
2054 "max": null,
2055 "required": false,
2056 "description": null
2057 }
2058 ],
2059 "scope": "Global",
2060 "reason": null,
2061 "expires_at": null,
2062 "metadata": {}
2063 }
2064 ]
2065 }"#;
2066
2067 let result = manager.import(config_json, PermissionScope::Global);
2068
2069 assert!(result.is_err());
2070 assert!(result.unwrap_err().to_string().contains("must have values"));
2071 }
2072
2073 #[test]
2078 fn test_register_template() {
2079 let mut manager = ToolPermissionManager::new(None);
2080 let template = vec![create_simple_permission(
2081 "custom_tool",
2082 true,
2083 PermissionScope::Global,
2084 )];
2085
2086 manager.register_template("my_template", template);
2087
2088 assert!(manager.has_template("my_template"));
2089 assert!(manager.get_template("my_template").is_some());
2090 }
2091
2092 #[test]
2093 fn test_register_template_replaces_existing() {
2094 let mut manager = ToolPermissionManager::new(None);
2095 let template1 = vec![create_simple_permission(
2096 "tool1",
2097 true,
2098 PermissionScope::Global,
2099 )];
2100 let template2 = vec![create_simple_permission(
2101 "tool2",
2102 false,
2103 PermissionScope::Global,
2104 )];
2105
2106 manager.register_template("my_template", template1);
2107 manager.register_template("my_template", template2);
2108
2109 let template = manager.get_template("my_template").unwrap();
2110 assert_eq!(template.len(), 1);
2111 assert_eq!(template[0].tool, "tool2");
2112 }
2113
2114 #[test]
2115 fn test_apply_template() {
2116 let mut manager = ToolPermissionManager::new(None);
2117 let template = vec![
2118 create_simple_permission("tool1", true, PermissionScope::Global),
2119 create_simple_permission("tool2", false, PermissionScope::Global),
2120 ];
2121
2122 manager.register_template("my_template", template);
2123 let result = manager.apply_template("my_template", PermissionScope::Project);
2124
2125 assert!(result);
2126 assert_eq!(manager.permission_counts(), (0, 2, 0));
2127
2128 let perm = manager.get_tool_permission("tool1").unwrap();
2130 assert_eq!(perm.scope, PermissionScope::Project);
2131 }
2132
2133 #[test]
2134 fn test_apply_template_not_found() {
2135 let mut manager = ToolPermissionManager::new(None);
2136
2137 let result = manager.apply_template("nonexistent", PermissionScope::Global);
2138
2139 assert!(!result);
2140 assert_eq!(manager.permission_counts(), (0, 0, 0));
2141 }
2142
2143 #[test]
2144 fn test_remove_template() {
2145 let mut manager = ToolPermissionManager::new(None);
2146 let template = vec![create_simple_permission(
2147 "tool1",
2148 true,
2149 PermissionScope::Global,
2150 )];
2151
2152 manager.register_template("my_template", template);
2153 let removed = manager.remove_template("my_template");
2154
2155 assert!(removed.is_some());
2156 assert!(!manager.has_template("my_template"));
2157 }
2158
2159 #[test]
2160 fn test_remove_template_not_found() {
2161 let mut manager = ToolPermissionManager::new(None);
2162
2163 let removed = manager.remove_template("nonexistent");
2164
2165 assert!(removed.is_none());
2166 }
2167
2168 #[test]
2169 fn test_list_templates() {
2170 let mut manager = ToolPermissionManager::new(None);
2171 manager.register_template("template1", vec![]);
2172 manager.register_template("template2", vec![]);
2173 manager.register_template("template3", vec![]);
2174
2175 let templates = manager.list_templates();
2176
2177 assert_eq!(templates.len(), 3);
2178 assert!(templates.iter().any(|t| *t == "template1"));
2179 assert!(templates.iter().any(|t| *t == "template2"));
2180 assert!(templates.iter().any(|t| *t == "template3"));
2181 }
2182
2183 #[test]
2184 fn test_has_template() {
2185 let mut manager = ToolPermissionManager::new(None);
2186 manager.register_template("exists", vec![]);
2187
2188 assert!(manager.has_template("exists"));
2189 assert!(!manager.has_template("not_exists"));
2190 }
2191
2192 #[test]
2193 fn test_apply_template_to_different_scopes() {
2194 let mut manager = ToolPermissionManager::new(None);
2195 let template = vec![create_simple_permission(
2196 "tool",
2197 true,
2198 PermissionScope::Global,
2199 )];
2200
2201 manager.register_template("my_template", template);
2202
2203 manager.apply_template("my_template", PermissionScope::Global);
2205 assert_eq!(manager.permission_counts(), (1, 0, 0));
2206
2207 manager.apply_template("my_template", PermissionScope::Session);
2209 assert_eq!(manager.permission_counts(), (1, 0, 1));
2210
2211 let global_perms = manager.get_permissions(Some(PermissionScope::Global));
2213 let session_perms = manager.get_permissions(Some(PermissionScope::Session));
2214
2215 assert_eq!(global_perms[0].scope, PermissionScope::Global);
2216 assert_eq!(session_perms[0].scope, PermissionScope::Session);
2217 }
2218}