adaptive_pipeline_domain/value_objects/file_permissions.rs
1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # File Permissions Value Objects
9//!
10//! This module provides comprehensive file permission management and validation
11//! for the adaptive pipeline system. It includes permission modeling,
12//! restoration validation, and security enforcement to ensure safe file
13//! operations across different platforms.
14//!
15//! ## Features
16//!
17//! - **Permission Modeling**: Cross-platform file permission representation
18//! - **Validation Rules**: Domain-driven permission validation for file
19//! operations
20//! - **Restoration Safety**: Comprehensive checks for file restoration
21//! operations
22//! - **Security Enforcement**: Prevention of unauthorized file access and
23//! modification
24//! - **Cross-Platform Support**: Unified permission handling across Unix and
25//! Windows
26//!
27//! ## Architecture
28//!
29//! The module follows Domain-Driven Design principles with value objects for
30//! permissions and domain services for validation. It integrates with the
31//! pipeline's security model to ensure all file operations respect system and
32//! user-defined permission constraints.
33//!
34//! ## Usage Examples
35
36use crate::PipelineError;
37use std::path::Path;
38
39/// File permission requirements and validation rules for secure file
40/// operations.
41///
42/// `FilePermissions` is a value object that encapsulates file access
43/// permissions in a cross-platform manner, supporting both Unix-style
44/// permission bits and Windows-style access control for comprehensive file
45/// security management.
46///
47/// ## Key Features
48///
49/// - **Cross-Platform Support**: Unified permission model for Unix and Windows
50/// - **Permission Validation**: Ensures operations respect security constraints
51/// - **Immutable Design**: Value object semantics prevent accidental
52/// modification
53/// - **Standard Presets**: Common permission patterns for typical use cases
54/// - **Compatibility Checking**: Validates if permissions meet requirements
55///
56/// ## Permission Model
57///
58/// The permission model supports three basic access types:
59/// - **Read**: Ability to read file contents
60/// - **Write**: Ability to modify file contents
61/// - **Execute**: Ability to execute the file (Unix) or run as program
62/// (Windows)
63///
64/// ## Usage Patterns
65///
66///
67/// ## Security Considerations
68///
69/// - Permissions are validated before file operations
70/// - Default permissions follow principle of least privilege
71/// - Cross-platform compatibility ensures consistent security
72/// - Mode bits provide fine-grained control on Unix systems
73///
74/// ## Cross-Language Compatibility
75///
76/// - **JSON**: Object with read/write/execute boolean fields
77/// - **Go**: Struct with similar field layout and os.FileMode integration
78/// - **Python**: Dataclass with stat module compatibility
79/// - **Database**: Separate columns for each permission type plus mode
80#[derive(Debug, Clone, PartialEq)]
81pub struct FilePermissions {
82 /// Whether the file can be read
83 pub read: bool,
84 /// Whether the file can be written
85 pub write: bool,
86 /// Whether the file can be executed
87 pub execute: bool,
88 /// Unix-style permission bits for fine-grained control
89 pub mode: Option<u32>,
90}
91
92impl FilePermissions {
93 /// Creates permissions for read-only access (0o444).
94 ///
95 /// This creates a permission set that allows reading but prevents writing
96 /// or executing. Suitable for configuration files, documentation, or
97 /// any content that should not be modified during pipeline operations.
98 ///
99 /// # Returns
100 ///
101 /// A `FilePermissions` instance with read-only access.
102 ///
103 /// # Examples
104 ///
105 ///
106 /// # Use Cases
107 ///
108 /// - Configuration files that should not be modified
109 /// - Documentation and reference materials
110 /// - Backup files for integrity protection
111 /// - Template files used for generation
112 pub fn read_only() -> Self {
113 Self {
114 read: true,
115 write: false,
116 execute: false,
117 mode: Some(0o444),
118 }
119 }
120
121 /// Creates permissions for read-write access (0o644).
122 ///
123 /// This creates a permission set that allows reading and writing but
124 /// prevents execution. This is the most common permission set for data
125 /// files, logs, and other content that needs to be modified during
126 /// pipeline operations.
127 ///
128 /// # Returns
129 ///
130 /// A `FilePermissions` instance with read-write access.
131 ///
132 /// # Examples
133 ///
134 ///
135 /// # Use Cases
136 ///
137 /// - Data files processed by the pipeline
138 /// - Log files and output files
139 /// - Temporary files during processing
140 /// - Cache files and intermediate results
141 pub fn read_write() -> Self {
142 Self {
143 read: true,
144 write: true,
145 execute: false,
146 mode: Some(0o644),
147 }
148 }
149
150 /// Creates permissions for full access including execution (0o755).
151 ///
152 /// This creates a permission set that allows reading, writing, and
153 /// executing. Should be used sparingly and only for files that
154 /// genuinely need to be executable, such as scripts or binary
155 /// executables.
156 ///
157 /// # Returns
158 ///
159 /// A `FilePermissions` instance with full access.
160 ///
161 /// # Examples
162 ///
163 ///
164 /// # Security Warning
165 ///
166 /// Execute permissions should be granted carefully as they allow the file
167 /// to be run as a program. Only use this for legitimate executables and
168 /// scripts.
169 ///
170 /// # Use Cases
171 ///
172 /// - Shell scripts and batch files
173 /// - Binary executables
174 /// - Pipeline processing scripts
175 /// - Utility programs and tools
176 pub fn full_access() -> Self {
177 Self {
178 read: true,
179 write: true,
180 execute: true,
181 mode: Some(0o755),
182 }
183 }
184
185 /// Validates if the current permissions satisfy the specified requirements.
186 ///
187 /// This method checks whether the current permission set provides at least
188 /// the access level specified in the required permissions. It uses
189 /// logical AND operations to ensure all required permissions are
190 /// available.
191 ///
192 /// # Arguments
193 ///
194 /// * `required` - The minimum permissions that must be satisfied
195 ///
196 /// # Returns
197 ///
198 /// `true` if current permissions meet or exceed requirements, `false`
199 /// otherwise.
200 ///
201 /// # Examples
202 ///
203 ///
204 /// # Logic
205 ///
206 /// The satisfaction logic works as follows:
207 /// - If required.read is true, self.read must be true
208 /// - If required.write is true, self.write must be true
209 /// - If required.execute is true, self.execute must be true
210 /// - All conditions must be met for satisfaction
211 pub fn satisfies(&self, required: &FilePermissions) -> bool {
212 (!required.read || self.read) && (!required.write || self.write) && (!required.execute || self.execute)
213 }
214}
215
216/// Domain rules for file restoration permission validation
217#[derive(Debug)]
218pub struct FileRestorationPermissionRules;
219
220impl FileRestorationPermissionRules {
221 /// Validates if a file can be restored to the given path
222 pub fn validate_restoration_permissions(
223 target_path: &Path,
224 overwrite_allowed: bool,
225 ) -> Result<FileRestorationPermissionValidation, PipelineError> {
226 let mut validation = FileRestorationPermissionValidation::new(target_path.to_path_buf());
227
228 // Rule 1: Target file existence check
229 if target_path.exists() {
230 validation.target_exists = true;
231 if !overwrite_allowed {
232 validation.add_violation(
233 PermissionViolationType::FileExists,
234 "Target file already exists and overwrite is not allowed".to_string(),
235 );
236 }
237 }
238
239 // Rule 2: Parent directory must exist or be creatable
240 if let Some(parent) = target_path.parent() {
241 validation.parent_directory = Some(parent.to_path_buf());
242 if !parent.exists() {
243 validation.parent_directory_exists = false;
244 validation.add_violation(
245 PermissionViolationType::DirectoryMissing,
246 format!("Parent directory does not exist: {}", parent.display()),
247 );
248 }
249 }
250
251 // Rule 3: Required permissions
252 validation.required_permissions = FilePermissions::read_write();
253
254 Ok(validation)
255 }
256}
257
258/// Result of file restoration permission validation
259#[derive(Debug)]
260pub struct FileRestorationPermissionValidation {
261 /// Target file path
262 pub target_path: std::path::PathBuf,
263 /// Whether target file exists
264 pub target_exists: bool,
265 /// Parent directory path
266 pub parent_directory: Option<std::path::PathBuf>,
267 /// Whether parent directory exists
268 pub parent_directory_exists: bool,
269 /// Required permissions for restoration
270 pub required_permissions: FilePermissions,
271 /// List of permission violations
272 pub violations: Vec<PermissionViolation>,
273}
274
275impl FileRestorationPermissionValidation {
276 pub fn new(target_path: std::path::PathBuf) -> Self {
277 Self {
278 target_path,
279 target_exists: false,
280 parent_directory: None,
281 parent_directory_exists: true,
282 required_permissions: FilePermissions::read_write(),
283 violations: Vec::new(),
284 }
285 }
286
287 pub fn add_violation(&mut self, violation_type: PermissionViolationType, message: String) {
288 self.violations.push(PermissionViolation {
289 violation_type,
290 message,
291 });
292 }
293
294 /// Returns true if validation passed (no violations)
295 pub fn is_valid(&self) -> bool {
296 self.violations.is_empty()
297 }
298
299 /// Returns all violation messages
300 pub fn violation_messages(&self) -> Vec<&str> {
301 self.violations.iter().map(|v| v.message.as_str()).collect()
302 }
303}
304
305/// Types of permission violations
306#[derive(Debug, Clone, PartialEq)]
307pub enum PermissionViolationType {
308 FileExists,
309 DirectoryMissing,
310 InsufficientPermissions,
311 ReadOnlyFile,
312 ReadOnlyFilesystem,
313 DiskSpaceInsufficient,
314}
315
316/// Represents a specific permission violation
317#[derive(Debug, Clone)]
318pub struct PermissionViolation {
319 pub violation_type: PermissionViolationType,
320 pub message: String,
321}