Disable place_order without Enova credentials

This commit is contained in:
2026-08-26 17:01:57 +00:00
parent f15f46945d
commit a92293eefa
5 changed files with 110 additions and 47 deletions

View File

@@ -14,7 +14,7 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365.
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. 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.
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ą. Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. 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 ## Budowanie i testy

View File

@@ -48,19 +48,22 @@ public sealed class ServiceSettings
public RuntimeSettings ResolveRuntimeSettings() public RuntimeSettings ResolveRuntimeSettings()
{ {
var enovaPassword = Enova.Password
?? throw new ConfigurationException("Parametr 'enova.password' jest wymagany.");
var installationPath = Path.GetFullPath( var installationPath = Path.GetFullPath(
Environment.ExpandEnvironmentVariables(Enova.InstallationPath)); Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
return new RuntimeSettings( EnovaAdapterConfiguration? enovaConfiguration = null;
installationPath, if (!string.IsNullOrWhiteSpace(Enova.Operator) && Enova.Password is not null)
new EnovaAdapterConfiguration( {
new EnovaConnectionOptions(Enova.Database, Enova.Operator, enovaPassword), enovaConfiguration = new EnovaAdapterConfiguration(
new EnovaConnectionOptions(Enova.Database, Enova.Operator, Enova.Password),
Enova.DocumentDefinition, Enova.DocumentDefinition,
Enova.DefaultWarehouseCode, Enova.DefaultWarehouseCode,
Enova.SaveMode), Enova.SaveMode);
}
return new RuntimeSettings(
installationPath,
enovaConfiguration,
Sql.ConnectionString, Sql.ConnectionString,
Sql.CommandTimeoutSeconds, Sql.CommandTimeoutSeconds,
ResolvePath(Diagnostics.LogDirectory), ResolvePath(Diagnostics.LogDirectory),
@@ -78,8 +81,6 @@ 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);
RequirePresent(Enova.Password, "enova.password", errors);
Require(Enova.DocumentDefinition, "enova.documentDefinition", errors); Require(Enova.DocumentDefinition, "enova.documentDefinition", errors);
Require(Sql.ConnectionString, "sql.connectionString", errors); Require(Sql.ConnectionString, "sql.connectionString", errors);
if (Sql.CommandTimeoutSeconds <= 0) if (Sql.CommandTimeoutSeconds <= 0)
@@ -101,14 +102,6 @@ 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);
} }
@@ -126,7 +119,7 @@ public sealed class EnovaSettings
public string Database { get; init; } = string.Empty; public string Database { get; init; } = string.Empty;
public string Operator { get; init; } = "Administrator"; public string? Operator { get; init; }
public string? Password { get; init; } public string? Password { get; init; }
@@ -153,7 +146,7 @@ public sealed class DiagnosticsSettings
public sealed record RuntimeSettings( public sealed record RuntimeSettings(
string EnovaInstallationPath, string EnovaInstallationPath,
EnovaAdapterConfiguration EnovaConfiguration, EnovaAdapterConfiguration? EnovaConfiguration,
string SqlConnectionString, string SqlConnectionString,
int SqlCommandTimeoutSeconds, int SqlCommandTimeoutSeconds,
string LogDirectory, string LogDirectory,

View File

@@ -7,13 +7,13 @@ namespace SmartB2B.Enova.Service.Diagnostics;
public sealed partial class DiagnosticsHandler public sealed partial class DiagnosticsHandler
{ {
private readonly EnovaAdapterInfo _adapterInfo; private readonly EnovaAdapterInfo? _adapterInfo;
private readonly string _enovaPath; private readonly string _enovaPath;
private readonly string _logDirectory; private readonly string _logDirectory;
private readonly string _logPrefix; private readonly string _logPrefix;
public DiagnosticsHandler( public DiagnosticsHandler(
EnovaAdapterInfo adapterInfo, EnovaAdapterInfo? adapterInfo,
string enovaPath, string enovaPath,
string logDirectory, string logDirectory,
string logPrefix) string logPrefix)
@@ -35,16 +35,25 @@ public sealed partial class DiagnosticsHandler
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "unknown"; var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "unknown";
var lines = new[] var lines = new List<string>
{ {
$"SmartB2B Enova Sync version: {serviceVersion}", $"SmartB2B Enova Sync version: {serviceVersion}",
$".NET version: {Environment.Version}", $".NET version: {Environment.Version}",
$"Adapter version: {_adapterInfo.AdapterVersion}", $"Enova path: {_enovaPath}"
$"Soneta.Business version: {_adapterInfo.SonetaBusinessVersion}",
$"Soneta.Handel version: {_adapterInfo.SonetaHandelVersion}",
$"Enova path: {_enovaPath}",
$"Database: {_adapterInfo.DatabaseName}"
}; };
if (_adapterInfo is null)
{
lines.Add("Adapter Enovy: nie zainicjalizowano (place_order jest wyłączony)");
}
else
{
lines.Add($"Adapter version: {_adapterInfo.AdapterVersion}");
lines.Add($"Soneta.Business version: {_adapterInfo.SonetaBusinessVersion}");
lines.Add($"Soneta.Handel version: {_adapterInfo.SonetaHandelVersion}");
lines.Add($"Database: {_adapterInfo.DatabaseName}");
}
return Task.FromResult<object?>(string.Join(Environment.NewLine, lines)); return Task.FromResult<object?>(string.Join(Environment.NewLine, lines));
} }

View File

@@ -1,3 +1,4 @@
using SmartB2B.Enova.Contracts;
using SmartB2B.Enova.Service.Configuration; using SmartB2B.Enova.Service.Configuration;
using SmartB2B.Enova.Service.Diagnostics; using SmartB2B.Enova.Service.Diagnostics;
using SmartB2B.Enova.Service.Orders; using SmartB2B.Enova.Service.Orders;
@@ -28,7 +29,21 @@ internal static class Program
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath); using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
var adapter = adapterLoader.Load(); var adapter = adapterLoader.Load();
var adapterInfo = adapter.Initialize(runtime.EnovaConfiguration); EnovaAdapterInfo? adapterInfo = null;
if (runtime.EnovaConfiguration is not null)
{
adapterInfo = adapter.Initialize(runtime.EnovaConfiguration);
Console.WriteLine(
$"Załadowano Enovę {adapterInfo.SonetaBusinessVersion} z '{runtime.EnovaInstallationPath}'.");
Console.WriteLine($"Baza: {adapterInfo.DatabaseName}; portal WAMP: {settings.Portal}.");
}
else
{
Console.WriteLine(
"Nie podano enova.operator lub enova.password; endpoint eu.smartb2b.place_order jest wyłączony.");
Console.WriteLine($"Portal WAMP: {settings.Portal}.");
}
_ = new System.Data.Common.DbConnectionStringBuilder _ = new System.Data.Common.DbConnectionStringBuilder
{ {
@@ -38,32 +53,32 @@ internal static class Program
runtime.SqlConnectionString, runtime.SqlConnectionString,
runtime.SqlCommandTimeoutSeconds); runtime.SqlCommandTimeoutSeconds);
Console.WriteLine(
$"Załadowano Enovę {adapterInfo.SonetaBusinessVersion} z '{runtime.EnovaInstallationPath}'.");
Console.WriteLine($"Baza: {adapterInfo.DatabaseName}; portal WAMP: {settings.Portal}.");
if (commandLine.CheckConfigurationOnly) if (commandLine.CheckConfigurationOnly)
{ {
Console.WriteLine("Konfiguracja i adapter Enovy są poprawne."); Console.WriteLine(runtime.EnovaConfiguration is null
? "Konfiguracja jest poprawna; endpoint place_order pozostaje wyłączony."
: "Konfiguracja i adapter Enovy są poprawne.");
return 0; return 0;
} }
var orders = new PlaceOrderHandler(adapter, runtime.EnovaConfiguration);
var diagnostics = new DiagnosticsHandler( var diagnostics = new DiagnosticsHandler(
adapterInfo, adapterInfo,
runtime.EnovaInstallationPath, runtime.EnovaInstallationPath,
runtime.LogDirectory, runtime.LogDirectory,
runtime.LogPrefix); runtime.LogPrefix);
IReadOnlyList<IWampRpcOperation> operations = var operations = new List<IWampRpcOperation>();
[ if (runtime.EnovaConfiguration is not null)
new DelegateRpcOperation("eu.smartb2b.place_order", orders.HandleAsync), {
new DelegateRpcOperation("eu.smartb2b.sql_raw", sql.HandleAsync), var orders = new PlaceOrderHandler(adapter, runtime.EnovaConfiguration);
new DelegateRpcOperation("eu.smartb2b.sync.get_version", diagnostics.GetVersionAsync), operations.Add(new DelegateRpcOperation("eu.smartb2b.place_order", orders.HandleAsync));
new DelegateRpcOperation("eu.smartb2b.sync.get_info", diagnostics.GetInfoAsync), }
new DelegateRpcOperation("eu.smartb2b.sync.get_log", diagnostics.GetLogAsync),
new DelegateRpcOperation("eu.smartb2b.sync.get_error_log", diagnostics.GetErrorLogAsync) operations.Add(new DelegateRpcOperation("eu.smartb2b.sql_raw", sql.HandleAsync));
]; operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_version", diagnostics.GetVersionAsync));
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_info", diagnostics.GetInfoAsync));
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_log", diagnostics.GetLogAsync));
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_error_log", diagnostics.GetErrorLogAsync));
var wampClient = new WampServiceClient( var wampClient = new WampServiceClient(
settings.Wamp.ServerUrl, settings.Wamp.ServerUrl,

View File

@@ -25,7 +25,9 @@ var tests = new (string Name, Func<Task> Run)[]
("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) ("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile),
("Brak danych operatora wyłącza place_order", MissingCredentialsDisablePlaceOrder),
("Częściowe dane operatora wyłączają place_order", PartialCredentialsDisablePlaceOrder)
}; };
var failures = 0; var failures = 0;
@@ -214,7 +216,10 @@ static Task SecretsComeFromConfigurationFile()
var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings(); var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings();
AssertEqual("haslo-z-pliku", runtime.EnovaConfiguration.Connection.Password, "hasło operatora"); AssertEqual(
"haslo-z-pliku",
runtime.EnovaConfiguration?.Connection.Password,
"hasło operatora");
AssertEqual( AssertEqual(
"Server=db;Database=enova;User ID=user;Password=sql-z-pliku", "Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
runtime.SqlConnectionString, runtime.SqlConnectionString,
@@ -227,6 +232,47 @@ static Task SecretsComeFromConfigurationFile()
} }
} }
static Task MissingCredentialsDisablePlaceOrder()
{
var runtime = LoadRuntimeSettings(string.Empty);
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
return Task.CompletedTask;
}
static Task PartialCredentialsDisablePlaceOrder()
{
var runtime = LoadRuntimeSettings("\"operator\": \"Administrator\",");
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
return Task.CompletedTask;
}
static RuntimeSettings LoadRuntimeSettings(string credentialsJson)
{
var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json");
try
{
File.WriteAllText(path, $$"""
{
"portal": "demo",
"wamp": { "serverUrl": "ws://localhost:8080/" },
"enova": {
"installationPath": "C:/Enova",
"database": "Firma demo",
{{credentialsJson}}
"documentDefinition": "ZO"
},
"sql": { "connectionString": "Server=db;Database=enova;User ID=user;Password=secret" }
}
""");
return ServiceSettings.Load(path).ResolveRuntimeSettings();
}
finally
{
File.Delete(path);
}
}
static RpcInvocation CreateInvocation() => new( static RpcInvocation CreateInvocation() => new(
[], [],
new Dictionary<string, object?> new Dictionary<string, object?>