use crate::{
metadata::security::{
encode_permission_set, ArgumentType, ArgumentValue, NamedArgument, Permission,
PermissionSetFormat,
},
Result,
};
pub struct PermissionSetBuilder {
permissions: Vec<Permission>,
}
impl PermissionSetBuilder {
#[must_use]
pub fn new() -> Self {
PermissionSetBuilder {
permissions: Vec::new(),
}
}
#[must_use]
pub fn add_permission(mut self, permission: Permission) -> Self {
self.permissions.push(permission);
self
}
#[must_use]
pub fn add_security_permission(self) -> SecurityPermissionBuilder {
SecurityPermissionBuilder::new(self)
}
#[must_use]
pub fn add_file_io_permission(self) -> FileIOPermissionBuilder {
FileIOPermissionBuilder::new(self)
}
pub fn encode(self, format: PermissionSetFormat) -> Result<Vec<u8>> {
encode_permission_set(&self.permissions, format)
}
#[must_use]
pub fn permissions(self) -> Vec<Permission> {
self.permissions
}
}
impl Default for PermissionSetBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct SecurityPermissionBuilder {
parent: PermissionSetBuilder,
named_arguments: Vec<NamedArgument>,
}
impl SecurityPermissionBuilder {
fn new(parent: PermissionSetBuilder) -> Self {
SecurityPermissionBuilder {
parent,
named_arguments: Vec::new(),
}
}
#[must_use]
pub fn unrestricted(mut self, value: bool) -> Self {
self.named_arguments.push(NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(value),
});
self
}
#[must_use]
pub fn flags(mut self, flags: &str) -> Self {
self.named_arguments.push(NamedArgument {
name: "Flags".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String(flags.to_string()),
});
self
}
#[must_use]
pub fn build(self) -> PermissionSetBuilder {
let permission = Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: self.named_arguments,
};
self.parent.add_permission(permission)
}
}
pub struct FileIOPermissionBuilder {
parent: PermissionSetBuilder,
named_arguments: Vec<NamedArgument>,
}
impl FileIOPermissionBuilder {
fn new(parent: PermissionSetBuilder) -> Self {
FileIOPermissionBuilder {
parent,
named_arguments: Vec::new(),
}
}
#[must_use]
pub fn read_paths(mut self, paths: &[&str]) -> Self {
let paths_str = paths.join(";");
self.named_arguments.push(NamedArgument {
name: "Read".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String(paths_str),
});
self
}
#[must_use]
pub fn write_paths(mut self, paths: &[&str]) -> Self {
let paths_str = paths.join(";");
self.named_arguments.push(NamedArgument {
name: "Write".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String(paths_str),
});
self
}
#[must_use]
pub fn unrestricted(mut self, value: bool) -> Self {
self.named_arguments.push(NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(value),
});
self
}
#[must_use]
pub fn build(self) -> PermissionSetBuilder {
let permission = Permission {
class_name: "System.Security.Permissions.FileIOPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: self.named_arguments,
};
self.parent.add_permission(permission)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::security::{ArgumentValue, PermissionSetFormat};
#[test]
fn test_permission_set_builder_basic() {
let permissions = PermissionSetBuilder::new()
.add_security_permission()
.unrestricted(true)
.build()
.permissions();
assert_eq!(permissions.len(), 1);
assert_eq!(
permissions[0].class_name,
"System.Security.Permissions.SecurityPermission"
);
assert_eq!(permissions[0].assembly_name, "mscorlib");
assert_eq!(permissions[0].named_arguments.len(), 1);
assert_eq!(permissions[0].named_arguments[0].name, "Unrestricted");
if let ArgumentValue::Boolean(value) = &permissions[0].named_arguments[0].value {
assert!(value);
} else {
panic!("Expected boolean value for Unrestricted");
}
}
#[test]
fn test_permission_set_builder_with_encoding() {
let encoded = PermissionSetBuilder::new()
.add_security_permission()
.unrestricted(true)
.build()
.add_file_io_permission()
.read_paths(&["C:\\temp"])
.write_paths(&["C:\\logs"])
.build()
.encode(PermissionSetFormat::BinaryLegacy)
.unwrap();
assert_eq!(encoded[0], 0x2E);
assert_eq!(encoded[1], 0x02);
}
#[test]
fn test_security_permission_builder_flags() {
let permissions = PermissionSetBuilder::new()
.add_security_permission()
.flags("SkipVerification, Execution")
.build()
.permissions();
assert_eq!(permissions.len(), 1);
assert_eq!(permissions[0].named_arguments.len(), 1);
assert_eq!(permissions[0].named_arguments[0].name, "Flags");
if let ArgumentValue::String(flags) = &permissions[0].named_arguments[0].value {
assert_eq!(flags, "SkipVerification, Execution");
} else {
panic!("Expected string value for flags");
}
}
#[test]
fn test_file_io_permission_builder() {
let permissions = PermissionSetBuilder::new()
.add_file_io_permission()
.read_paths(&["C:\\Data", "C:\\Config"])
.write_paths(&["C:\\Logs"])
.unrestricted(false)
.build()
.permissions();
assert_eq!(permissions.len(), 1);
assert_eq!(
permissions[0].class_name,
"System.Security.Permissions.FileIOPermission"
);
assert_eq!(permissions[0].named_arguments.len(), 3);
let read_arg = permissions[0]
.named_arguments
.iter()
.find(|arg| arg.name == "Read")
.expect("Should have Read argument");
if let ArgumentValue::String(paths) = &read_arg.value {
assert_eq!(paths, "C:\\Data;C:\\Config");
} else {
panic!("Expected string value for Read paths");
}
let write_arg = permissions[0]
.named_arguments
.iter()
.find(|arg| arg.name == "Write")
.expect("Should have Write argument");
if let ArgumentValue::String(paths) = &write_arg.value {
assert_eq!(paths, "C:\\Logs");
} else {
panic!("Expected string value for Write paths");
}
let unrestricted_arg = permissions[0]
.named_arguments
.iter()
.find(|arg| arg.name == "Unrestricted")
.expect("Should have Unrestricted argument");
if let ArgumentValue::Boolean(value) = &unrestricted_arg.value {
assert!(!value);
} else {
panic!("Expected boolean value for Unrestricted");
}
}
#[test]
fn test_mixed_permission_builder() {
let permissions = PermissionSetBuilder::new()
.add_security_permission()
.flags("Execution, ControlEvidence")
.build()
.add_file_io_permission()
.read_paths(&["C:\\Data"])
.build()
.permissions();
assert_eq!(permissions.len(), 2);
let security_perm = &permissions[0];
assert_eq!(
security_perm.class_name,
"System.Security.Permissions.SecurityPermission"
);
let fileio_perm = &permissions[1];
assert_eq!(
fileio_perm.class_name,
"System.Security.Permissions.FileIOPermission"
);
}
#[test]
fn test_builder_default_implementation() {
let builder1 = PermissionSetBuilder::new();
let builder2 = PermissionSetBuilder::default();
assert_eq!(builder1.permissions().len(), builder2.permissions().len());
}
#[test]
fn test_compressed_format_encoding() {
let encoded = PermissionSetBuilder::new()
.add_security_permission()
.unrestricted(true)
.build()
.encode(PermissionSetFormat::BinaryCompressed)
.unwrap();
assert_eq!(encoded[0], 0x2F);
}
#[test]
fn test_xml_format_encoding() {
let encoded = PermissionSetBuilder::new()
.add_security_permission()
.unrestricted(true)
.build()
.encode(PermissionSetFormat::Xml)
.unwrap();
let xml_str = String::from_utf8(encoded).unwrap();
assert!(xml_str.contains("<PermissionSet"));
assert!(xml_str.contains("System.Security.Permissions.SecurityPermission"));
assert!(xml_str.contains("Unrestricted=\"true\""));
assert!(xml_str.contains("</PermissionSet>"));
}
}