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}