Skip to content

feat: Implement signalering widgets (#213)#270

Draft
rubenvdlinde wants to merge 18 commits intodevelopmentfrom
feature/213/signalering-widgets
Draft

feat: Implement signalering widgets (#213)#270
rubenvdlinde wants to merge 18 commits intodevelopmentfrom
feature/213/signalering-widgets

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Closes #213

Summary

Implement Signalering Widgets V1 for Procest — dashboard widgets providing proactive deadline awareness to case handlers. This feature surfaces three time-sensitive alert types:

  1. Deadline Alerts — cases approaching or past their processing deadline (with color-coded severity: red/overdue, orange/at-risk)
  2. Task Reminders — current user's tasks approaching or past due dates
  3. Stalled Cases — cases inactive for 7+ days (no status change or file updates)

All computation is client-side from already-loaded case/task data; no new API calls. Fully integrated with Nextcloud Dashboard widget system, allowing users to add/remove widgets from picker.

Spec Reference

Changes

  • lib/Dashboard/DeadlineAlertsWidget.php — Nextcloud IWidget adapter (adds @SPEC tag)
  • lib/Dashboard/TaskRemindersWidget.php — Nextcloud IWidget adapter (adds @SPEC tag)
  • lib/Dashboard/StalledCasesWidget.php — Nextcloud IWidget adapter (adds @SPEC tag)
  • src/utils/dashboardHelpers.js — Helper functions (getDeadlineAlerts, getTaskDueReminders, getStalledCases)
  • src/views/dashboard/DeadlineAlerts.vue — Dashboard component showing deadline alerts
  • src/views/dashboard/TaskDueReminders.vue — Dashboard component showing task reminders
  • src/views/dashboard/StalledCases.vue — Dashboard component showing stalled cases
  • src/views/widgets/DeadlineAlertsWidget.vue — Nextcloud widget component
  • src/views/widgets/TaskRemindersWidget.vue — Nextcloud widget component
  • src/views/widgets/StalledCasesWidget.vue — Nextcloud widget component
  • lib/AppInfo/Application.php — Widget registration (already done)
  • openspec/changes/signalering-widgets/design.md — Design document
  • openspec/changes/signalering-widgets/tasks.md — Task tracking

Test Coverage

  • tests/Unit/Dashboard/SignaleringWidgetsTest.php — Unit tests for all three widget classes
    • Widget ID uniqueness
    • Widget title/URL availability
    • Widget order and icon class

Notes

  • V1 Scope: Dashboard widgets only (per spec.md)
  • Performance: No new API calls; O(n) computation over already-loaded data
  • Accessibility: Severity indicators use both color and text for colorblind users
  • Localization: All labels support t('procest', '...') for English/Dutch

🤖 Generated with Claude Code

Hydra Builder and others added 11 commits April 18, 2026 20:40
- Fix OWASP A05:2021 (CWE-209): Do not expose exception messages to client in error responses
  - Changed generic error responses in DeadlineNotificationController (lines 118, 164)
  - Changed generic error responses in SignaleringConfigController (lines 85, 151, 229)
- Fix OWASP A07:2021 (CWE-306): Add authentication check to notifyWebhook endpoint
  - Added Authorization/X-Webhook-Secret header validation
- Fix OWASP A01:2021 (CWE-639): Add per-object authorization check to getDeadlines endpoint
  - Added check for case ownership, group membership, or admin status
- Auto-fix phpcs style issues (phpcbf)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/procest @ 4647214

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-04-21 04:52 UTC

Download the full PDF report from the workflow artifacts.

* @param string $caseId The case UUID
* @return JSONResponse
*/
public function getDeadlines(string $caseId): JSONResponse
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[unfixed: read-only workspace; requires adding @NoAdminRequired docblock annotation] Rule: OWASP A01:2021 — Broken Access Control. Without @NoAdminRequired, Nextcloud's middleware restricts this endpoint to admin-only, blocking regular users from querying their own case deadline status. The method already performs proper per-user authorization (owner/group/admin check via IGroupManager). Fix: add @NoAdminRequired to the method docblock above getDeadlines().

*
* @return JSONResponse
*/
public function notifyWebhook(): JSONResponse
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[unfixed: read-only workspace; requires adding @publicpage, @NoCSRFRequired, @NoAdminRequired docblock annotations] Rule: OWASP A05:2021 — Security Misconfiguration / Nextcloud annotation contract. This endpoint is designed to receive POST callbacks from n8n (an external service with no Nextcloud session). Without @PublicPage, Nextcloud's auth middleware rejects all unauthenticated requests before they reach this method. Without @NoCSRFRequired, Nextcloud's CSRF middleware blocks POST requests from n8n (which cannot supply a CSRF token). The existing header-based auth check inside the method body is therefore never reached. Fix: add @NoAdminRequired, @NoCSRFRequired, and @PublicPage to this method's docblock, matching the pattern in NrcController and ZtcController. Note: also fix the presence-only auth check (see separate comment).

try {
// Verify webhook authentication
// Check for Authorization header with Bearer token or webhook secret
$authHeader = $this->request->getHeader('Authorization');
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[unfixed: requires architectural fix — implement shared secret validation] Rule: OWASP A07:2021 — Identification and Authentication Failures / CWE-287. The webhook auth check only verifies that the Authorization or X-Webhook-Secret header is non-empty — it does NOT compare the value against a configured shared secret. Any caller who supplies Authorization: x or X-Webhook-Secret: x bypasses authentication. Fix: read the configured secret from SettingsService (e.g. getConfigValue('n8n_signalering_webhook_secret')), then compare using hash_equals($configuredSecret, $incomingSecret) to prevent timing attacks. Return 401 if values do not match.

];

$client = $this->clientService->newClient();
$response = $client->post($n8nWebhookUrl, [
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[unfixed: requires URL validation logic — admin-accessible SSRF vector] Rule: OWASP A10:2021 — Server-Side Request Forgery (SSRF) / CWE-918. The n8n webhook URL is read directly from app settings and passed without validation to IClientService::post(). An admin who controls app settings could set this to an internal network address (e.g. http://169.254.169.254/, http://localhost:6379/) to probe internal infrastructure. Fix: validate with filter_var($n8nWebhookUrl, FILTER_VALIDATE_URL) and restrict to https:// scheme before calling $client->post(). Example: if (str_starts_with($n8nWebhookUrl, 'https://') === false) { return false; }.

@rubenvdlinde
Copy link
Copy Markdown
Contributor Author

Security Review — Clyde Barcode

Result: FAIL (0 fixed, 4 unfixed — 2 WARNING, 2 WARNING)

Note: The workspace is root-owned and not writable by the review agent. All findings that would normally qualify for in-container fixes are documented as [unfixed] with precise fix instructions. The Applier (Axel Pliér) should apply the annotation fixes in the next cycle.


Findings summary

# Severity File Line Rule Status
1 WARNING lib/Controller/DeadlineNotificationController.php 50 OWASP A01:2021 [unfixed: missing @NoAdminRequired]
2 WARNING lib/Controller/DeadlineNotificationController.php 153 OWASP A05:2021 [unfixed: missing @PublicPage, @NoCSRFRequired, @NoAdminRequired]
3 WARNING lib/Controller/DeadlineNotificationController.php 158 OWASP A07:2021 / CWE-287 [unfixed: presence-only webhook auth — needs hash_equals() secret check]
4 WARNING lib/Service/SignaleringNotificationService.php 115 OWASP A10:2021 / CWE-918 [unfixed: admin-accessible SSRF — n8n URL not validated for scheme]

False positives suppressed

  • [FALSE POSITIVE] gitleaks jwt — 303 findings all in newman/ test-run JSON artifacts; 0 findings in changed files.

Checks run

  • composer audit ✅ — no packages to audit (clean)
  • npm audit --production ✅ — 15 vulns (1 high, 4 moderate, 10 low) all in existing Vue chain, pre-run passed; drift noted — pre-run marked clean but I observe high-severity vuln in Vue chain; pre-existing, not introduced by this PR
  • gitleaks detect --no-git ✅ — 0 findings in changed files (303 all in newman artifacts)
  • semgrep scan --config=p/security-audit --config=p/owasp-top-ten --metrics=off ✅ — 0 findings
  • Manual OWASP diff review ✅ — 4 findings documented above

Required actions before merge

  1. getDeadlines() L50 — add @NoAdminRequired to docblock (1 line)
  2. notifyWebhook() L153 — add @NoAdminRequired, @NoCSRFRequired, @PublicPage to docblock (3 lines, matches NrcController/ZtcController pattern)
  3. notifyWebhook() L158 — replace presence-only header check with hash_equals($configuredSecret, $incomingValue) using a SettingsService key
  4. SignaleringNotificationService::notifyByEmail() L115 — guard with str_starts_with($n8nWebhookUrl, 'https://') before $client->post()

See inline comments for per-finding detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant