Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,784 @@
|
||||
using System.Data.OleDb;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Omika.Data.HfSql.Protection;
|
||||
|
||||
namespace HfSqlOleDbAudit;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
// Mode "connection-test" (lecture seule, OLE DB PCSOFT.HFSQL). Dispatch
|
||||
// ajouté sans modifier le comportement historique ci-dessous : tout autre
|
||||
// premier argument emprunte exactement le chemin d'origine.
|
||||
if (args.Length > 0
|
||||
&& StringComparer.OrdinalIgnoreCase.Equals(args[0], ConnectionTest.CommandName))
|
||||
{
|
||||
return ConnectionTest.Run(args);
|
||||
}
|
||||
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Usage: HfSqlOleDbAudit <configPath> <Batik|Omika>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
string configPath = args[0];
|
||||
string schema = args[1];
|
||||
|
||||
// ================================================================
|
||||
// CONFIG LOADING
|
||||
// ================================================================
|
||||
TestConfig? config;
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
config = JsonSerializer.Deserialize<TestConfig>(File.ReadAllText(configPath), options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
config = null;
|
||||
}
|
||||
|
||||
if (config is null)
|
||||
{
|
||||
Console.WriteLine("Config JSON: INVALID");
|
||||
return 1;
|
||||
}
|
||||
Console.WriteLine("Config JSON: VALID");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.ProtectionPlusSecret))
|
||||
{
|
||||
Console.WriteLine("ProtectionPlusSecret: MISSING");
|
||||
return 1;
|
||||
}
|
||||
Console.WriteLine("ProtectionPlusSecret: AVAILABLE");
|
||||
|
||||
// ================================================================
|
||||
// DATABASE CHOICE
|
||||
// ================================================================
|
||||
DatabaseConfig db = schema.Equals("Batik", StringComparison.OrdinalIgnoreCase)
|
||||
? config.Batik
|
||||
: config.Omika;
|
||||
|
||||
string connectionDatabase = db.Database;
|
||||
string passwordDatabaseName = db.OriginalDatabaseName ?? db.Database;
|
||||
|
||||
Console.WriteLine("PasswordDatabaseNameSource: " +
|
||||
(db.OriginalDatabaseName is null ? "CURRENT" : "ORIGINAL"));
|
||||
|
||||
// ================================================================
|
||||
// EXPLICIT LOCAL CONFIG — file passwords come from the local config,
|
||||
// NOT derived. No derivation, no wildcard.
|
||||
// ================================================================
|
||||
string protectionStd = config.FilePasswords.ProtectionStd;
|
||||
string protectionPlus = config.FilePasswords.ProtectionPlus;
|
||||
|
||||
Console.WriteLine("PasswordSource: EXPLICIT_LOCAL_CONFIG");
|
||||
Console.WriteLine("PasswordDerivationUsed: NO");
|
||||
Console.WriteLine("ProtectionPlusSecretUsedForConnection: NO");
|
||||
|
||||
|
||||
// ================================================================
|
||||
// LOAD TABLES
|
||||
// ================================================================
|
||||
List<TableInfo> tables = LoadTables(schema);
|
||||
var results = new Dictionary<string, TableResult>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var t in tables)
|
||||
{
|
||||
results[t.LogicalName] = new TableResult { Table = t };
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// PHASE 1 — DIRECT
|
||||
// ================================================================
|
||||
using (var cn = new OleDbConnection(BuildConnectionString(config, db, null)))
|
||||
{
|
||||
cn.Open();
|
||||
foreach (var t in tables)
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
QueryResult qr = TrySelect(cn, t.LogicalName);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.DirectAccess = true;
|
||||
r.FinalAccess = FinalAccess.Direct;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.DirectAccess = false;
|
||||
r.FinalAccess = FinalAccess.Unknown;
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// PHASE 2 — FAILURES-ONLY EXPLICIT MAP
|
||||
// ================================================================
|
||||
string strategy = "FAILURES_ONLY_EXPLICIT_MAP";
|
||||
List<string>? workingEntries = null;
|
||||
|
||||
var failedProtected = tables
|
||||
.Where(t => !results[t.LogicalName].DirectAccess
|
||||
&& (t.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
|| t.ExpectedGroup == ExpectedGroup.ProtectionPlus))
|
||||
.ToList();
|
||||
|
||||
if (failedProtected.Count > 0)
|
||||
{
|
||||
var entries = new List<string>();
|
||||
foreach (var t in failedProtected)
|
||||
{
|
||||
if (t.ExpectedGroup == ExpectedGroup.ProtectionStd)
|
||||
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionStd));
|
||||
else
|
||||
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
|
||||
}
|
||||
|
||||
string ext = string.Join(";", entries);
|
||||
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
|
||||
{
|
||||
cn.Open();
|
||||
foreach (var t in failedProtected)
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
QueryResult qr = TrySelect(cn, t.LogicalName);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.ProtectedAccess = true;
|
||||
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
? FinalAccess.ProtectionStd
|
||||
: FinalAccess.ProtectionPlus;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workingEntries = entries;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// CANARY CHECK — if ParametreMetier passes, STD is verified.
|
||||
// ================================================================
|
||||
bool stdVerified = false;
|
||||
bool plusVerified = false;
|
||||
|
||||
if (results.TryGetValue("ParametreMetier", out var pmFinal)
|
||||
&& !pmFinal.DirectAccess && pmFinal.ProtectedAccess)
|
||||
{
|
||||
stdVerified = true;
|
||||
}
|
||||
|
||||
if ((results.TryGetValue("ParametreMetierEntete", out var pmeFinal)
|
||||
&& !pmeFinal.DirectAccess && pmeFinal.ProtectedAccess)
|
||||
|| (results.TryGetValue("ParametreSysteme", out var psFinal)
|
||||
&& !psFinal.DirectAccess && psFinal.ProtectedAccess))
|
||||
{
|
||||
plusVerified = true;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// STRATEGY 2 — FULL EXPLICIT MAP (if ParametreMetier still failed)
|
||||
// ================================================================
|
||||
bool pmStillFailed = results.TryGetValue("ParametreMetier", out var pmCheck)
|
||||
&& pmCheck.FinalAccess != FinalAccess.Direct
|
||||
&& pmCheck.FinalAccess != FinalAccess.ProtectionStd;
|
||||
|
||||
if (pmStillFailed)
|
||||
{
|
||||
strategy = "FULL_EXPLICIT_MAP";
|
||||
var entries = new List<string>();
|
||||
foreach (var t in tables)
|
||||
{
|
||||
if (t.ExpectedGroup == ExpectedGroup.ProtectionStd)
|
||||
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionStd));
|
||||
else if (t.ExpectedGroup == ExpectedGroup.ProtectionPlus)
|
||||
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
|
||||
}
|
||||
|
||||
string ext = string.Join(";", entries);
|
||||
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
|
||||
{
|
||||
cn.Open();
|
||||
foreach (var name in new[] { "Client", "ParametreMetier", "ParametreMetierEntete", "ParametreSysteme" })
|
||||
{
|
||||
if (results.TryGetValue(name, out var r) && !r.DirectAccess)
|
||||
{
|
||||
QueryResult qr = TrySelect(cn, name);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.ProtectedAccess = true;
|
||||
r.FinalAccess = r.Table.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
? FinalAccess.ProtectionStd
|
||||
: FinalAccess.ProtectionPlus;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in tables.Where(t => !results[t.LogicalName].DirectAccess
|
||||
&& results[t.LogicalName].FinalAccess == FinalAccess.Unknown))
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
QueryResult qr = TrySelect(cn, t.LogicalName);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.ProtectedAccess = true;
|
||||
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
? FinalAccess.ProtectionStd
|
||||
: FinalAccess.ProtectionPlus;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workingEntries = entries;
|
||||
|
||||
// Re-evaluate canaries after strategy 2
|
||||
if (results.TryGetValue("ParametreMetier", out var pm2)
|
||||
&& !pm2.DirectAccess && pm2.ProtectedAccess)
|
||||
{
|
||||
stdVerified = true;
|
||||
}
|
||||
if ((results.TryGetValue("ParametreMetierEntete", out var pme2)
|
||||
&& !pme2.DirectAccess && pme2.ProtectedAccess)
|
||||
|| (results.TryGetValue("ParametreSysteme", out var ps2)
|
||||
&& !ps2.DirectAccess && ps2.ProtectedAccess))
|
||||
{
|
||||
plusVerified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// STRATEGY 3 — WILDCARD_PLUS_OVERRIDES (if ParametreMetier still failed)
|
||||
// ================================================================
|
||||
bool pmStillFailed2 = results.TryGetValue("ParametreMetier", out var pmCheck2)
|
||||
&& pmCheck2.FinalAccess != FinalAccess.Direct
|
||||
&& pmCheck2.FinalAccess != FinalAccess.ProtectionStd;
|
||||
|
||||
if (pmStillFailed2)
|
||||
{
|
||||
strategy = "WILDCARD_PLUS_OVERRIDES";
|
||||
var entries = new List<string>();
|
||||
// Wildcard first: applies ProtectionStd to all files.
|
||||
entries.Add(BuildFilePasswordEntry("*", protectionStd));
|
||||
// Then explicit overrides for each ProtectionPlus file.
|
||||
foreach (var t in tables.Where(t => t.ExpectedGroup == ExpectedGroup.ProtectionPlus))
|
||||
{
|
||||
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
|
||||
}
|
||||
|
||||
string ext = string.Join(";", entries);
|
||||
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
|
||||
{
|
||||
cn.Open();
|
||||
foreach (var name in new[] { "Client", "ParametreMetier", "ParametreMetierEntete", "ParametreSysteme" })
|
||||
{
|
||||
if (results.TryGetValue(name, out var r) && !r.DirectAccess)
|
||||
{
|
||||
QueryResult qr = TrySelect(cn, name);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.ProtectedAccess = true;
|
||||
r.FinalAccess = r.Table.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
? FinalAccess.ProtectionStd
|
||||
: FinalAccess.ProtectionPlus;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in tables.Where(t => !results[t.LogicalName].DirectAccess
|
||||
&& results[t.LogicalName].FinalAccess == FinalAccess.Unknown))
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
QueryResult qr = TrySelect(cn, t.LogicalName);
|
||||
if (qr.Success)
|
||||
{
|
||||
r.ProtectedAccess = true;
|
||||
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
|
||||
? FinalAccess.ProtectionStd
|
||||
: FinalAccess.ProtectionPlus;
|
||||
r.FieldCount = qr.FieldCount;
|
||||
r.HasRows = qr.HasRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.HfSqlErrorCode = qr.HfSqlErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workingEntries = entries;
|
||||
|
||||
// Re-evaluate canaries after strategy 3
|
||||
if (results.TryGetValue("ParametreMetier", out var pm3)
|
||||
&& !pm3.DirectAccess && pm3.ProtectedAccess)
|
||||
{
|
||||
stdVerified = true;
|
||||
}
|
||||
if ((results.TryGetValue("ParametreMetierEntete", out var pme3)
|
||||
&& !pme3.DirectAccess && pme3.ProtectedAccess)
|
||||
|| (results.TryGetValue("ParametreSysteme", out var ps3)
|
||||
&& !ps3.DirectAccess && ps3.ProtectedAccess))
|
||||
{
|
||||
plusVerified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// NEGATIVE CONTROL
|
||||
// ================================================================
|
||||
bool negativeControlRun = false;
|
||||
bool negativeControlPass = false;
|
||||
|
||||
var protectedPassed = tables.FirstOrDefault(t => results[t.LogicalName].ProtectedAccess);
|
||||
if (protectedPassed is not null && workingEntries is not null)
|
||||
{
|
||||
negativeControlRun = true;
|
||||
string fakePassword = "00000000000000000000000000000000";
|
||||
string chosenName = protectedPassed.LogicalName;
|
||||
|
||||
var fakeEntries = new List<string>(workingEntries);
|
||||
string escapedChosen = EscapeExtendedPropertyValue(chosenName);
|
||||
fakeEntries.RemoveAll(e => e.StartsWith("Password=" + escapedChosen + ":"));
|
||||
fakeEntries.Add(BuildFilePasswordEntry(chosenName, fakePassword));
|
||||
|
||||
string fakeExt = string.Join(";", fakeEntries);
|
||||
using (var cn = new OleDbConnection(BuildConnectionString(config, db, fakeExt)))
|
||||
{
|
||||
cn.Open();
|
||||
QueryResult qr = TrySelect(cn, chosenName);
|
||||
negativeControlPass = !qr.Success;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// FINALIZE — mark any table still unresolved as Failed
|
||||
// ================================================================
|
||||
foreach (var t in tables)
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
if (r.FinalAccess == FinalAccess.Unknown)
|
||||
{
|
||||
r.FinalAccess = FinalAccess.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// CONSOLE OUTPUT
|
||||
// ================================================================
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"=== {schema} ===");
|
||||
Console.WriteLine();
|
||||
|
||||
foreach (var t in tables)
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
Console.WriteLine("LogicalFile: " + t.LogicalName);
|
||||
Console.WriteLine("ExpectedGroup: " + t.ExpectedGroup);
|
||||
Console.WriteLine("DirectAccess: " + r.DirectAccess);
|
||||
Console.WriteLine("ProtectedAccess: " + r.ProtectedAccess);
|
||||
Console.WriteLine("FinalAccess: " + GetFinalAccessLabel(r.FinalAccess));
|
||||
Console.WriteLine("FieldCount: " + r.FieldCount);
|
||||
Console.WriteLine("HasRows: " + r.HasRows);
|
||||
Console.WriteLine("HfSqlErrorCode: " + (r.HfSqlErrorCode ?? "NONE"));
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
int directCount = tables.Count(t => results[t.LogicalName].DirectAccess);
|
||||
int stdCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.ProtectionStd);
|
||||
int plusCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.ProtectionPlus);
|
||||
int failedCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.Failed);
|
||||
|
||||
Console.WriteLine("=== SUMMARY ===");
|
||||
Console.WriteLine($"Tables: {tables.Count}");
|
||||
Console.WriteLine($"Direct: {directCount}");
|
||||
Console.WriteLine($"ProtectionStd: {stdCount}");
|
||||
Console.WriteLine($"ProtectionPlus: {plusCount}");
|
||||
Console.WriteLine($"Failed: {failedCount}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
|
||||
Console.WriteLine($"ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
|
||||
Console.WriteLine($"ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Strategy: {strategy}");
|
||||
Console.WriteLine($"ProtectionStd OLE DB: {(stdVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
|
||||
Console.WriteLine($"ProtectionPlus OLE DB: {(plusVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
|
||||
Console.WriteLine($"NegativeControl: {(negativeControlRun ? (negativeControlPass ? "PASS" : "FAIL") : "NOT_RUN")}");
|
||||
|
||||
// ================================================================
|
||||
// CSV REPORT
|
||||
// ================================================================
|
||||
string csvPath = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "docs", "diagnostics", "hfsql-oledb-open-all.csv");
|
||||
csvPath = Path.GetFullPath(csvPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(csvPath)!);
|
||||
|
||||
bool csvExists = File.Exists(csvPath);
|
||||
var csvLines = new List<string>();
|
||||
if (!csvExists)
|
||||
{
|
||||
csvLines.Add("Schema,Table,DiskName,ExpectedGroup,DirectAccess,ProtectedAccess,FinalAccess,FieldCount,HasRows,HfSqlErrorCode");
|
||||
}
|
||||
foreach (var t in tables)
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
csvLines.Add(string.Join(",",
|
||||
Csv(schema), Csv(t.LogicalName), Csv(t.DiskName), Csv(t.ExpectedGroup.ToString()),
|
||||
Csv(r.DirectAccess.ToString()), Csv(r.ProtectedAccess.ToString()),
|
||||
Csv(GetFinalAccessLabel(r.FinalAccess)),
|
||||
Csv(r.FieldCount.ToString()), Csv(r.HasRows.ToString()),
|
||||
Csv(r.HfSqlErrorCode ?? "")));
|
||||
}
|
||||
File.AppendAllLines(csvPath, csvLines);
|
||||
Console.WriteLine($"CSV: CREATED ({csvPath})");
|
||||
|
||||
// ================================================================
|
||||
// MARKDOWN REPORT
|
||||
// ================================================================
|
||||
string mdPath = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "docs", "diagnostics", "hfsql-oledb-open-all.md");
|
||||
mdPath = Path.GetFullPath(mdPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(mdPath)!);
|
||||
|
||||
var md = new System.Text.StringBuilder();
|
||||
bool mdExists = File.Exists(mdPath);
|
||||
if (!mdExists)
|
||||
{
|
||||
md.AppendLine("# HFSQL OLE DB Open All — Diagnostic");
|
||||
md.AppendLine();
|
||||
}
|
||||
md.AppendLine($"## {schema}");
|
||||
md.AppendLine();
|
||||
md.AppendLine("### Résumé");
|
||||
md.AppendLine();
|
||||
md.AppendLine($"- Tables: {tables.Count}");
|
||||
md.AppendLine($"- Direct: {directCount}");
|
||||
md.AppendLine($"- ProtectionStd: {stdCount}");
|
||||
md.AppendLine($"- ProtectionPlus: {plusCount}");
|
||||
md.AppendLine($"- Failed: {failedCount}");
|
||||
md.AppendLine();
|
||||
md.AppendLine($"- ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
|
||||
md.AppendLine($"- ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
|
||||
md.AppendLine($"- ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
|
||||
md.AppendLine();
|
||||
md.AppendLine($"- Strategy: {strategy}");
|
||||
md.AppendLine($"- ProtectionStd OLE DB: {(stdVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
|
||||
md.AppendLine($"- ProtectionPlus OLE DB: {(plusVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
|
||||
md.AppendLine($"- NegativeControl: {(negativeControlRun ? (negativeControlPass ? "PASS" : "FAIL") : "NOT_RUN")}");
|
||||
md.AppendLine();
|
||||
md.AppendLine("### Canaris");
|
||||
md.AppendLine();
|
||||
md.AppendLine($"- ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
|
||||
md.AppendLine($"- ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
|
||||
md.AppendLine($"- ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
|
||||
md.AppendLine();
|
||||
md.AppendLine("### Échecs");
|
||||
md.AppendLine();
|
||||
foreach (var t in tables.Where(t => results[t.LogicalName].FinalAccess == FinalAccess.Failed))
|
||||
{
|
||||
var r = results[t.LogicalName];
|
||||
md.AppendLine($"- {t.LogicalName} (Expected={t.ExpectedGroup}, HfSql={r.HfSqlErrorCode ?? "NONE"})");
|
||||
}
|
||||
md.AppendLine();
|
||||
File.AppendAllText(mdPath, md.ToString());
|
||||
Console.WriteLine($"MD: CREATED ({mdPath})");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// HELPERS
|
||||
// ================================================================
|
||||
|
||||
private static string GetCanaryLabel(Dictionary<string, TableResult> results, string name)
|
||||
{
|
||||
if (!results.TryGetValue(name, out var r)) return "FAILED";
|
||||
return r.FinalAccess switch
|
||||
{
|
||||
FinalAccess.Direct => "DIRECT",
|
||||
FinalAccess.ProtectionStd => "PROTECTION_STD",
|
||||
FinalAccess.ProtectionPlus => "PROTECTION_PLUS",
|
||||
FinalAccess.Failed => "FAILED",
|
||||
_ => "FAILED"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetFinalAccessLabel(FinalAccess access)
|
||||
{
|
||||
return access switch
|
||||
{
|
||||
FinalAccess.Direct => "DIRECT",
|
||||
FinalAccess.ProtectionStd => "PROTECTION_STD",
|
||||
FinalAccess.ProtectionPlus => "PROTECTION_PLUS",
|
||||
FinalAccess.Failed => "FAILED",
|
||||
_ => "UNKNOWN"
|
||||
};
|
||||
}
|
||||
|
||||
private static string Csv(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
return value;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// MODEL LOADING (reflection)
|
||||
// ================================================================
|
||||
|
||||
private static List<TableInfo> LoadTables(string schema)
|
||||
{
|
||||
var assembly = typeof(HfsqlFilePasswordDeriver).Assembly;
|
||||
string ns = schema.Equals("Batik", StringComparison.OrdinalIgnoreCase)
|
||||
? "Omika.Data.HfSql.Models.Batik"
|
||||
: "Omika.Data.HfSql.Models.Omika";
|
||||
|
||||
var tables = new List<TableInfo>();
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.Namespace != ns) continue;
|
||||
|
||||
var attr = type.GetCustomAttributes()
|
||||
.FirstOrDefault(a => a.GetType().Name == "HfSqlTableAttribute");
|
||||
if (attr is null) continue;
|
||||
|
||||
string tableName = GetAttrProperty(attr, "TableName") ?? type.Name;
|
||||
string diskName = GetAttrProperty(attr, "DiskName") ?? tableName + ".fic";
|
||||
string[] groups = GetAttrGroups(attr);
|
||||
|
||||
bool hasStd = groups.Any(g => g.Equals("ProtectionStd", StringComparison.OrdinalIgnoreCase));
|
||||
bool hasPlus = groups.Any(g => g.Equals("ProtectionPlus", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
ExpectedGroup expected;
|
||||
if (hasStd && hasPlus) expected = ExpectedGroup.Conflict;
|
||||
else if (hasPlus) expected = ExpectedGroup.ProtectionPlus;
|
||||
else if (hasStd) expected = ExpectedGroup.ProtectionStd;
|
||||
else expected = ExpectedGroup.None;
|
||||
|
||||
tables.Add(new TableInfo
|
||||
{
|
||||
LogicalName = tableName,
|
||||
DiskName = diskName,
|
||||
ExpectedGroup = expected
|
||||
});
|
||||
}
|
||||
|
||||
return tables.OrderBy(t => t.LogicalName).ToList();
|
||||
}
|
||||
|
||||
private static string? GetAttrProperty(object attr, string propName)
|
||||
{
|
||||
var prop = attr.GetType().GetProperty(propName);
|
||||
if (prop is null) return null;
|
||||
var val = prop.GetValue(attr);
|
||||
return val?.ToString();
|
||||
}
|
||||
|
||||
private static string[] GetAttrGroups(object attr)
|
||||
{
|
||||
var prop = attr.GetType().GetProperty("Groups");
|
||||
if (prop is null) return [];
|
||||
var val = prop.GetValue(attr);
|
||||
if (val is string[] arr) return arr;
|
||||
if (val is IEnumerable<string> en) return en.ToArray();
|
||||
return [];
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// CONNECTION STRING / EXTENDED PROPERTIES
|
||||
// ================================================================
|
||||
|
||||
private static string EscapeExtendedPropertyValue(string value)
|
||||
{
|
||||
return value
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("=", "\\=")
|
||||
.Replace(";", "\\;")
|
||||
.Replace(":", "\\:");
|
||||
}
|
||||
|
||||
private static string BuildFilePasswordEntry(string logicalFileName, string password)
|
||||
{
|
||||
return "Password=" +
|
||||
EscapeExtendedPropertyValue(logicalFileName) +
|
||||
":" +
|
||||
EscapeExtendedPropertyValue(password);
|
||||
}
|
||||
|
||||
private static string BuildConnectionString(
|
||||
TestConfig root,
|
||||
DatabaseConfig database,
|
||||
string? extendedProperties)
|
||||
{
|
||||
var builder = new OleDbConnectionStringBuilder();
|
||||
|
||||
builder.Provider = "PCSOFT.HFSQL";
|
||||
|
||||
builder.DataSource = root.Server + ":" + root.Port;
|
||||
|
||||
builder["Initial Catalog"] = database.Database;
|
||||
|
||||
builder["User ID"] = database.User;
|
||||
|
||||
builder["Password"] = database.Credential;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(extendedProperties))
|
||||
{
|
||||
builder["Extended Properties"] = extendedProperties;
|
||||
}
|
||||
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// QUERY
|
||||
// ================================================================
|
||||
|
||||
private static QueryResult TrySelect(OleDbConnection connection, string logicalName)
|
||||
{
|
||||
if (!IsValidIdentifier(logicalName))
|
||||
{
|
||||
return new QueryResult(false, 0, false, null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = $"SELECT TOP 1 * FROM {logicalName}";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
int fieldCount = reader.FieldCount;
|
||||
bool hasRows = reader.Read();
|
||||
return new QueryResult(true, fieldCount, hasRows, null);
|
||||
}
|
||||
catch (OleDbException ex)
|
||||
{
|
||||
return new QueryResult(false, 0, false, ExtractHfSqlErrorCode(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidIdentifier(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return false;
|
||||
foreach (char c in name)
|
||||
{
|
||||
bool ok = (c >= 'A' && c <= 'Z')
|
||||
|| (c >= 'a' && c <= 'z')
|
||||
|| (c >= '0' && c <= '9')
|
||||
|| c == '_';
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? ExtractHfSqlErrorCode(OleDbException ex)
|
||||
{
|
||||
string msg = ex.Message;
|
||||
var m = Regex.Match(msg, @"Code\s+erreur\s*:\s*(\d+)");
|
||||
if (!m.Success)
|
||||
m = Regex.Match(msg, @"Error\s+code\s*:\s*(\d+)");
|
||||
return m.Success ? m.Groups[1].Value : null;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// STRUCTURES
|
||||
// ================================================================
|
||||
|
||||
private sealed record QueryResult(
|
||||
bool Success,
|
||||
int FieldCount,
|
||||
bool HasRows,
|
||||
string? HfSqlErrorCode);
|
||||
|
||||
private sealed class TestConfig
|
||||
{
|
||||
public int Port { get; set; }
|
||||
public string Server { get; set; } = "";
|
||||
public string ProtectionPlusSecret { get; set; } = "";
|
||||
public FilePasswordsConfig FilePasswords { get; set; } = new();
|
||||
public DatabaseConfig Batik { get; set; } = new();
|
||||
public DatabaseConfig Omika { get; set; } = new();
|
||||
}
|
||||
|
||||
private sealed class FilePasswordsConfig
|
||||
{
|
||||
public string ProtectionStd { get; set; } = "";
|
||||
public string ProtectionPlus { get; set; } = "";
|
||||
}
|
||||
|
||||
|
||||
private sealed class DatabaseConfig
|
||||
{
|
||||
public string Credential { get; set; } = "";
|
||||
public string Database { get; set; } = "";
|
||||
public string User { get; set; } = "";
|
||||
public string? OriginalDatabaseName { get; set; }
|
||||
}
|
||||
|
||||
private enum ExpectedGroup
|
||||
{
|
||||
None,
|
||||
ProtectionStd,
|
||||
ProtectionPlus,
|
||||
Conflict
|
||||
}
|
||||
|
||||
private enum FinalAccess
|
||||
{
|
||||
Unknown,
|
||||
Direct,
|
||||
ProtectionStd,
|
||||
ProtectionPlus,
|
||||
Failed
|
||||
}
|
||||
|
||||
private sealed class TableInfo
|
||||
{
|
||||
public string LogicalName { get; init; } = "";
|
||||
public string DiskName { get; init; } = "";
|
||||
public ExpectedGroup ExpectedGroup { get; init; }
|
||||
}
|
||||
|
||||
private sealed class TableResult
|
||||
{
|
||||
public required TableInfo Table { get; init; }
|
||||
|
||||
public bool DirectAccess { get; set; }
|
||||
|
||||
public bool ProtectedAccess { get; set; }
|
||||
|
||||
public FinalAccess FinalAccess { get; set; }
|
||||
|
||||
public int FieldCount { get; set; }
|
||||
|
||||
public bool HasRows { get; set; }
|
||||
|
||||
public string? HfSqlErrorCode { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user