Read Enova credentials from configuration file

This commit is contained in:
2026-08-26 01:13:45 +00:00
parent 0eded90e6b
commit f15f46945d
11 changed files with 71 additions and 33 deletions

View File

@@ -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', '<hasło operatora>', '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`.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,13 +8,13 @@
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
"database": "Firma demo",
"operator": "Administrator",
"passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD",
"password": "<hasło operatora>",
"documentDefinition": "ZO",
"defaultWarehouseCode": null,
"saveMode": "Buffer"
},
"sql": {
"connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING",
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
"commandTimeoutSeconds": 30
},
"diagnostics": {

View File

@@ -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<string> 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;
}

View File

@@ -8,13 +8,13 @@
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
"database": "Firma demo",
"operator": "Administrator",
"passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD",
"password": "<hasło operatora>",
"documentDefinition": "ZO",
"defaultWarehouseCode": null,
"saveMode": "Buffer"
},
"sql": {
"connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING",
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
"commandTimeoutSeconds": 30
},
"diagnostics": {

View File

@@ -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<Task> 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<string, object?>