#![allow(dead_code)]
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub enum PluginCapability {
FileRead,
FileWrite,
Network,
Memory,
Compute,
}
impl PluginCapability {
pub fn as_str(self) -> &'static str {
match self {
PluginCapability::FileRead => "FileRead",
PluginCapability::FileWrite => "FileWrite",
PluginCapability::Network => "Network",
PluginCapability::Memory => "Memory",
PluginCapability::Compute => "Compute",
}
}
pub fn all() -> &'static [PluginCapability] {
&[
PluginCapability::FileRead,
PluginCapability::FileWrite,
PluginCapability::Network,
PluginCapability::Memory,
PluginCapability::Compute,
]
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SandboxPolicy {
pub name: String,
allowed: HashSet<PluginCapability>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PluginSandbox {
pub plugin_id: String,
granted: HashSet<PluginCapability>,
restricted: bool,
}
pub fn create_policy(name: &str) -> SandboxPolicy {
SandboxPolicy {
name: name.to_string(),
allowed: HashSet::new(),
}
}
pub fn policy_allow(mut policy: SandboxPolicy, cap: PluginCapability) -> SandboxPolicy {
policy.allowed.insert(cap);
policy
}
pub fn policy_allows(policy: &SandboxPolicy, cap: PluginCapability) -> bool {
policy.allowed.contains(&cap)
}
pub fn merge_policies(a: &SandboxPolicy, b: &SandboxPolicy) -> SandboxPolicy {
let allowed = a.allowed.union(&b.allowed).copied().collect();
SandboxPolicy {
name: a.name.clone(),
allowed,
}
}
pub fn policy_to_json(policy: &SandboxPolicy) -> String {
let mut caps: Vec<&str> = policy.allowed.iter().map(|c| c.as_str()).collect();
caps.sort_unstable();
format!(
r#"{{"name":"{}","allowed":[{}]}}"#,
policy.name,
caps.iter()
.map(|s| format!(r#""{}""#, s))
.collect::<Vec<_>>()
.join(",")
)
}
pub fn new_plugin_sandbox(plugin_id: &str) -> PluginSandbox {
PluginSandbox {
plugin_id: plugin_id.to_string(),
granted: HashSet::new(),
restricted: false,
}
}
pub fn grant_capability(sandbox: &mut PluginSandbox, cap: PluginCapability) {
sandbox.granted.insert(cap);
}
pub fn revoke_capability(sandbox: &mut PluginSandbox, cap: PluginCapability) {
sandbox.granted.remove(&cap);
}
pub fn has_capability(sandbox: &PluginSandbox, cap: PluginCapability) -> bool {
if sandbox.restricted {
return false;
}
sandbox.granted.contains(&cap)
}
pub fn apply_policy(sandbox: &mut PluginSandbox, policy: &SandboxPolicy) {
sandbox.granted = policy.allowed.clone();
}
pub fn sandbox_to_json(sandbox: &PluginSandbox) -> String {
let mut caps: Vec<&str> = sandbox.granted.iter().map(|c| c.as_str()).collect();
caps.sort_unstable();
format!(
r#"{{"plugin_id":"{}","restricted":{},"granted":[{}]}}"#,
sandbox.plugin_id,
sandbox.restricted,
caps.iter()
.map(|s| format!(r#""{}""#, s))
.collect::<Vec<_>>()
.join(",")
)
}
pub fn list_capabilities(sandbox: &PluginSandbox) -> Vec<&'static str> {
let mut caps: Vec<&'static str> = sandbox.granted.iter().map(|c| c.as_str()).collect();
caps.sort_unstable();
caps
}
pub fn capability_count(sandbox: &PluginSandbox) -> usize {
sandbox.granted.len()
}
pub fn reset_sandbox(sandbox: &mut PluginSandbox) {
sandbox.granted.clear();
sandbox.restricted = false;
}
pub fn is_restricted(sandbox: &PluginSandbox) -> bool {
sandbox.restricted
}
pub fn set_restricted(sandbox: &mut PluginSandbox, restricted: bool) {
sandbox.restricted = restricted;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_sandbox_has_no_capabilities() {
let sb = new_plugin_sandbox("test");
assert_eq!(capability_count(&sb), 0);
}
#[test]
fn grant_and_has_capability() {
let mut sb = new_plugin_sandbox("p1");
grant_capability(&mut sb, PluginCapability::FileRead);
assert!(has_capability(&sb, PluginCapability::FileRead));
}
#[test]
fn revoke_removes_capability() {
let mut sb = new_plugin_sandbox("p2");
grant_capability(&mut sb, PluginCapability::Network);
revoke_capability(&mut sb, PluginCapability::Network);
assert!(!has_capability(&sb, PluginCapability::Network));
}
#[test]
fn has_capability_restricted_mode_returns_false() {
let mut sb = new_plugin_sandbox("p3");
grant_capability(&mut sb, PluginCapability::Compute);
set_restricted(&mut sb, true);
assert!(!has_capability(&sb, PluginCapability::Compute));
}
#[test]
fn unrestrict_restores_access() {
let mut sb = new_plugin_sandbox("p4");
grant_capability(&mut sb, PluginCapability::Memory);
set_restricted(&mut sb, true);
set_restricted(&mut sb, false);
assert!(has_capability(&sb, PluginCapability::Memory));
}
#[test]
fn create_policy_is_empty() {
let p = create_policy("empty");
assert!(!policy_allows(&p, PluginCapability::FileRead));
}
#[test]
fn policy_allow_adds_capability() {
let p = policy_allow(create_policy("r"), PluginCapability::FileRead);
assert!(policy_allows(&p, PluginCapability::FileRead));
assert!(!policy_allows(&p, PluginCapability::FileWrite));
}
#[test]
fn apply_policy_replaces_grants() {
let mut sb = new_plugin_sandbox("p5");
grant_capability(&mut sb, PluginCapability::Network);
let pol = policy_allow(create_policy("safe"), PluginCapability::FileRead);
apply_policy(&mut sb, &pol);
assert!(!has_capability(&sb, PluginCapability::Network));
assert!(has_capability(&sb, PluginCapability::FileRead));
}
#[test]
fn sandbox_to_json_contains_plugin_id() {
let sb = new_plugin_sandbox("myplugin");
let json = sandbox_to_json(&sb);
assert!(json.contains("myplugin"));
}
#[test]
fn sandbox_to_json_reflects_grants() {
let mut sb = new_plugin_sandbox("p6");
grant_capability(&mut sb, PluginCapability::Compute);
let json = sandbox_to_json(&sb);
assert!(json.contains("Compute"));
}
#[test]
fn list_capabilities_sorted() {
let mut sb = new_plugin_sandbox("p7");
grant_capability(&mut sb, PluginCapability::Network);
grant_capability(&mut sb, PluginCapability::FileRead);
let caps = list_capabilities(&sb);
let sorted = {
let mut c = caps.clone();
c.sort_unstable();
c
};
assert_eq!(caps, sorted);
}
#[test]
fn capability_count_accurate() {
let mut sb = new_plugin_sandbox("p8");
grant_capability(&mut sb, PluginCapability::FileRead);
grant_capability(&mut sb, PluginCapability::FileWrite);
assert_eq!(capability_count(&sb), 2);
}
#[test]
fn reset_sandbox_clears_all() {
let mut sb = new_plugin_sandbox("p9");
grant_capability(&mut sb, PluginCapability::Memory);
set_restricted(&mut sb, true);
reset_sandbox(&mut sb);
assert_eq!(capability_count(&sb), 0);
assert!(!is_restricted(&sb));
}
#[test]
fn merge_policies_union() {
let pa = policy_allow(create_policy("a"), PluginCapability::FileRead);
let pb = policy_allow(create_policy("b"), PluginCapability::Network);
let merged = merge_policies(&pa, &pb);
assert!(policy_allows(&merged, PluginCapability::FileRead));
assert!(policy_allows(&merged, PluginCapability::Network));
}
#[test]
fn merge_policies_name_from_a() {
let pa = create_policy("alpha");
let pb = create_policy("beta");
let merged = merge_policies(&pa, &pb);
assert_eq!(merged.name, "alpha");
}
#[test]
fn policy_to_json_contains_name() {
let p = create_policy("mypol");
let json = policy_to_json(&p);
assert!(json.contains("mypol"));
}
#[test]
fn capability_all_has_five_entries() {
assert_eq!(PluginCapability::all().len(), 5);
}
#[test]
fn grant_duplicate_does_not_increase_count() {
let mut sb = new_plugin_sandbox("dup");
grant_capability(&mut sb, PluginCapability::Compute);
grant_capability(&mut sb, PluginCapability::Compute);
assert_eq!(capability_count(&sb), 1);
}
#[test]
fn revoke_nonexistent_is_safe() {
let mut sb = new_plugin_sandbox("safe");
revoke_capability(&mut sb, PluginCapability::Memory); assert_eq!(capability_count(&sb), 0);
}
#[test]
fn all_capability_names_are_unique() {
let names: HashSet<&str> = PluginCapability::all().iter().map(|c| c.as_str()).collect();
assert_eq!(names.len(), PluginCapability::all().len());
}
}