Read Enova credentials from configuration file
This commit is contained in:
13
README.md
13
README.md
@@ -12,16 +12,9 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365.
|
|||||||
|
|
||||||
## Konfiguracja
|
## 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:
|
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ą.
|
||||||
|
|
||||||
```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ą.
|
|
||||||
|
|
||||||
## Budowanie i testy
|
## 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ą:
|
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.
|
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.
|
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ę.
|
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`.
|
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.
Binary file not shown.
@@ -8,13 +8,13 @@
|
|||||||
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
|
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
|
||||||
"database": "Firma demo",
|
"database": "Firma demo",
|
||||||
"operator": "Administrator",
|
"operator": "Administrator",
|
||||||
"passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD",
|
"password": "<hasło operatora>",
|
||||||
"documentDefinition": "ZO",
|
"documentDefinition": "ZO",
|
||||||
"defaultWarehouseCode": null,
|
"defaultWarehouseCode": null,
|
||||||
"saveMode": "Buffer"
|
"saveMode": "Buffer"
|
||||||
},
|
},
|
||||||
"sql": {
|
"sql": {
|
||||||
"connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING",
|
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
||||||
"commandTimeoutSeconds": 30
|
"commandTimeoutSeconds": 30
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
|
|||||||
@@ -48,19 +48,8 @@ public sealed class ServiceSettings
|
|||||||
|
|
||||||
public RuntimeSettings ResolveRuntimeSettings()
|
public RuntimeSettings ResolveRuntimeSettings()
|
||||||
{
|
{
|
||||||
var enovaPassword = Environment.GetEnvironmentVariable(Enova.PasswordEnvironmentVariable);
|
var enovaPassword = Enova.Password
|
||||||
if (enovaPassword is null)
|
?? throw new ConfigurationException("Parametr 'enova.password' jest wymagany.");
|
||||||
{
|
|
||||||
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 installationPath = Path.GetFullPath(
|
var installationPath = Path.GetFullPath(
|
||||||
Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
|
Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
|
||||||
@@ -72,7 +61,7 @@ public sealed class ServiceSettings
|
|||||||
Enova.DocumentDefinition,
|
Enova.DocumentDefinition,
|
||||||
Enova.DefaultWarehouseCode,
|
Enova.DefaultWarehouseCode,
|
||||||
Enova.SaveMode),
|
Enova.SaveMode),
|
||||||
connectionString,
|
Sql.ConnectionString,
|
||||||
Sql.CommandTimeoutSeconds,
|
Sql.CommandTimeoutSeconds,
|
||||||
ResolvePath(Diagnostics.LogDirectory),
|
ResolvePath(Diagnostics.LogDirectory),
|
||||||
Diagnostics.LogPrefix);
|
Diagnostics.LogPrefix);
|
||||||
@@ -90,9 +79,9 @@ public sealed class ServiceSettings
|
|||||||
Require(Enova.InstallationPath, "enova.installationPath", errors);
|
Require(Enova.InstallationPath, "enova.installationPath", errors);
|
||||||
Require(Enova.Database, "enova.database", errors);
|
Require(Enova.Database, "enova.database", errors);
|
||||||
Require(Enova.Operator, "enova.operator", 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(Enova.DocumentDefinition, "enova.documentDefinition", errors);
|
||||||
Require(Sql.ConnectionStringEnvironmentVariable, "sql.connectionStringEnvironmentVariable", errors);
|
Require(Sql.ConnectionString, "sql.connectionString", errors);
|
||||||
if (Sql.CommandTimeoutSeconds <= 0)
|
if (Sql.CommandTimeoutSeconds <= 0)
|
||||||
{
|
{
|
||||||
errors.Add("sql.commandTimeoutSeconds musi być większe od zera.");
|
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) =>
|
private static string ResolvePath(string path) =>
|
||||||
Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(path, AppContext.BaseDirectory);
|
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 Operator { get; init; } = "Administrator";
|
||||||
|
|
||||||
public string PasswordEnvironmentVariable { get; init; } = "SMARTB2B_ENOVA_PASSWORD";
|
public string? Password { get; init; }
|
||||||
|
|
||||||
public string DocumentDefinition { get; init; } = "ZO";
|
public string DocumentDefinition { get; init; } = "ZO";
|
||||||
|
|
||||||
@@ -142,7 +139,7 @@ public sealed class EnovaSettings
|
|||||||
|
|
||||||
public sealed class SqlSettings
|
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;
|
public int CommandTimeoutSeconds { get; init; } = 30;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
|
"installationPath": "C:\\Program Files (x86)\\Soneta\\enova365 2604.4.4",
|
||||||
"database": "Firma demo",
|
"database": "Firma demo",
|
||||||
"operator": "Administrator",
|
"operator": "Administrator",
|
||||||
"passwordEnvironmentVariable": "SMARTB2B_ENOVA_PASSWORD",
|
"password": "<hasło operatora>",
|
||||||
"documentDefinition": "ZO",
|
"documentDefinition": "ZO",
|
||||||
"defaultWarehouseCode": null,
|
"defaultWarehouseCode": null,
|
||||||
"saveMode": "Buffer"
|
"saveMode": "Buffer"
|
||||||
},
|
},
|
||||||
"sql": {
|
"sql": {
|
||||||
"connectionStringEnvironmentVariable": "SMARTB2B_SQL_CONNECTION_STRING",
|
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
||||||
"commandTimeoutSeconds": 30
|
"commandTimeoutSeconds": 30
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using SmartB2B.Enova.Contracts;
|
using SmartB2B.Enova.Contracts;
|
||||||
|
using SmartB2B.Enova.Service.Configuration;
|
||||||
using SmartB2B.Enova.Service.Orders;
|
using SmartB2B.Enova.Service.Orders;
|
||||||
using SmartB2B.Enova.Service.Rpc;
|
using SmartB2B.Enova.Service.Rpc;
|
||||||
using SmartB2B.Enova.Service.Runtime;
|
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),
|
("Warstwa WAMP wysyła wynik jako kwargs", WampKeywordWireResult),
|
||||||
("Brak waluty zwraca stabilny błąd", MissingCurrency),
|
("Brak waluty zwraca stabilny błąd", MissingCurrency),
|
||||||
("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency),
|
("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;
|
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(
|
static RpcInvocation CreateInvocation() => new(
|
||||||
[],
|
[],
|
||||||
new Dictionary<string, object?>
|
new Dictionary<string, object?>
|
||||||
|
|||||||
Reference in New Issue
Block a user