diff --git a/README.md b/README.md index aba3bfc..a797352 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,9 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365. ## Konfiguracja -Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje. +Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje. -Sekrety nie są przechowywane w JSON-ie. Konto usługi musi widzieć dwie zmienne środowiskowe maszyny: - -```powershell -[Environment]::SetEnvironmentVariable('SMARTB2B_ENOVA_PASSWORD', '', 'Machine') -[Environment]::SetEnvironmentVariable('SMARTB2B_SQL_CONNECTION_STRING', 'Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True', 'Machine') -``` - -Po zmianie zmiennych trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą. +Uzupełnij pola `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą. ## Budowanie i testy @@ -50,7 +43,7 @@ Kontrola konfiguracji i dynamicznego ładowania, bez zapisu do bazy: Skrypt odtwarza katalog `release` z hostem, adapterem, wzorcem konfiguracji i WinSW. Repozytorium zawiera również gotowy, skompilowany katalog `release`, więc na komputerze produkcyjnym nie trzeba instalować SDK. Przed instalacją: 1. Skopiuj katalog `release` na komputer produkcyjny. Możesz skopiować `release/config/enova.json.sample` do `release/config/enova.json` i uzupełnić go przed instalacją; jeżeli tego nie zrobisz, `install.cmd` utworzy plik automatycznie. -2. Ustaw sekrety maszyny. +2. Uzupełnij w `release/config/enova.json` hasło operatora i connection string SQL. 3. Zatrzymaj starą usługę Node dla tego samego portalu WAMP. 4. Skonfiguruj usługę do pracy pod dedykowanym kontem Windows, które potrafi uruchomić Enovę, widzi jej bazę i licencję. 5. Jako administrator uruchom `release/daemon/install.cmd`. diff --git a/release/SmartB2B.Enova.Contracts.dll b/release/SmartB2B.Enova.Contracts.dll index c680eb4..47b969b 100644 Binary files a/release/SmartB2B.Enova.Contracts.dll and b/release/SmartB2B.Enova.Contracts.dll differ diff --git a/release/SmartB2B.Enova.Contracts.pdb b/release/SmartB2B.Enova.Contracts.pdb index 4fbbca9..1a3ce34 100644 Binary files a/release/SmartB2B.Enova.Contracts.pdb and b/release/SmartB2B.Enova.Contracts.pdb differ diff --git a/release/SmartB2B.Enova.Service.dll b/release/SmartB2B.Enova.Service.dll index 2ba3fdf..132d6d3 100644 Binary files a/release/SmartB2B.Enova.Service.dll and b/release/SmartB2B.Enova.Service.dll differ diff --git a/release/SmartB2B.Enova.Service.exe b/release/SmartB2B.Enova.Service.exe index fa0cab7..ceb7c37 100644 Binary files a/release/SmartB2B.Enova.Service.exe and b/release/SmartB2B.Enova.Service.exe differ diff --git a/release/SmartB2B.Enova.Service.pdb b/release/SmartB2B.Enova.Service.pdb index 7720eed..c5b9055 100644 Binary files a/release/SmartB2B.Enova.Service.pdb and b/release/SmartB2B.Enova.Service.pdb differ diff --git a/release/adapters/SmartB2B.Enova.Adapter.dll b/release/adapters/SmartB2B.Enova.Adapter.dll index 8991425..8eaf0d9 100644 Binary files a/release/adapters/SmartB2B.Enova.Adapter.dll and b/release/adapters/SmartB2B.Enova.Adapter.dll differ diff --git a/release/config/enova.json.sample b/release/config/enova.json.sample index c21bbcc..0754aa2 100644 --- a/release/config/enova.json.sample +++ b/release/config/enova.json.sample @@ -8,13 +8,13 @@ "installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4", "database": "Firma demo", "operator": "Administrator", - "passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD", + "password": "", "documentDefinition": "ZO", "defaultWarehouseCode": null, "saveMode": "Buffer" }, "sql": { - "connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING", + "connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True", "commandTimeoutSeconds": 30 }, "diagnostics": { diff --git a/src/SmartB2B.Enova.Service/Configuration/ServiceSettings.cs b/src/SmartB2B.Enova.Service/Configuration/ServiceSettings.cs index 5b52f24..d0a8b32 100644 --- a/src/SmartB2B.Enova.Service/Configuration/ServiceSettings.cs +++ b/src/SmartB2B.Enova.Service/Configuration/ServiceSettings.cs @@ -48,19 +48,8 @@ public sealed class ServiceSettings public RuntimeSettings ResolveRuntimeSettings() { - var enovaPassword = Environment.GetEnvironmentVariable(Enova.PasswordEnvironmentVariable); - if (enovaPassword is null) - { - throw new ConfigurationException( - $"Brak zmiennej środowiskowej {Enova.PasswordEnvironmentVariable} z hasłem operatora Enovy."); - } - - var connectionString = Environment.GetEnvironmentVariable(Sql.ConnectionStringEnvironmentVariable); - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ConfigurationException( - $"Brak zmiennej środowiskowej {Sql.ConnectionStringEnvironmentVariable} z connection stringiem SQL."); - } + var enovaPassword = Enova.Password + ?? throw new ConfigurationException("Parametr 'enova.password' jest wymagany."); var installationPath = Path.GetFullPath( Environment.ExpandEnvironmentVariables(Enova.InstallationPath)); @@ -72,7 +61,7 @@ public sealed class ServiceSettings Enova.DocumentDefinition, Enova.DefaultWarehouseCode, Enova.SaveMode), - connectionString, + Sql.ConnectionString, Sql.CommandTimeoutSeconds, ResolvePath(Diagnostics.LogDirectory), Diagnostics.LogPrefix); @@ -90,9 +79,9 @@ public sealed class ServiceSettings Require(Enova.InstallationPath, "enova.installationPath", errors); Require(Enova.Database, "enova.database", errors); Require(Enova.Operator, "enova.operator", errors); - Require(Enova.PasswordEnvironmentVariable, "enova.passwordEnvironmentVariable", errors); + RequirePresent(Enova.Password, "enova.password", errors); Require(Enova.DocumentDefinition, "enova.documentDefinition", errors); - Require(Sql.ConnectionStringEnvironmentVariable, "sql.connectionStringEnvironmentVariable", errors); + Require(Sql.ConnectionString, "sql.connectionString", errors); if (Sql.CommandTimeoutSeconds <= 0) { errors.Add("sql.commandTimeoutSeconds musi być większe od zera."); @@ -112,6 +101,14 @@ public sealed class ServiceSettings } } + private static void RequirePresent(string? value, string name, ICollection errors) + { + if (value is null) + { + errors.Add($"Parametr '{name}' jest wymagany."); + } + } + private static string ResolvePath(string path) => Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(path, AppContext.BaseDirectory); } @@ -131,7 +128,7 @@ public sealed class EnovaSettings public string Operator { get; init; } = "Administrator"; - public string PasswordEnvironmentVariable { get; init; } = "SMARTB2B_ENOVA_PASSWORD"; + public string? Password { get; init; } public string DocumentDefinition { get; init; } = "ZO"; @@ -142,7 +139,7 @@ public sealed class EnovaSettings public sealed class SqlSettings { - public string ConnectionStringEnvironmentVariable { get; init; } = "SMARTB2B_SQL_CONNECTION_STRING"; + public string ConnectionString { get; init; } = string.Empty; public int CommandTimeoutSeconds { get; init; } = 30; } diff --git a/src/SmartB2B.Enova.Service/config/enova.json.sample b/src/SmartB2B.Enova.Service/config/enova.json.sample index c21bbcc..0754aa2 100644 --- a/src/SmartB2B.Enova.Service/config/enova.json.sample +++ b/src/SmartB2B.Enova.Service/config/enova.json.sample @@ -8,13 +8,13 @@ "installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4", "database": "Firma demo", "operator": "Administrator", - "passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD", + "password": "", "documentDefinition": "ZO", "defaultWarehouseCode": null, "saveMode": "Buffer" }, "sql": { - "connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING", + "connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True", "commandTimeoutSeconds": 30 }, "diagnostics": { diff --git a/tests/SmartB2B.Enova.Tests/Program.cs b/tests/SmartB2B.Enova.Tests/Program.cs index 3c6342b..93b664d 100644 --- a/tests/SmartB2B.Enova.Tests/Program.cs +++ b/tests/SmartB2B.Enova.Tests/Program.cs @@ -1,4 +1,5 @@ using SmartB2B.Enova.Contracts; +using SmartB2B.Enova.Service.Configuration; using SmartB2B.Enova.Service.Orders; using SmartB2B.Enova.Service.Rpc; using SmartB2B.Enova.Service.Runtime; @@ -23,7 +24,8 @@ var tests = new (string Name, Func Run)[] ("Warstwa WAMP wysyła wynik jako kwargs", WampKeywordWireResult), ("Brak waluty zwraca stabilny błąd", MissingCurrency), ("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency), - ("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory) + ("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory), + ("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile) }; var failures = 0; @@ -179,6 +181,52 @@ static Task MissingEnovaDirectory() } } +static Task SecretsComeFromConfigurationFile() +{ + var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json"); + try + { + File.WriteAllText(path, """ + { + "portal": "demo", + "wamp": { + "serverUrl": "ws://localhost:8080/", + "reconnectDelaySeconds": 10 + }, + "enova": { + "installationPath": "C:/Enova", + "database": "Firma demo", + "operator": "Administrator", + "password": "haslo-z-pliku", + "documentDefinition": "ZO", + "saveMode": "Buffer" + }, + "sql": { + "connectionString": "Server=db;Database=enova;User ID=user;Password=sql-z-pliku", + "commandTimeoutSeconds": 30 + }, + "diagnostics": { + "logDirectory": "C:/logs", + "logPrefix": "test" + } + } + """); + + var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings(); + + AssertEqual("haslo-z-pliku", runtime.EnovaConfiguration.Connection.Password, "hasło operatora"); + AssertEqual( + "Server=db;Database=enova;User ID=user;Password=sql-z-pliku", + runtime.SqlConnectionString, + "connection string SQL"); + return Task.CompletedTask; + } + finally + { + File.Delete(path); + } +} + static RpcInvocation CreateInvocation() => new( [], new Dictionary