Simplify DB to six tables and load credentials via Always Encrypted SonicCredential.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 08:23:55 +02:00
co-authored by Cursor
parent 4d19873776
commit dcfcb5289d
15 changed files with 1356 additions and 461 deletions
@@ -1,4 +1,5 @@
using Microsoft.Data.SqlClient;
using System.Data;
using Microsoft.Data.SqlClient;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
@@ -20,65 +21,57 @@ public sealed class DeploymentTargetRepository
}
public async Task<IReadOnlyList<DeploymentTarget>> GetActiveAsync(
string environmentCode,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(environmentCode))
{
throw new ArgumentException(
"Der EnvironmentCode fehlt.",
nameof(environmentCode));
}
const string sql = """
SELECT
target.CertificateTargetId,
target.TargetDirectory,
target.TargetFileName,
target.BackupEnabled,
target.BackupDirectoryName,
target.BackupRetentionDays,
target.IsActive AS TargetIsActive,
target.[CertificateTargetId],
target.[TargetDirectory],
target.[TargetFileName],
target.[BackupEnabled],
target.[BackupDirectoryName],
target.[IsActive] AS [TargetIsActive],
container.SonicContainerId,
container.ContainerName,
container.ContainerDisplayName,
container.RestartTimeoutSeconds,
container.[SonicContainerId],
container.[ContainerName],
container.[ContainerDisplayName],
container.[RestartTimeoutSeconds],
connection.ConnectionName,
connection.[ConnectionName],
service.ServiceCode,
service.ServiceName,
environment.[EnvironmentCode],
environment.[SortOrder] AS [EnvironmentSortOrder],
environment.EnvironmentCode,
environment.SortOrder AS EnvironmentSortOrder,
company.CompanyCode,
country.CountryCode
FROM dbo.CertificateTarget AS target
INNER JOIN dbo.SonicContainer AS container
ON container.SonicContainerId = target.SonicContainerId
INNER JOIN dbo.SonicConnection AS connection
ON connection.SonicConnectionId = container.SonicConnectionId
INNER JOIN dbo.EsbService AS service
ON service.EsbServiceId = container.EsbServiceId
INNER JOIN dbo.Company AS company
ON company.CompanyId = service.CompanyId
LEFT JOIN dbo.Country AS country
ON country.CountryId = service.CountryId
INNER JOIN dbo.Environment AS environment
ON environment.EnvironmentId = service.EnvironmentId
WHERE target.IsActive = 1
AND container.IsActive = 1
AND connection.IsActive = 1
AND service.IsActive = 1
AND company.IsActive = 1
AND environment.IsActive = 1
AND
(
country.CountryId IS NULL
OR country.IsActive = 1
)
company.[CompanyCode]
FROM [dbo].[CertificateTarget] AS target
INNER JOIN [dbo].[SonicContainer] AS container
ON container.[SonicContainerId] = target.[SonicContainerId]
INNER JOIN [dbo].[SonicConnection] AS connection
ON connection.[SonicConnectionId] = container.[SonicConnectionId]
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] = connection.[EnvironmentId]
INNER JOIN [dbo].[Company] AS company
ON company.[CompanyId] = container.[CompanyId]
WHERE target.[IsActive] = 1
AND container.[IsActive] = 1
AND connection.[IsActive] = 1
AND company.[IsActive] = 1
AND environment.[IsActive] = 1
AND environment.[EnvironmentCode] = @EnvironmentCode
ORDER BY
company.CompanyCode,
country.CountryCode,
environment.SortOrder,
service.ServiceCode,
container.ContainerName,
target.TargetDirectory,
target.TargetFileName;
company.[CompanyCode],
environment.[SortOrder],
container.[ContainerName],
target.[TargetDirectory],
target.[TargetFileName];
""";
List<DeploymentTarget> targets = [];
@@ -88,6 +81,17 @@ public sealed class DeploymentTargetRepository
await using SqlCommand command = new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@EnvironmentCode",
SqlDbType.NVarChar,
30)
{
Value = environmentCode
.Trim()
.ToUpperInvariant()
});
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken);
@@ -106,9 +110,6 @@ public sealed class DeploymentTargetRepository
int backupDirectoryNameOrdinal =
reader.GetOrdinal("BackupDirectoryName");
int backupRetentionDaysOrdinal =
reader.GetOrdinal("BackupRetentionDays");
int targetIsActiveOrdinal =
reader.GetOrdinal("TargetIsActive");
@@ -124,12 +125,6 @@ public sealed class DeploymentTargetRepository
int connectionNameOrdinal =
reader.GetOrdinal("ConnectionName");
int serviceCodeOrdinal =
reader.GetOrdinal("ServiceCode");
int serviceNameOrdinal =
reader.GetOrdinal("ServiceName");
int environmentCodeOrdinal =
reader.GetOrdinal("EnvironmentCode");
@@ -139,9 +134,6 @@ public sealed class DeploymentTargetRepository
int companyCodeOrdinal =
reader.GetOrdinal("CompanyCode");
int countryCodeOrdinal =
reader.GetOrdinal("CountryCode");
while (await reader.ReadAsync(cancellationToken))
{
string containerName =
@@ -152,34 +144,19 @@ public sealed class DeploymentTargetRepository
reader,
containerDisplayNameOrdinal);
string serviceCode =
reader.GetString(serviceCodeOrdinal);
string serviceName =
reader.GetString(serviceNameOrdinal);
string companyCode =
reader.GetString(companyCodeOrdinal);
string? countryCode =
ReadNullableString(reader, countryCodeOrdinal);
string displayName =
string.IsNullOrWhiteSpace(containerDisplayName)
? containerName
: containerDisplayName;
string locationCode =
string.IsNullOrWhiteSpace(countryCode)
? companyCode
: $"{companyCode}/{countryCode}";
targets.Add(new DeploymentTarget
{
Id = reader.GetInt32(targetIdOrdinal),
Name =
$"{locationCode} / {serviceName} / {displayName}",
Name = $"{companyCode} / {displayName}",
Environment =
reader.GetString(environmentCodeOrdinal),
@@ -199,10 +176,7 @@ public sealed class DeploymentTargetRepository
BackupDirectoryName =
reader.GetString(backupDirectoryNameOrdinal),
BackupRetentionDays =
ReadNullableInt32(
reader,
backupRetentionDaysOrdinal),
BackupRetentionDays = null,
ContainerName = containerName,
@@ -238,13 +212,4 @@ public sealed class DeploymentTargetRepository
? null
: reader.GetString(ordinal);
}
private static int? ReadNullableInt32(
SqlDataReader reader,
int ordinal)
{
return reader.IsDBNull(ordinal)
? null
: reader.GetInt32(ordinal);
}
}
}
@@ -44,7 +44,7 @@ public sealed class LocalSetupSelectionStore
catch
{
// Eine beschädigte lokale Auswahl darf den Start
// nicht verhindern. Das Setup wird erneut angezeigt.
// nicht verhindern. Die Einstellungen werden erneut angezeigt.
return null;
}
}
@@ -60,7 +60,7 @@ public sealed class LocalSetupSelectionStore
if (string.IsNullOrWhiteSpace(directory))
{
throw new InvalidOperationException(
"Der lokale Setup-Ordner konnte nicht bestimmt werden.");
"Der lokale Einstellungen-Ordner konnte nicht bestimmt werden.");
}
Directory.CreateDirectory(directory);
@@ -23,26 +23,31 @@ public sealed class SonicConnectionRepository
public async Task<IReadOnlyList<DatabaseSonicConnection>>
GetActiveAsync(
string? environmentCode = null,
CancellationToken cancellationToken = default)
{
const string sql = """
SELECT
SonicConnectionId,
ConnectionName,
ManagementHost,
ManagementPort,
ConnectionProtocol,
DomainName,
CredentialUserName,
CredentialReference,
CredentialSecret,
JavaHomePath,
SonicHomePath,
Notes,
IsActive
FROM dbo.SonicConnection
WHERE IsActive = 1
ORDER BY ConnectionName;
connection.[SonicConnectionId],
connection.[ConnectionName],
connection.[ManagementHost],
connection.[ManagementPort],
connection.[ConnectionProtocol],
connection.[DomainName],
environment.[EnvironmentCode],
connection.[IsActive]
FROM [dbo].[SonicConnection] AS connection
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] =
connection.[EnvironmentId]
WHERE connection.[IsActive] = 1
AND environment.[IsActive] = 1
AND
(
@EnvironmentCode IS NULL
OR environment.[EnvironmentCode] = @EnvironmentCode
)
ORDER BY connection.[ConnectionName];
""";
List<DatabaseSonicConnection> connections = [];
@@ -55,6 +60,22 @@ public sealed class SonicConnectionRepository
await using SqlCommand command =
new(sql, connection);
string? normalizedEnvironmentCode =
string.IsNullOrWhiteSpace(environmentCode)
? null
: environmentCode.Trim().ToUpperInvariant();
command.Parameters.Add(
new SqlParameter(
"@EnvironmentCode",
SqlDbType.NVarChar,
30)
{
Value = normalizedEnvironmentCode is null
? DBNull.Value
: normalizedEnvironmentCode
});
await using SqlDataReader reader =
await command.ExecuteReaderAsync(
cancellationToken);
@@ -77,23 +98,8 @@ public sealed class SonicConnectionRepository
int domainOrdinal =
reader.GetOrdinal("DomainName");
int userNameOrdinal =
reader.GetOrdinal("CredentialUserName");
int credentialReferenceOrdinal =
reader.GetOrdinal("CredentialReference");
int credentialSecretOrdinal =
reader.GetOrdinal("CredentialSecret");
int javaHomeOrdinal =
reader.GetOrdinal("JavaHomePath");
int sonicHomeOrdinal =
reader.GetOrdinal("SonicHomePath");
int notesOrdinal =
reader.GetOrdinal("Notes");
int environmentCodeOrdinal =
reader.GetOrdinal("EnvironmentCode");
int isActiveOrdinal =
reader.GetOrdinal("IsActive");
@@ -112,9 +118,7 @@ public sealed class SonicConnectionRepository
reader.GetString(hostOrdinal),
ManagementPort =
ReadNullableInt32(
reader,
portOrdinal),
reader.GetInt32(portOrdinal),
ConnectionProtocol =
ReadNullableString(
@@ -126,35 +130,10 @@ public sealed class SonicConnectionRepository
reader,
domainOrdinal),
CredentialUserName =
EnvironmentCode =
ReadNullableString(
reader,
userNameOrdinal),
CredentialReference =
ReadNullableString(
reader,
credentialReferenceOrdinal),
CredentialSecret =
ReadNullableString(
reader,
credentialSecretOrdinal),
JavaHomePath =
ReadNullableString(
reader,
javaHomeOrdinal),
SonicHomePath =
ReadNullableString(
reader,
sonicHomeOrdinal),
Notes =
ReadNullableString(
reader,
notesOrdinal),
environmentCodeOrdinal),
IsActive =
reader.GetBoolean(
@@ -165,154 +144,6 @@ public sealed class SonicConnectionRepository
return connections;
}
public async Task SaveCredentialsAsync(
int sonicConnectionId,
string credentialUserName,
string credentialSecret,
CancellationToken cancellationToken = default)
{
if (sonicConnectionId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(sonicConnectionId),
"Die SonicConnectionId ist ungültig.");
}
if (string.IsNullOrWhiteSpace(
credentialUserName))
{
throw new ArgumentException(
"Der Sonic-Benutzername fehlt.",
nameof(credentialUserName));
}
if (string.IsNullOrEmpty(
credentialSecret))
{
throw new ArgumentException(
"Das Sonic-Kennwort fehlt.",
nameof(credentialSecret));
}
string normalizedUserName =
credentialUserName.Trim();
if (normalizedUserName.Length > 256)
{
throw new ArgumentException(
"Der Sonic-Benutzername darf maximal 256 Zeichen enthalten.",
nameof(credentialUserName));
}
if (credentialSecret.Length > 512)
{
throw new ArgumentException(
"Das Sonic-Kennwort darf maximal 512 Zeichen enthalten.",
nameof(credentialSecret));
}
const string sql = """
UPDATE dbo.SonicConnection
SET
CredentialUserName = @CredentialUserName,
CredentialSecret = @CredentialSecret,
UpdatedAtUtc = SYSUTCDATETIME()
WHERE SonicConnectionId = @SonicConnectionId
AND IsActive = 1;
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@CredentialUserName",
SqlDbType.NVarChar,
256)
{
Value = normalizedUserName
});
command.Parameters.Add(
new SqlParameter(
"@CredentialSecret",
SqlDbType.NVarChar,
512)
{
Value = credentialSecret
});
command.Parameters.Add(
new SqlParameter(
"@SonicConnectionId",
SqlDbType.Int)
{
Value = sonicConnectionId
});
int affectedRows =
await command.ExecuteNonQueryAsync(
cancellationToken);
if (affectedRows != 1)
{
throw new InvalidOperationException(
"Die aktive Sonic-Verbindung wurde nicht eindeutig gefunden.");
}
}
public async Task ClearCredentialsAsync(
int sonicConnectionId,
CancellationToken cancellationToken = default)
{
if (sonicConnectionId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(sonicConnectionId),
"Die SonicConnectionId ist ungültig.");
}
const string sql = """
UPDATE dbo.SonicConnection
SET
CredentialUserName = NULL,
CredentialSecret = NULL,
UpdatedAtUtc = SYSUTCDATETIME()
WHERE SonicConnectionId = @SonicConnectionId;
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@SonicConnectionId",
SqlDbType.Int)
{
Value = sonicConnectionId
});
int affectedRows =
await command.ExecuteNonQueryAsync(
cancellationToken);
if (affectedRows != 1)
{
throw new InvalidOperationException(
"Die Sonic-Verbindung wurde nicht eindeutig gefunden.");
}
}
private static string? ReadNullableString(
SqlDataReader reader,
int ordinal)
@@ -321,13 +152,4 @@ public sealed class SonicConnectionRepository
? null
: reader.GetString(ordinal);
}
private static int? ReadNullableInt32(
SqlDataReader reader,
int ordinal)
{
return reader.IsDBNull(ordinal)
? null
: reader.GetInt32(ordinal);
}
}
}
@@ -60,7 +60,7 @@ public sealed class SonicMfApiExecutor
null,
"Für die Sonic-Verbindung sind keine " +
"vollständigen Zugangsdaten eingerichtet. " +
"Bitte das Setup ausführen.");
"Bitte die Einstellungen öffnen und Zugangsdaten hinterlegen.");
}
if (string.IsNullOrWhiteSpace(
@@ -145,8 +145,8 @@ public sealed class SonicMfApiExecutor
* übergeben. Dadurch erscheint das Kennwort nicht in
* der Prozessargumentliste.
*
* CredentialSecret wurde zuvor durch Microsoft.Data.SqlClient
* clientseitig entschlüsselt und als Laufzeitwert in
* Das Kennwort stammt aus dbo.SonicCredential (Always Encrypted),
* wurde clientseitig entschlüsselt und als Laufzeitwert in
* SonicConnection.Password übernommen.
*/
startInfo.Environment[
@@ -33,40 +33,36 @@ public sealed class SonicSetupRepository
const string sql = """
SELECT
connection.SonicConnectionId,
connection.ConnectionName,
environment.EnvironmentCode,
connection.DomainName,
connection.ManagementHost,
connection.ManagementPort,
connection.ConnectionProtocol,
MIN(container.ContainerName)
AS ValidationContainerName
FROM dbo.SonicConnection AS connection
INNER JOIN dbo.SonicContainer AS container
ON container.SonicConnectionId =
connection.SonicConnectionId
INNER JOIN dbo.EsbService AS service
ON service.EsbServiceId =
container.EsbServiceId
INNER JOIN dbo.Environment AS environment
ON environment.EnvironmentId =
service.EnvironmentId
WHERE connection.IsActive = 1
AND container.IsActive = 1
AND service.IsActive = 1
AND environment.IsActive = 1
AND environment.EnvironmentCode =
connection.[SonicConnectionId],
connection.[ConnectionName],
environment.[EnvironmentCode],
connection.[DomainName],
connection.[ManagementHost],
connection.[ManagementPort],
connection.[ConnectionProtocol],
MIN(container.[ContainerName])
AS [ValidationContainerName]
FROM [dbo].[SonicConnection] AS connection
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] =
connection.[EnvironmentId]
INNER JOIN [dbo].[SonicContainer] AS container
ON container.[SonicConnectionId] =
connection.[SonicConnectionId]
WHERE connection.[IsActive] = 1
AND container.[IsActive] = 1
AND environment.[IsActive] = 1
AND environment.[EnvironmentCode] =
@EnvironmentCode
GROUP BY
connection.SonicConnectionId,
connection.ConnectionName,
environment.EnvironmentCode,
connection.DomainName,
connection.ManagementHost,
connection.ManagementPort,
connection.ConnectionProtocol
ORDER BY connection.ConnectionName;
connection.[SonicConnectionId],
connection.[ConnectionName],
environment.[EnvironmentCode],
connection.[DomainName],
connection.[ManagementHost],
connection.[ManagementPort],
connection.[ConnectionProtocol]
ORDER BY connection.[ConnectionName];
""";
List<SonicSystemOption> systems = [];
@@ -119,11 +115,6 @@ public sealed class SonicSetupRepository
while (await reader.ReadAsync(cancellationToken))
{
if (reader.IsDBNull(managementPortOrdinal))
{
continue;
}
systems.Add(
new SonicSystemOption
{
@@ -248,13 +239,14 @@ public sealed class SonicSetupRepository
if (isDefault)
{
const string clearDefaultSql = """
UPDATE dbo.SonicCredential
UPDATE [dbo].[SonicCredential]
SET
IsDefault = 0,
UpdatedAtUtc = SYSUTCDATETIME()
WHERE SonicConnectionId =
[IsDefault] = 0,
[ModifiedDateTime] = SYSUTCDATETIME(),
[ModifiedBy] = SUSER_SNAME()
WHERE [SonicConnectionId] =
@SonicConnectionId
AND IsDefault = 1;
AND [IsDefault] = 1;
""";
await using SqlCommand clearDefaultCommand =
@@ -276,18 +268,20 @@ public sealed class SonicSetupRepository
}
const string insertSql = """
INSERT INTO dbo.SonicCredential
INSERT INTO [dbo].[SonicCredential]
(
SonicConnectionId,
CredentialName,
CredentialUserName,
CredentialSecret,
IsDefault,
IsActive,
CreatedAtUtc,
UpdatedAtUtc
[SonicConnectionId],
[CredentialName],
[CredentialUserName],
[CredentialSecret],
[IsDefault],
[IsActive],
[CreationDateTime],
[CreatedBy],
[ModifiedDateTime],
[ModifiedBy]
)
OUTPUT INSERTED.SonicCredentialId
OUTPUT INSERTED.[SonicCredentialId]
VALUES
(
@SonicConnectionId,
@@ -297,6 +291,8 @@ public sealed class SonicSetupRepository
@IsDefault,
1,
SYSUTCDATETIME(),
SUSER_SNAME(),
NULL,
NULL
);
""";
@@ -397,19 +393,19 @@ public sealed class SonicSetupRepository
const string sql = """
SELECT
SonicCredentialId,
SonicConnectionId,
CredentialName,
CredentialUserName,
CredentialSecret,
IsDefault,
IsActive
FROM dbo.SonicCredential
WHERE SonicConnectionId = @SonicConnectionId
AND IsActive = 1
[SonicCredentialId],
[SonicConnectionId],
[CredentialName],
[CredentialUserName],
[CredentialSecret],
[IsDefault],
[IsActive]
FROM [dbo].[SonicCredential]
WHERE [SonicConnectionId] = @SonicConnectionId
AND [IsActive] = 1
ORDER BY
IsDefault DESC,
CredentialName;
[IsDefault] DESC,
[CredentialName];
""";
List<SonicCredentialProfile> profiles = [];
@@ -502,4 +498,101 @@ public sealed class SonicSetupRepository
return profiles;
}
}
public async Task<SonicCredentialProfile?> GetCredentialByIdAsync(
int sonicCredentialId,
CancellationToken cancellationToken = default)
{
if (sonicCredentialId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(sonicCredentialId));
}
const string sql = """
SELECT
[SonicCredentialId],
[SonicConnectionId],
[CredentialName],
[CredentialUserName],
[CredentialSecret],
[IsDefault],
[IsActive]
FROM [dbo].[SonicCredential]
WHERE [SonicCredentialId] = @SonicCredentialId
AND [IsActive] = 1;
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@SonicCredentialId",
SqlDbType.Int)
{
Value = sonicCredentialId
});
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken);
if (!await reader.ReadAsync(cancellationToken))
{
return null;
}
int userNameOrdinal =
reader.GetOrdinal("CredentialUserName");
int secretOrdinal =
reader.GetOrdinal("CredentialSecret");
string? userName =
reader.IsDBNull(userNameOrdinal)
? null
: reader.GetString(userNameOrdinal);
string? secret =
reader.IsDBNull(secretOrdinal)
? null
: reader.GetString(secretOrdinal);
if (string.IsNullOrWhiteSpace(userName)
|| string.IsNullOrEmpty(secret))
{
return null;
}
return new SonicCredentialProfile
{
SonicCredentialId =
reader.GetInt32(
reader.GetOrdinal("SonicCredentialId")),
SonicConnectionId =
reader.GetInt32(
reader.GetOrdinal("SonicConnectionId")),
CredentialName =
reader.GetString(
reader.GetOrdinal("CredentialName")),
UserName = userName,
Secret = secret,
IsDefault =
reader.GetBoolean(
reader.GetOrdinal("IsDefault")),
IsActive =
reader.GetBoolean(
reader.GetOrdinal("IsActive"))
};
}
}