use super::{AccessMask, Ace, AceType, Dacl, PermissionTarget, SecurityDescriptor, Sid};
use crate::Result;
use crate::error::{Error, PermissionEditError, SecurityError, SecurityUnsupportedError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApplyMode {
ValidateOnly,
Apply,
DryRunDiff,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PermissionEditPolicy {
pub preserve_inherited: bool,
pub reject_conflicting_changes: bool,
}
impl Default for PermissionEditPolicy {
fn default() -> Self {
Self {
preserve_inherited: true,
reject_conflicting_changes: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PermissionOperation {
Grant {
trustee: Sid,
access: AccessMask,
},
Deny {
trustee: Sid,
access: AccessMask,
},
Revoke {
trustee: Sid,
access: Option<AccessMask>,
},
ReplaceTrustee {
old_trustee: Sid,
new_trustee: Sid,
},
}
#[derive(Debug, Clone)]
pub struct PermissionEditor {
policy: PermissionEditPolicy,
operations: Vec<PermissionOperation>,
}
impl PermissionEditor {
pub fn new() -> Self {
Self {
policy: PermissionEditPolicy::default(),
operations: Vec::new(),
}
}
pub fn policy(mut self, policy: PermissionEditPolicy) -> Self {
self.policy = policy;
self
}
pub fn grant(mut self, trustee: Sid, access: AccessMask) -> Self {
self.operations
.push(PermissionOperation::Grant { trustee, access });
self
}
pub fn deny(mut self, trustee: Sid, access: AccessMask) -> Self {
self.operations
.push(PermissionOperation::Deny { trustee, access });
self
}
pub fn revoke(mut self, trustee: Sid, access: Option<AccessMask>) -> Self {
self.operations
.push(PermissionOperation::Revoke { trustee, access });
self
}
pub fn replace_trustee(mut self, old_trustee: Sid, new_trustee: Sid) -> Self {
self.operations.push(PermissionOperation::ReplaceTrustee {
old_trustee,
new_trustee,
});
self
}
pub fn build(self) -> Result<PermissionEditPlan> {
if self.policy.reject_conflicting_changes {
for op in &self.operations {
if let PermissionOperation::Grant { trustee, access } = op {
let conflict = self.operations.iter().any(|other| {
matches!(other, PermissionOperation::Deny { trustee: t, access: a } if t == trustee && a == access)
});
if conflict {
return Err(Error::Security(SecurityError::PermissionEdit(
PermissionEditError::new(
"grant/deny",
"conflicting allow and deny operation for same trustee and mask",
),
)));
}
}
}
}
Ok(PermissionEditPlan {
policy: self.policy,
operations: self.operations,
})
}
}
impl Default for PermissionEditor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PermissionEditPlan {
policy: PermissionEditPolicy,
operations: Vec<PermissionOperation>,
}
impl PermissionEditPlan {
pub fn execute(&self, current: &Dacl, mode: ApplyMode) -> PermissionEditResult {
let mut next = current.clone();
if matches!(mode, ApplyMode::ValidateOnly) {
return PermissionEditResult {
mode,
updated_dacl: None,
diff: PermissionDiff::between(current, current),
};
}
for op in &self.operations {
match op {
PermissionOperation::Grant { trustee, access } => {
next.entries_mut()
.push(Ace::new(trustee.clone(), AceType::Allow, *access));
}
PermissionOperation::Deny { trustee, access } => {
next.entries_mut()
.push(Ace::new(trustee.clone(), AceType::Deny, *access));
}
PermissionOperation::Revoke { trustee, access } => {
next.entries_mut().retain(|ace| {
if self.policy.preserve_inherited && ace.inherited {
return true;
}
if &ace.trustee != trustee {
return true;
}
match access {
Some(mask) => ace.access_mask != *mask,
None => false,
}
});
}
PermissionOperation::ReplaceTrustee {
old_trustee,
new_trustee,
} => {
for ace in next.entries_mut().iter_mut() {
if self.policy.preserve_inherited && ace.inherited {
continue;
}
if ace.trustee == *old_trustee {
ace.trustee = new_trustee.clone();
}
}
}
}
}
next.canonicalize();
PermissionEditResult {
mode,
updated_dacl: if matches!(mode, ApplyMode::Apply) {
Some(next.clone())
} else {
None
},
diff: PermissionDiff::between(current, &next),
}
}
pub fn execute_on_descriptor(
&self,
current: &SecurityDescriptor,
mode: ApplyMode,
) -> DescriptorEditResult {
let dacl_result = self.execute(current.dacl(), mode);
let mut updated = None;
if let Some(next_dacl) = dacl_result.updated_dacl.clone() {
let mut descriptor = current.clone();
*descriptor.dacl_mut() = next_dacl;
updated = Some(descriptor);
}
DescriptorEditResult {
mode: dacl_result.mode,
updated_descriptor: updated,
diff: dacl_result.diff,
}
}
pub fn execute_for_target(
&self,
target: &PermissionTarget,
current: &SecurityDescriptor,
mode: ApplyMode,
) -> Result<DescriptorEditResult> {
let result = self.execute_on_descriptor(current, mode);
if matches!(mode, ApplyMode::Apply) {
if let Some(updated) = &result.updated_descriptor {
target.write_descriptor(updated)?;
} else {
return Err(Error::Security(SecurityError::Unsupported(
SecurityUnsupportedError::new(
"permission_target",
"apply_without_updated_descriptor",
),
)));
}
}
Ok(result)
}
pub fn execute_against_target(
&self,
target: &PermissionTarget,
mode: ApplyMode,
) -> Result<DescriptorEditResult> {
let current = target.read_descriptor()?;
self.execute_for_target(target, ¤t, mode)
}
}
#[derive(Debug, Clone)]
pub struct PermissionEditResult {
pub mode: ApplyMode,
pub updated_dacl: Option<Dacl>,
pub diff: PermissionDiff,
}
#[derive(Debug, Clone)]
pub struct DescriptorEditResult {
pub mode: ApplyMode,
pub updated_descriptor: Option<SecurityDescriptor>,
pub diff: PermissionDiff,
}
#[derive(Debug, Clone, Default)]
pub struct PermissionDiff {
pub added: Vec<Ace>,
pub removed: Vec<Ace>,
}
impl PermissionDiff {
fn between(before: &Dacl, after: &Dacl) -> Self {
let mut added = Vec::new();
let mut removed = Vec::new();
for ace in after.entries() {
if !before.entries().contains(ace) {
added.push(ace.clone());
}
}
for ace in before.entries() {
if !after.entries().contains(ace) {
removed.push(ace.clone());
}
}
Self { added, removed }
}
}
#[cfg(test)]
mod tests {
use super::{AccessMask, ApplyMode, PermissionEditor};
use crate::security::{Ace, AceType, Dacl, SecurityDescriptor, Sid};
#[test]
fn build_rejects_conflicting_grant_and_deny() {
let sid = Sid::parse("S-1-5-32-545").expect("valid sid");
let err = PermissionEditor::new()
.grant(sid.clone(), AccessMask::from_bits(0x1))
.deny(sid, AccessMask::from_bits(0x1))
.build()
.expect_err("expected conflict");
assert!(err.to_string().contains("conflicting allow and deny"));
}
#[test]
fn dry_run_diff_reports_added_ace() {
let sid = Sid::parse("S-1-5-32-545").expect("valid sid");
let dacl = Dacl::new();
let plan = PermissionEditor::new()
.grant(sid.clone(), AccessMask::from_bits(0x2))
.build()
.expect("build plan");
let result = plan.execute(&dacl, ApplyMode::DryRunDiff);
assert_eq!(result.diff.added.len(), 1);
assert!(result.updated_dacl.is_none());
assert_eq!(
result.diff.added[0],
Ace::new(sid, AceType::Allow, AccessMask::from_bits(0x2))
);
}
#[test]
fn execute_on_descriptor_updates_descriptor_dacl() {
let sid = Sid::parse("S-1-5-32-545").expect("valid sid");
let descriptor = SecurityDescriptor::new().with_dacl(Dacl::new());
let plan = PermissionEditor::new()
.grant(sid, AccessMask::from_bits(0x4))
.build()
.expect("build plan");
let result = plan.execute_on_descriptor(&descriptor, ApplyMode::Apply);
assert!(result.updated_descriptor.is_some());
assert_eq!(result.diff.added.len(), 1);
}
}