From 351eb2d851cdbf4d19258522ebd7aec8097f676b Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Sat, 24 Oct 2026 14:25:23 -0700 Subject: [PATCH] case 4712 - service schedule needs work order, quote or PM rights Step 5 of 4712 (security review F16, staff side; contacts are already refused by step 1). schedule/svc and schedule/user had no role check, so any login got the whole service schedule (work order numbers, customer names, technicians). They now need read rights on work orders, quotes or PMs, the test the client uses to show the Schedule menu, so no role that sees the schedule loses it; only Inventory and OpsAdmin roles (no menu) are refused. Co-Authored-By: Claude Opus 5.5 --- devdocs/modernization/NET10-PORT.md | 2 +- devdocs/modernization/SECURITY-REVIEW.md | 2 +- server/AyaNova/Controllers/ScheduleController.cs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index f1d7117d..7f850e96 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -179,7 +179,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s |---|---|---| | 4710 | 2FA step requires the temp token (raven and sockeye) | F5, F41. **Done**: raven `141bdeb8` (proof script 401 on both bypasses; integration test `TwoFactor` in `8e5eec4`); sockeye `f7c1d7c` (same checks on local Sockeye with a temporary user). Sockeye to be deployed to its new droplet by John; 2FA can be turned back on once each server runs the fix | | 4711 | JWT secret setting removed: the server generates its signing key once into `jwt-signing.key` in the data folder (4698 folded in) | F1. **Done** (placeholder-forged token 401; key survives restart; auth tests pass). Hosted setup script's secret step now does nothing; tidy later in `sockeye-client` | -| 4712 | Customer contact route allowlist + ownership checks | F13, F14, F15, F16, F17. In 6 steps: 1 allowlist **done**; 2 work-order ownership **done**; 3 CSR and unit ownership **done**; 4 pick lists for contacts **done**; 5 schedule staff rights; 6 notification subscriptions | +| 4712 | Customer contact route allowlist + ownership checks | F13, F14, F15, F16, F17. In 6 steps: 1 allowlist **done**; 2 work-order ownership **done**; 3 CSR and unit ownership **done**; 4 pick lists for contacts **done**; 5 schedule staff rights **done**; 6 notification subscriptions | | 4713 | Contact users limited to customer roles | F28 | | 4715 | Download file names checked (one helper) | F21. **Done** (`FileUtil.FullPathInFolderOrNull` in temp/backup exists checks and both log routes; test `DownloadNames`) | | 4716 | Pick-list search text as SQL parameters | F18, and F43 (unit pick-list variant parsed as a number, found during 4712, test `FetchUnitPickListCustomerVariant`). **Done** (`@plq`/`@pltag` parameters; test `FetchUserPickListAutoCompleteApostrophe` fails on the old code, passes now; 9/9 pick-list tests) | diff --git a/devdocs/modernization/SECURITY-REVIEW.md b/devdocs/modernization/SECURITY-REVIEW.md index 7138eaca..3a34200d 100644 --- a/devdocs/modernization/SECURITY-REVIEW.md +++ b/devdocs/modernization/SECURITY-REVIEW.md @@ -201,7 +201,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes | F13 | High | **fixed in case 4712** | Customer contacts can open any customer's work order (customer view, customer report, attachments) by id | | F14 | High | **fixed in case 4712** | Customer contacts can read, and by code change, delete and create, any customer's service requests | | F15 | High | **fixed in case 4712** | Customer contacts can list every customer (and vendors, users, parts...) with any pick-list columns, phones and emails included | -| F16 | High | **confirmed** | `schedule/svc` and `schedule/user` have no role check: anyone logged in sees the whole service schedule | +| F16 | High | **fixed in case 4712** | `schedule/svc` and `schedule/user` have no role check: anyone logged in sees the whole service schedule | | F17 | Medium | confirmed (code) | Notification subscriptions accept any `UserId`, and object events reach subscribers without a rights check: record names to any email address | | F20 | High | candidate | Report templates receive the renderer's token (SuperUser for customer notification reports) and the report browser can reach any network address | | F27 | High | **confirmed** | Restricted roles (TechRestricted, SubContractor, SubContractorRestricted) get whole work-order items through report data, rendering and export | diff --git a/server/AyaNova/Controllers/ScheduleController.cs b/server/AyaNova/Controllers/ScheduleController.cs index 69e81ac2..986ac689 100644 --- a/server/AyaNova/Controllers/ScheduleController.cs +++ b/server/AyaNova/Controllers/ScheduleController.cs @@ -60,6 +60,8 @@ namespace AyaNova.Api.Controllers { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!CanSeeServiceSchedule()) + return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); @@ -152,6 +154,8 @@ namespace AyaNova.Api.Controllers { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!CanSeeServiceSchedule()) + return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); @@ -460,6 +464,15 @@ namespace AyaNova.Api.Controllers return v.Serial.ToString() + " " + v.CustomerName; } + //case 4712: the service schedule (and its per user drill down) needs the same rights the client + //uses to show the Schedule menu: open work orders, quotes or PMs + private bool CanSeeServiceSchedule() + { + return Authorized.HasReadFullRole(HttpContext.Items, AyaType.WorkOrder) + || Authorized.HasReadFullRole(HttpContext.Items, AyaType.Quote) + || Authorized.HasReadFullRole(HttpContext.Items, AyaType.PM); + } + public enum ScheduleWorkOrderColorSource : int {