feat(filesystem): introduce FileOwnership and ProcessUser value objects
- Add `FileOwnership` to encapsulate file owner and group information. - Add `ProcessUser` to represent and manage system process user details. - Enhance ownership matching and debugging with structured data objects. - Include new documentation on file ownership handling and permission improvements. - Prepare infrastructure for enriched error handling in filesystem operations.
This commit is contained in:
226
docs/FILEPERMISSION_EXCEPTION_IMPROVEMENT_PLAN.md
Normal file
226
docs/FILEPERMISSION_EXCEPTION_IMPROVEMENT_PLAN.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# FilePermissionException Verbesserungs-Plan
|
||||
|
||||
## Ziel
|
||||
|
||||
Verbesserung der `FilePermissionException` um detaillierte Permission- und User-Informationen f?r besseres Debugging.
|
||||
|
||||
## Problem-Analyse
|
||||
|
||||
### Aktueller Zustand
|
||||
|
||||
Die `FilePermissionException` gibt momentan nur minimale Informationen:
|
||||
|
||||
```php
|
||||
FilePermissionException::delete($path, 'Directory is not writable: ' . $dir);
|
||||
// Output: "Permission denied for delete on file: /path/to/file (Directory is not writable: /path/to/dir)"
|
||||
```
|
||||
|
||||
**Fehlende Informationen:**
|
||||
- Aktuelle Permissions der Datei/Verzeichnisses
|
||||
- Owner/Group der Datei
|
||||
- Aktueller Process-User
|
||||
- Erwartete Permissions
|
||||
- Octal-Darstellung der Permissions
|
||||
|
||||
### Verf?gbare Infrastruktur
|
||||
|
||||
? **PermissionChecker** hat bereits `getDiagnosticInfo()`:
|
||||
- Liefert owner, group, permissions, parent_dir info
|
||||
- Nutzt `posix_getpwuid()` und `posix_getgrgid()`
|
||||
|
||||
? **ExceptionContext** hat `withData()`, `withDebug()`, `withMetadata()`
|
||||
|
||||
? **posix_geteuid()** verf?gbar f?r Process-User
|
||||
|
||||
## L?sungsplan
|
||||
|
||||
### L?sung 1: Erweitere FilePermissionException (HOHE PRIORIT?T)
|
||||
|
||||
**Neue statische Factory-Methoden mit Permission-Details:**
|
||||
|
||||
```php
|
||||
public static function delete(
|
||||
string $path,
|
||||
?string $reason = null,
|
||||
?PermissionChecker $permissionChecker = null
|
||||
): self {
|
||||
$diagnosticInfo = $permissionChecker?->getDiagnosticInfo($path) ?? [];
|
||||
$currentUser = self::getCurrentUserInfo();
|
||||
|
||||
return new self(
|
||||
path: $path,
|
||||
operation: 'delete',
|
||||
reason: $reason,
|
||||
diagnosticInfo: $diagnosticInfo,
|
||||
currentUser: $currentUser
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Neue Properties:**
|
||||
- `?array $diagnosticInfo` - Permission-Details vom PermissionChecker
|
||||
- `?array $currentUser` - Aktueller Process-User Info
|
||||
|
||||
**Verbesserte Message-Formatierung:**
|
||||
```php
|
||||
$message = "Permission denied for {$operation} on file: {$path}";
|
||||
if ($reason) {
|
||||
$message .= " ({$reason})";
|
||||
}
|
||||
|
||||
// Add detailed info if available
|
||||
if ($this->diagnosticInfo) {
|
||||
$message .= sprintf(
|
||||
"\n File owner: %s, group: %s, permissions: %s",
|
||||
$this->diagnosticInfo['owner'] ?? 'unknown',
|
||||
$this->diagnosticInfo['group'] ?? 'unknown',
|
||||
$this->diagnosticInfo['permissions'] ?? 'unknown'
|
||||
);
|
||||
|
||||
if (isset($this->diagnosticInfo['parent_dir'])) {
|
||||
$parentInfo = $permissionChecker?->getDiagnosticInfo($this->diagnosticInfo['parent_dir']) ?? [];
|
||||
$message .= sprintf(
|
||||
"\n Parent directory: %s (owner: %s, permissions: %s)",
|
||||
$this->diagnosticInfo['parent_dir'],
|
||||
$parentInfo['owner'] ?? 'unknown',
|
||||
$parentInfo['permissions'] ?? 'unknown'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->currentUser) {
|
||||
$message .= sprintf(
|
||||
"\n Current process user: %s (uid: %d, gid: %d)",
|
||||
$this->currentUser['name'],
|
||||
$this->currentUser['uid'],
|
||||
$this->currentUser['gid']
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### L?sung 2: Helper-Methode f?r Process-User Info
|
||||
|
||||
**Neue private Methode in FilePermissionException:**
|
||||
|
||||
```php
|
||||
private static function getCurrentUserInfo(): ?array
|
||||
{
|
||||
if (!function_exists('posix_geteuid')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$uid = posix_geteuid();
|
||||
$gid = posix_getegid();
|
||||
$userInfo = posix_getpwuid($uid);
|
||||
$groupInfo = posix_getgrgid($gid);
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'gid' => $gid,
|
||||
'name' => $userInfo['name'] ?? 'unknown',
|
||||
'group' => $groupInfo['name'] ?? 'unknown',
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### L?sung 3: Integration in FileStorage-Methoden
|
||||
|
||||
**Anpassung aller FilePermissionException Aufrufe:**
|
||||
|
||||
**Vorher:**
|
||||
```php
|
||||
if (! is_writable($dir)) {
|
||||
throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir);
|
||||
}
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
```php
|
||||
if (! is_writable($dir)) {
|
||||
throw FilePermissionException::delete(
|
||||
path: $path,
|
||||
reason: 'Directory is not writable: ' . $dir,
|
||||
permissionChecker: $this->permissions
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Betroffene Stellen:**
|
||||
1. `FileStorage::get()` - read exception
|
||||
2. `FileStorage::put()` - write exception, createDirectory exception
|
||||
3. `FileStorage::delete()` - delete exception
|
||||
4. `FileStorage::createDirectory()` - createDirectory exception
|
||||
5. Alle anderen Stellen, die FilePermissionException werfen
|
||||
|
||||
### L?sung 4: ExceptionContext erweitern
|
||||
|
||||
**Erweiterte Context-Daten in Exception:**
|
||||
|
||||
```php
|
||||
$context = ExceptionContext::forOperation('file.permission', 'filesystem')
|
||||
->withData([
|
||||
'path' => $path,
|
||||
'operation' => $operation,
|
||||
'reason' => $reason,
|
||||
])
|
||||
->withDebug([
|
||||
'file_permissions' => $diagnosticInfo['permissions'] ?? null,
|
||||
'file_owner' => $diagnosticInfo['owner'] ?? null,
|
||||
'file_group' => $diagnosticInfo['group'] ?? null,
|
||||
'parent_dir' => $diagnosticInfo['parent_dir'] ?? null,
|
||||
'parent_writable' => $diagnosticInfo['parent_writable'] ?? null,
|
||||
])
|
||||
->withMetadata([
|
||||
'current_user' => $currentUser,
|
||||
'file_exists' => $diagnosticInfo['exists'] ?? false,
|
||||
'is_file' => $diagnosticInfo['is_file'] ?? false,
|
||||
'is_dir' => $diagnosticInfo['is_dir'] ?? false,
|
||||
]);
|
||||
```
|
||||
|
||||
## Implementierungsreihenfolge
|
||||
|
||||
1. ? **L?sung 2** - Helper-Methode f?r Process-User (einfach, keine Dependencies)
|
||||
2. ? **L?sung 1** - Erweitere FilePermissionException Constructor und Factory-Methoden
|
||||
3. ? **L?sung 3** - Integration in FileStorage-Methoden
|
||||
4. ? **L?sung 4** - ExceptionContext erweitern
|
||||
|
||||
## Beispiel-Output
|
||||
|
||||
### Vorher:
|
||||
```
|
||||
Permission denied for delete on file: /var/www/html/storage/cache/file.cache.php (Directory is not writable: /var/www/html/storage/cache)
|
||||
```
|
||||
|
||||
### Nachher:
|
||||
```
|
||||
Permission denied for delete on file: /var/www/html/storage/cache/file.cache.php (Directory is not writable: /var/www/html/storage/cache)
|
||||
File owner: www-data, group: www-data, permissions: drwxrwxr-x
|
||||
Parent directory: /var/www/html/storage/cache (owner: root, permissions: drwxr-xr-x)
|
||||
Current process user: www-data (uid: 33, gid: 33)
|
||||
```
|
||||
|
||||
## Vorteile
|
||||
|
||||
1. **Besseres Debugging**: Sofort sichtbar, welche Permissions falsch sind
|
||||
2. **User-Info**: Zeigt Owner-Mismatch sofort
|
||||
3. **Context**: Vollst?ndige Permission-Info in Exception-Context f?r Logging
|
||||
4. **R?ckw?rtskompatibel**: Alte Aufrufe funktionieren weiterhin (optional Parameter)
|
||||
|
||||
## Performance-?berlegungen
|
||||
|
||||
- `getDiagnosticInfo()` ist relativ teuer (mehrere `stat()` Calls)
|
||||
- **L?sung**: Nur bei Exception-Throw aufrufen (nicht bei jedem Check)
|
||||
- Optional: K?nnte in `withDebug()` ausgelagert werden, wenn Performance kritisch ist
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Unit-Tests**: FilePermissionException mit Permission-Details
|
||||
2. **Integration-Tests**: FileStorage-Methoden mit verschiedenen Permission-Szenarien
|
||||
3. **E2E-Tests**: Cache-Operationen mit Permission-Problemen
|
||||
|
||||
## Dokumentation
|
||||
|
||||
Nach Implementierung aktualisieren:
|
||||
- `docs/claude/error-handling.md` - FilePermissionException mit Details
|
||||
- `docs/deployment/cache-configuration.md` - Exception-Beispiele
|
||||
Reference in New Issue
Block a user