This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
LegalAssistance/.taskmaster/tasks/tasks.json
T
chaim 3178904b28 feat: place generated report in the CaseFiles case folder (P7)
DirectAccessReportGenerator now, when CaseFilesCore is installed (soft-detected,
no hard dependency), sets the generated Document's folderId to the case
"מסמכים" folder via CaseFolderService so it shows in the CaseDocs panel. The
existing Case link + best-effort NetworkStorage upload are unchanged.

Verified on dev (P7_OK): php -l clean; Case.documents relate is visible via
Document.cases (shared junction); folder placement works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:53:23 +00:00

67 lines
15 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"master": {
"tasks": [
{
"id": 1,
"title": "fix: subject-only direct-access report naming (tight hyphen) + case-folder upload",
"description": "Report file uses subject-only canonical naming and is uploaded to the case folder correctly.",
"details": "DirectAccessReportGenerator: em-dash -> tight hyphen; sanitize via LocalFilesystemClient::sanitizeFileName; save Document with skipNetworkUpload, relate to Case, explicit uploadDocument + DB realign to canonical stored name.",
"testStrategy": "",
"status": "done",
"dependencies": [],
"priority": "high",
"subtasks": [],
"updatedAt": "2026-06-03T08:19:08.135Z"
},
{
"id": 2,
"title": "תיקון אבטחה: הסרת מחיקת טבלאות ועמודות מ-AfterUninstall",
"description": "הסרת הקריאות ל-dropTables ו-cleanCaseColumns מ-AfterUninstall.php כדי למנוע אובדן נתונים בלתי הפיך בעת הסרה/התקנה מחדש של ההרחבה.",
"details": "## רקע\n\nהממצא F-002 בביקורת האבטחה (SECURITY_AUDIT_2026-04-25.md:137-168) מזהה שהסקריפט `scripts/AfterUninstall.php` מבצע פעולות הרסניות:\n\n1. **מחיקת טבלאות** (שורות 54-73): `DROP TABLE legal_aid` ו-`DROP TABLE direct_access_report`\n2. **מחיקת עמודות** (שורות 75-101): `ALTER TABLE case DROP COLUMN` על 5 עמודות (`c_legal_aid_type`, `c_aid_type`, `c_urgency_level`, `c_appointment_date`, `c_filing_deadline`)\n\nזה מפר את **כלל N2** מ-EXTENSION_DEVELOPMENT_RULES.md:\n> כאשר הרחבה מוסיפה שדות לישויות ליבה (Case, Account, Contact וכו'), הסקריפט AfterUninstall חייב להסיר רק את ההפניות מ-layouts אבל **להשאיר את עמודות הדאטאבייס**, כך שנתוני הלקוח ישרדו מחזור הסרה/התקנה מחדש.\n\n## שינויים נדרשים\n\n### 1. הסרת הפונקציה `dropTables()` (שורות 54-73)\nמחק את כל הפונקציה והקריאה אליה בשורה 31.\n\n### 2. הסרת הפונקציה `cleanCaseColumns()` (שורות 75-101)\nמחק את כל הפונקציה והקריאה אליה בשורה 34.\n\n### 3. הוספת פונקציית ניקוי layouts (אופציונלי)\nאם יש layouts ב-`custom/Espo/Custom/Resources/layouts/Case/*.json` שמפנים לשדות שנוספו, יש להסיר את ההפניות:\n\n```php\nprivate function removeLayoutReferences(Log $log): void\n{\n $layoutDir = 'custom/Espo/Custom/Resources/layouts/Case';\n if (!is_dir($layoutDir)) {\n return;\n }\n\n $fieldsToRemove = [\n 'cLegalAidType',\n 'cAidType',\n 'cUrgencyLevel',\n 'cAppointmentDate',\n 'cFilingDeadline',\n ];\n\n foreach (glob($layoutDir . '/*.json') as $file) {\n $content = file_get_contents($file);\n $layout = json_decode($content, true);\n if (!$layout) continue;\n\n $modified = $this->removeFieldsFromLayout($layout, $fieldsToRemove);\n if ($modified) {\n file_put_contents($file, json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));\n $log->info(\"LegalAssistance: Removed field references from {$file}\");\n }\n }\n}\n\nprivate function removeFieldsFromLayout(array &$layout, array $fields): bool\n{\n $modified = false;\n foreach ($layout as $key => &$value) {\n if (is_array($value)) {\n if (isset($value['name']) && in_array($value['name'], $fields)) {\n unset($layout[$key]);\n $modified = true;\n } else {\n $modified = $this->removeFieldsFromLayout($value, $fields) || $modified;\n }\n }\n }\n $layout = array_values($layout); // Re-index\n return $modified;\n}\n```\n\n### 4. עדכון תיעוד הקוד בראש הקובץ\nשנה את התיאור ב-PHPDoc להסביר שהסקריפט מסיר רק:\n- רשומות DocumentTemplate\n- קבצי i18n\n- הפניות ב-layouts (אם רלוונטי)\n- וניקוי cache\n\n**לא** מוחק טבלאות או עמודות.\n\n## מבנה הקובץ לאחר התיקון\n\n```php\n<?php\n/************************************************************************\n * LegalAssistance Extension — AfterUninstall\n *\n * 1. Remove DocumentTemplate records created by this extension\n * 2. Remove custom i18n files\n * 3. (Optional) Remove layout references to extension fields\n * 4. Clear cache\n *\n * Note: Database tables and columns are intentionally preserved to\n * protect customer data during uninstall/reinstall cycles (rule N2).\n ************************************************************************/\n\nclass AfterUninstall\n{\n public function run(Container $container): void\n {\n // ... (שמירה על removeDocumentTemplates, removeCustomI18n, clearCache)\n // הסרה מלאה של dropTables ו-cleanCaseColumns\n }\n}\n```",
"testStrategy": "## אסטרטגיית בדיקה\n\n### 1. בדיקת יחידה - סקריפט האנאינסטול\n- וודא שהקובץ `AfterUninstall.php` לא מכיל את המילים `DROP TABLE` או `DROP COLUMN`\n- וודא שאין קריאות לפונקציות `dropTables` או `cleanCaseColumns`\n\n### 2. בדיקת אינטגרציה בסביבת פיתוח\n1. **הכנה**: התקן את ההרחבה על instance ריק של EspoCRM\n2. **יצירת נתונים**: צור Case עם ערכים בשדות הרלוונטיים (אם קיימים)\n3. **הסרה**: הסר את ההרחבה דרך Administration → Extensions\n4. **אימות**: בדוק שהטבלאות `legal_aid` ו-`direct_access_report` עדיין קיימות (אם היו מלכתחילה)\n5. **אימות**: בדוק שהעמודות ב-Case עדיין קיימות (אם היו מלכתחילה)\n6. **התקנה מחדש**: התקן את ההרחבה שוב\n7. **אימות**: וודא שהנתונים שנשמרו בשלב 2 עדיין קיימים\n\n### 3. בדיקת לוגים\n- לאחר הסרה, בדוק את `data/logs/espo-YYYY-MM-DD.log`\n- וודא שאין שורות `Dropping table` או `Dropped column`\n- וודא שיש שורות `Removed DocumentTemplate` ו-`Cache cleared`\n\n### 4. בדיקת קוד סטטית\n```bash\n# בדוק שאין DROP statements\ngrep -n \"DROP TABLE\\|DROP COLUMN\" scripts/AfterUninstall.php\n# צפוי: אין תוצאות\n\n# בדוק שאין קריאות לפונקציות שהוסרו\ngrep -n \"dropTables\\|cleanCaseColumns\" scripts/AfterUninstall.php\n# צפוי: אין תוצאות\n```\n\n### 5. בדיקת רגרסיה\n- וודא שה-DocumentTemplate עדיין נמחק כראוי\n- וודא שקבצי i18n עדיין נמחקים כראוי\n- וודא ש-cache clearing עדיין עובד",
"status": "pending",
"dependencies": [
1
],
"priority": "high",
"subtasks": []
},
{
"id": 3,
"title": "תיקון אבטחה: הוספת בדיקת ACL ברמת רשומה ב-DirectAccessReportGenerator",
"description": "הוספת בדיקת הרשאות ברמת רשומה (record-level ACL) לכלי generate_direct_access_report למניעת IDOR — כרגע הכלי סומך רק על בדיקת scope-level ומאפשר למשתמש עם הרשאת קריאה לתיקים לצרף מסמכים לכל תיק במערכת.",
"details": "## רקע\n\nהממצא F-001 בביקורת האבטחה (SECURITY_AUDIT_2026-04-25.md:61-134) מזהה פגיעות IDOR חמורה:\n\nהכלי `DirectAccessReportGenerator.php` מקבל `caseId` שרירותי ומייצר מסמך DOCX שמצורף לתיק ללא בדיקת הרשאות ברמת הרשומה. הבדיקה היחידה שמתבצעת היא ב-upstream (SmartAssistant ActionExecutor) ברמת scope בלבד (`acl->checkScope('Case', 'read')`).\n\n**וקטור התקיפה:** כל משתמש EspoCRM מאומת עם הרשאת קריאה לתיקים יכול:\n1. לקרוא ל-`POST /api/v1/SmartAssistant/action/executeTool` עם `caseId` של תיק שאינו שלו\n2. הכלי קורא ל-`getEntityById('Case', $caseId)` שעוקף ACL כברירת מחדל\n3. מסמך DOCX מזויף נוצר ומצורף לתיק הקורבן\n4. המסמך מכיל תוכן שנשלט על ידי התוקף אך נושא את שם עורך הדין המוקצה לתיק\n\n## שינויים נדרשים\n\n### 1. הזרקת AclManager לקונסטרקטור\n\n```php\n// files/custom/Espo/Modules/LegalAssistance/SmartAssistant/Tools/DirectAccessReportGenerator.php\n\nuse Espo\\Core\\Acl;\nuse Espo\\Core\\Exceptions\\Forbidden;\n\n// ...\n\npublic function __construct(\n private EntityManager $entityManager,\n private InjectableFactory $injectableFactory,\n private Log $log,\n private User $user,\n private Acl $acl // הוסף\n) {}\n```\n\n### 2. בדיקת הרשאות ברמת רשומה בתחילת execute()\n\nבתחילת המתודה `execute()`, מיד אחרי שליפת התיק (שורה 41-44):\n\n```php\npublic function execute(array $params, ?string $caseId, string $userId): array\n{\n if (!$caseId) {\n throw new Error('generate_direct_access_report requires a case context.');\n }\n\n $this->log->info(\"LegalAssistance: Generating direct access report for case {$caseId}\");\n\n $case = $this->entityManager->getEntityById('Case', $caseId);\n if (!$case) {\n throw new Error(\"Case {$caseId} not found.\");\n }\n\n // === תיקון אבטחה F-001: בדיקת ACL ברמת רשומה ===\n // נדרשת הרשאת עריכה כי אנחנו מצרפים מסמך לתיק\n if (!$this->acl->checkEntityEdit($case)) {\n $this->log->warning(\n \"LegalAssistance: ACL denied — user {$this->user->getId()} \" .\n \"attempted to attach document to case {$caseId}\"\n );\n throw new Forbidden(\"אין לך הרשאה לצרף מסמכים לתיק זה.\");\n }\n\n // ... המשך הקוד הקיים\n}\n```\n\n### 3. שיקולי מימוש\n\n- **למה `checkEntityEdit` ולא `checkEntityRead`?** — יצירת Document וקישורו לתיק היא פעולת כתיבה, לא קריאה. משתמש עם read-only על התיק לא צריך לוכל לשנות את רשימת המסמכים שלו.\n\n- **למה `$this->acl` ולא `AclManager`?** — ה-`Acl` service כבר מוזרק עם ה-User הנכון (המשתמש המבצע את הבקשה). שימוש ב-`AclManager->checkEntity($user, $entity, 'edit')` היה דורש לשלוף את ה-User לפי `$userId` — מיותר כש-DI כבר מספק לנו את זה.\n\n- **לוגים:** חשוב לתעד ניסיונות גישה שנדחו לצורך אבחון וניטור אבטחה.\n\n- **שגיאה בעברית:** ההודעה מוחזרת למשתמש דרך shira-hermes, אז עברית מתאימה לחוויית משתמש.\n\n### 4. קובץ יחיד לעריכה\n\n`files/custom/Espo/Modules/LegalAssistance/SmartAssistant/Tools/DirectAccessReportGenerator.php`\n\n**שורות לעריכה:**\n- שורות 12-19: הוספת `use` statements\n- שורות 26-31: הוספת `Acl` לקונסטרקטור\n- שורות 40-44: הוספת בדיקת ACL אחרי שליפת התיק",
"testStrategy": "## אסטרטגיית בדיקה\n\n### 1. בדיקה ידנית — מניעת IDOR\n\n**הכנה:**\n1. צור שני משתמשים ב-EspoCRM:\n - `lawyer-a` — עם תיק משויך (נקרא לו `case-a`)\n - `lawyer-b` — ללא גישה ל-`case-a`\n\n2. וודא ש-`lawyer-b` יכול לקרוא תיקים (scope-level read) אבל לא את `case-a` (record-level)\n\n**בדיקה:**\n1. התחבר כ-`lawyer-b`\n2. קרא ל-API:\n ```bash\n curl -X POST https://espocrm.dev.marcus-law.co.il/api/v1/SmartAssistant/action/executeTool \\\n -H \"Content-Type: application/json\" \\\n -H \"X-Api-Key: <lawyer-b-api-key>\" \\\n -d '{\n \"tool\": \"generate_direct_access_report\",\n \"caseId\": \"<case-a-id>\",\n \"params\": {\"aidType\": \"יעוץ והדרכה\"}\n }'\n ```\n3. **תוצאה צפויה:** שגיאת 403 Forbidden עם הודעה \"אין לך הרשאה לצרף מסמכים לתיק זה.\"\n4. וודא שלא נוצר Document חדש ב-`case-a`\n\n### 2. בדיקת regression — פעולה תקינה\n\n1. התחבר כ-`lawyer-a` (בעל התיק)\n2. קרא לאותו API עם `caseId` של התיק שלו\n3. **תוצאה צפויה:** הדוח נוצר בהצלחה ומצורף לתיק\n\n### 3. בדיקת לוגים\n\n1. לאחר ניסיון IDOR שנדחה, בדוק בלוג:\n ```bash\n docker exec <espocrm-container> tail -20 data/logs/espo-$(date +%Y-%m-%d).log | grep \"ACL denied\"\n ```\n2. **תוצאה צפויה:** שורת לוג עם WARNING המציינת את user ID וה-case ID\n\n### 4. בדיקת אינטגרציה עם shira-hermes\n\n1. דרך ממשק Shira (צ'אט), בקש מעוזר המשפט ליצור דוח לתיק שאין לך גישה אליו\n2. **תוצאה צפויה:** הודעת שגיאה ידידותית בעברית מוחזרת למשתמש\n\n### 5. בדיקת unit (אופציונלי)\n\nאם קיימת תשתית PHPUnit בפרויקט:\n\n```php\npublic function testExecuteThrowsForbiddenWhenUserCannotEditCase(): void\n{\n // Mock Acl to return false for checkEntityEdit\n $acl = $this->createMock(Acl::class);\n $acl->method('checkEntityEdit')->willReturn(false);\n \n $generator = new DirectAccessReportGenerator(\n $this->entityManager,\n $this->injectableFactory,\n $this->log,\n $this->user,\n $acl\n );\n \n $this->expectException(Forbidden::class);\n $generator->execute(['aidType' => 'test'], 'some-case-id', 'user-id');\n}\n```",
"status": "pending",
"dependencies": [
1
],
"priority": "high",
"subtasks": []
},
{
"id": 4,
"title": "feat: P7 — שיוך דוח גישה ישירה לתיקיית CaseFiles",
"description": "הוספת ענף CaseFiles ליצירת המסמכים: אם CaseFilesCore מותקן (soft-detect), המסמך שנוצר משויך לתיקיית 'מסמכים' של התיק (folderId) כדי שיופיע בפאנל CaseDocs. ההתנהגות הקיימת (קישור לתיק + העלאה ל-NetworkStorage אם פעיל) נשמרת — ללא רגרסיה.",
"details": "אומת על dev 21/06/2026 (P7_OK): php -l נקי; קישור דרך Case.documents נראה דרך Document.cases (אותה טבלת קישור), folderId נקבע לתיקיית 'מסמכים'. ענף זהה לנתב P5/P6 המאומת.",
"testStrategy": "dev: יצירת מסמך עם CaseFilesCore מותקן → folderId=מסמכים, נראה בפאנל.",
"status": "done",
"dependencies": [],
"priority": "medium",
"subtasks": []
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-06-03T08:19:08.136Z",
"taskCount": 1,
"completedCount": 1,
"tags": [
"master"
],
"created": "2026-06-20T12:07:53.783Z",
"description": "Tasks for master context",
"updated": "2026-06-21"
}
}
}