From b0e3933a9f42b4e2b0e12475e7eccef23bcfeee0 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Sat, 24 Oct 2026 21:30:47 -0700 Subject: [PATCH] case 4727 - report template export only to users who may use or edit it report/export/{id} by download key gives a template only to a user whose roles include one of the template's roles, or who may edit templates (a user with no roles got any template). Job owner check accepted (no owner recorded; random job ids). F22 (4727 step 4). Co-Authored-By: Claude Opus 5.5 --- devdocs/modernization/NET10-PORT.md | 3 ++- devdocs/modernization/SECURITY-REVIEW.md | 3 ++- server/AyaNova/Controllers/ReportController.cs | 8 +++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index d5ff33c5..860c0540 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -193,7 +193,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s | 4724 | SMTP: certificate checked, password never returned | F30, F31. **Done** (certificate checked, opt-out config setting `AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE` with manual page; password masked as `********` and kept on save; test `SmtpPasswordHidden`; self-signed listener proof; changelog lines for both) | | 4725 | Sessions: password change ends sessions, exact-path token exemption, default SuperUser refused when licensed | F7, F8, F9. **Done** (sessions end on reset, administrator password change and login disabled; exact route exemption, inactive users refused; default-password SuperUser limited to changing it on a purchased licence, option A; test `Sessions`; John's click-through with a temporary licence override; manual and changelog) | | 4726 | Login throttling/lockout (raven and sockeye) | F6. **Done** (10 failures per login or 50 per address in 15 minutes, then 429 for the rest of the window; same in Sockeye, whose login is public; test `LoginLockout`; per-address limit proved with a forwarded documentation address) | -| 4727 | Low severity hardening | F10, F11, F12, F22, F23, F25, F29, F35, F36, F38; the AyaNova half of F39 (DB ID not shown anonymously). In steps: 1 logo (F23) **done**; 2 API root page (F11, F39) **done**; 3 `server-info` (F29) **done**; 4 job owner and template export (F22); 5 `OverrideUserId` and export download (F25); 6 safe headers (F35); 7 remove `dist/docker` (F38) | +| 4727 | Low severity hardening | F10, F11, F12, F22, F23, F25, F29, F35, F36, F38; the AyaNova half of F39 (DB ID not shown anonymously). In steps: 1 logo (F23) **done**; 2 API root page (F11, F39) **done**; 3 `server-info` (F29) **done**; 4 template export (F22) **done**, job owner check accepted; 5 `OverrideUserId` and export download (F25); 6 safe headers (F35); 7 remove `dist/docker` (F38) | | 4681 | Sockeye: licence/trial request hardening (tagged sockeye, rockfish) | F40. **Done** (sockeye `cd3ebed`: rate limits, email check, cryptographic codes; rvf unchanged) | **From the port and earlier triage** @@ -251,6 +251,7 @@ Found during the port, out of scope. One line each. - `NotifyController.SendNotifySmtpDirectMessage`, User branch: the missing-address and inactive messages say "Vendor" instead of "User" (found in case 4723) - SMTP connection security mapping (`Mailer.cs`): "StartTls" connects with `useSsl: true`, which MailKit treats as TLS from the start (port 465), not STARTTLS (587); "None" uses STARTTLS when offered, which can be stripped. Correcting it could break customers using "StartTls" on port 465 (found in case 4724) +- Integration test `DownloadNames` logs in again as the shared `Sales` user to get a download key, which replaces the token the other tests use for Sales while running in parallel (one session per user). Tests that need a download key should create their own user, as `TemplateExportRights` does (found in case 4727) - From F6 (case 4726), split off as separate changes: a password strength rule (not empty is the only rule today; seeded trial users use their login as password) is a new, visible feature; `limit_req` on `/api/v8/auth` in the hosted nginx configuration belongs in the `sockeye-client` setup script and affects every hosted droplet. Both are second layers behind the login lockout - Race in the notification system: a record saved while a matching notification subscription is being deleted fails with 500 (`anotifyevent_notifysubscriptionid_fkey` violation), and the user's save fails with it. Seen once in the integration suite (`CustomerCrud.CRUD`, run in parallel with subscription tests) during case 4724 - **Case 4731 (John, priority 1, after 8.3.0):** a global notification setting "allow email to any address from the API and report scripts" for `notify/direct-smtp`; off for new installs, on for upgraded databases; when off, only a record's saved address. Follows from F3 / case 4723 diff --git a/devdocs/modernization/SECURITY-REVIEW.md b/devdocs/modernization/SECURITY-REVIEW.md index 80b8dc77..b2428c53 100644 --- a/devdocs/modernization/SECURITY-REVIEW.md +++ b/devdocs/modernization/SECURITY-REVIEW.md @@ -231,7 +231,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes | F38 | Low | confirmed (code) | Linux hardening: no systemd sandboxing, service user can replace program files, desktop runs as root, unused Docker files with a fixed database password | | F39 | Low | confirmed (code) | Licence fetch is one-shot by DB ID, and the DB ID is shown anonymously while unlicensed: someone else can take a new key first | | F40 | Low | **fixed in case 4681** (Sockeye) | Trial request routes: no rate limit, non-cryptographic verification codes, a fixed bot-check value | -| F22 | Low | confirmed | Minor report and job route gaps: template export by any download key, job status, logs and cancel by GUID without an owner check, browser without sandbox | +| F22 | Low | **fixed in case 4727** (job owner check and Chrome sandbox accepted) | Minor report and job route gaps: template export by any download key, job status, logs and cancel by GUID without an owner check, browser without sandbox | **Release decisions.** John, 2026-10-23: **F13 to F17 are urgent and are fixed in 8.3.0**, as "next release" cases (case numbers to come). Hosted and self-hosted perpetual installations @@ -512,6 +512,7 @@ browser bug reached through a template is not contained; Linux runs as `www-data as the logged-in user. Fix: owner check on job routes; consider enabling the sandbox where the platform allows. **John, 2026-10-24:** owner check and template export in case 4727; Chrome's sandbox **accepted as off** (it often fails on Linux servers and can't run as root). The systemd options in F38 would contain the browser as a child of the service on hosted droplets. +**Case 4727, step 4 (2026-10-24):** `report/export/{id}` gives a template only to a download-key user whose (effective) roles include one of the template's Roles, or who may edit templates; test `TemplateExportRights` (a user with no roles got any template on the old code). Job owner check **accepted, John 2026-10-24**: jobs record no owner, so it needs a schema change and changes at the 13 places that create jobs; job ids are random GUIDs and the routes return status and log only. A possible later case. ### F23 Logo upload: any content type, served inline (pass 5, from code) diff --git a/server/AyaNova/Controllers/ReportController.cs b/server/AyaNova/Controllers/ReportController.cs index 849d9b2d..7c12fada 100644 --- a/server/AyaNova/Controllers/ReportController.cs +++ b/server/AyaNova/Controllers/ReportController.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; +using EnumsNET; using AyaNova.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -288,7 +289,8 @@ namespace AyaNova.Api.Controllers if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); - if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) + var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct); + if (DownloadUser == null) { await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); @@ -300,6 +302,10 @@ namespace AyaNova.Api.Controllers { return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); } + + //case 4727 (F22): only a template the user may use (its Roles) or may edit + if (!DownloadUser.Roles.HasAnyFlags(o.Roles) && !Authorized.HasModifyRole(DownloadUser.Roles, AyaType.Report)) + return StatusCode(403, new ApiNotAuthorizedResponse()); var asText = Newtonsoft.Json.JsonConvert.SerializeObject( o, Newtonsoft.Json.Formatting.None,