diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index b2459587..c616e77c 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -214,7 +214,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s | 4700 | Restore note for customers with a dedicated database role | 1, docs | | 4703 | Report JS console error logging | 1 | | 4705 | License.DoFetchAsync never tries the fifth licence server | 1. **Done** (rethrow after the last server, `x == 4`; the trial request (`rvr`, rockfish only) now uses the same fallback loop, John's decision. Sandbox with rockfish, io, europa and callisto pointed at 127.0.0.1: the old build failed the trial request with a connection error, the new build reached sockeye.ayanova.com. No changelog: only noticeable if the licence server names fail) | -| 4706 | Remove the unused RunProgram.Run | 1 | +| 4706 | Remove the unused RunProgram.Run | 1. **Done** (`Run` and its `RunWindows`/`RunLinuxShell`/`RunOSX` removed, no callers; `RunNoShell` stays. Backup through the API wrote the database dump and attachments zip; full suite = baseline) | | 4707 | Stray cypress.json in raven-client | 1, investigate | | 4687 | Generator polls every second (raven and sockeye) | 1 | | 4682 | Time-only picker keeps the old date's DST offset | 1 | @@ -270,7 +270,7 @@ Found during the port, out of scope. One line each. - Sockeye 8.1.0 has the NLog 6 archive naming problem fixed here for AyaNova: archives are `log-sockeye_NN.txt`, old `-0` to `-8` archives are never deleted, and its log viewer still sorts by name (it also filters on `ayanova.txt`, not `sockeye.txt`, so the current file is listed twice). Same fix applies: `ArchiveSuffixFormat = "-{0}"` and the date sort - **Security review: customer contact isolation (John, 2026-10-23: urgent, fixed with the "next release" cases before 8.3.0; case numbers to come).** Findings F13 to F17 in `devdocs/modernization/SECURITY-REVIEW.md`, confirmed on the local server as a seeded customer contact: any customer's work order by id (and, by code, its customer report and attachments); other customers' service requests; the full customer list with phones and emails through pick lists; the whole service schedule; notification subscriptions created for another user. Hosted and self-hosted perpetual installations are equally important. Fix direction in the review: block customer-type users by default except the routes the customer UI uses, then add a "belongs to my customer" check to each allowed route that takes an id. F5 (2FA without password), F1 (placeholder JWT secret) and F18 (pick-list search text in SQL) are fixed in 8.3.0 too (John, 2026-10-23). **8.3.0 is not released until every security review finding is fixed or explicitly accepted by John** ("all holes must be plugged"); the review's remaining passes therefore come before the release. Front-end exception (John, 2026-10-23): DOMPurify to 2.5.x and Vuetify to the latest 2.x (2.7.2, or 2.6.16 if 2.7 does not build on Node 12) for security finding F34, each proven by a full client build and Cypress - **Fixed in case 4692.** **SuperUser can be deleted (serious, pre-existing, own case; John's decision 2026-10-23: fixed with the other "next release" cases before 8.3.0 ships, not under the port):** `UserBiz.ValidateCanDelete` (`biz/UserBiz.cs:1055`) only refuses a delete when the user has related records; nothing protects user id 1, although updates to it are blocked (`UserBiz.cs:946`). On the droplet, whose sample data left SuperUser with no related records, the integration test `UserWithActivityShouldNotBeDeleteable` (`DELETE User/1`) succeeded and removed the account; `AYANOVA_SET_SUPERUSER_PW` cannot bring it back (it only resets an existing user 1). Same code in 8.2.4; likely also why `superuser` could not log in on the reused local database at the first baseline attempt. Any user with delete rights on Users can do this -- `RunProgram.Run` (and its `RunWindows`/`RunLinuxShell`/`RunOSX` helpers) has no callers since the backup moved to `RunNoShell`. It runs through `cmd.exe /C` or `/bin/sh -c` and logs its full command line on error: delete it rather than reuse it +- **Removed in case 4706.** `RunProgram.Run` (and its `RunWindows`/`RunLinuxShell`/`RunOSX` helpers) has no callers since the backup moved to `RunNoShell`. It runs through `cmd.exe /C` or `/bin/sh -c` and logs its full command line on error: delete it rather than reuse it - **Own case after the port (John): automated provisioning from Sockeye.** Goal: from a trial subscription server request, one menu item in Sockeye (after John's manual approval) provisions the whole server through the DigitalOcean API, ready for login: droplet, DNS, firewall, setup, licence. Replaces today's manual steps (header comments of the setup script), which are slow and error-prone. Not part of this port - Linux desktop manual (`ops-install-linux-desktop.md`), pre-existing, same in 8.2.4: `sudo mkdir ~/.ayanova-data` creates the folder in the user's home, but `sudo ./AyaNova` runs as root, so `AYANOVA_DATA_PATH` `~/.ayanova-data` resolves to `/root/.ayanova-data` and the user's folder is never used (data, logs and backups end up under `/root`). Found in the 8.3.0 desktop zip test. Fix the manual (absolute data path, or not running as root) after the release - **Own case after the port (John):** the subscription server setup script is hard-coded in `biz-subscription-server.vue`, so every change needs a new Sockeye client build. Make it editable without a rebuild diff --git a/server/AyaNova/util/RunProgram.cs b/server/AyaNova/util/RunProgram.cs index 63057d4e..e6f16aea 100644 --- a/server/AyaNova/util/RunProgram.cs +++ b/server/AyaNova/util/RunProgram.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; namespace AyaNova.Util @@ -11,42 +10,6 @@ namespace AyaNova.Util { - public static string Run(string cmd, string arguments, ILogger log = null, int waitForExitTimeOut = int.MaxValue) - { - try - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return RunWindows(cmd, arguments, waitForExitTimeOut); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return RunOSX(cmd, arguments, waitForExitTimeOut); - - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return RunLinuxShell(cmd, arguments, waitForExitTimeOut); - } - } - catch (Exception ex) - { - if (log != null) - { - log.LogError(ex, $"RunProgram error running command:{cmd} {arguments}"); - } - throw; - } - - - - - throw new PlatformNotSupportedException(); - - } - - - /// /// Run a program directly, no shell: each argument is passed as-is (no quoting or escaping needed, nothing is interpreted by a shell) /// and extra environment variables are set for this process only (e.g. PGPASSWORD so a password never appears on a command line) @@ -98,87 +61,5 @@ namespace AyaNova.Util } } - - - private static string RunWindows(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) - { - //RunProgram.Run("cmd.exe",FullRunCommand, log); - // var FullRunCommand=$"/C {BackupUtilityCommand} {Arguments}"; - - //for Windows need to pass to cmd.exe because often have command line piping etc - var args = $"/C {cmd} {arguments}"; - using (var process = new Process()) - { - process.StartInfo = new ProcessStartInfo - { - FileName = "cmd.exe", - Arguments = args, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - - process.Start(); - - string result = $"{process.StandardOutput.ReadToEnd()}{process.StandardError.ReadToEnd()} "; - if (!process.WaitForExit(waitForExitTimeOut)) - { - result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; - } - return result; - } - } - - private static string RunLinuxShell(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) - { - var escapedArgs = $"{cmd} {arguments}".Replace("\"", "\\\""); - - using (var process = new Process()) - { - process.StartInfo = new ProcessStartInfo - { - FileName = "/bin/sh", - Arguments = $"-c \"{escapedArgs}\"", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - process.Start(); - string result = process.StandardOutput.ReadToEnd(); - if (!process.WaitForExit(waitForExitTimeOut)) - { - result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; - } - return result; - } - } - - - private static string RunOSX(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) - { - using (var process = new Process()) - { - process.StartInfo = new ProcessStartInfo - { - FileName = cmd, - Arguments = arguments, - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - process.Start(); - string result = process.StandardOutput.ReadToEnd(); - if (!process.WaitForExit(waitForExitTimeOut)) - { - result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; - } - return result; - } - } - } } \ No newline at end of file