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
@@ -0,0 +1,36 @@
/*
DATABASE: [master]
OBJECTNAME: DropExisting
ACTION: DROP
IDX: 001
DATE: 2026-07-29
USER: CUR
COMMENT: Loescht die befuellte Alt-DB (Tippfehler-Name) und die Ziel-DB
vollstaendig inkl. aller Tabellen und Always-Encrypted-Keys
(CMK/CEK). Danach: 01_CreateDatabase -> 02_CreateSchema -> 03_Seed.
Voraussetzung Client: Always-Encrypted-Zertifikat mit Thumbprint
aus appsettings (CurrentUser\My) muss vorhanden bleiben.
ACHTUNG: destruktiv, keine Datenmigration.
*/
USE [master];
GO
/* Alte DB mit Tippfehler im Namen */
IF DB_ID(N'EsbZertifkatManager') IS NOT NULL
BEGIN
ALTER DATABASE [EsbZertifkatManager]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [EsbZertifkatManager];
END;
GO
/* Ziel-DB (falls bereits angelegt / befuellt) */
IF DB_ID(N'EsbZertifikatManager') IS NOT NULL
BEGIN
ALTER DATABASE [EsbZertifikatManager]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [EsbZertifikatManager];
END;
GO
@@ -0,0 +1,31 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Database
ACTION: CREATE
IDX: 002
DATE: 2026-07-29
USER: CUR
COMMENT: Legt die leere Ziel-DB an. Vorher 00_DropExisting.sql ausfuehren
(loescht Alt- und Ziel-DB inkl. Always Encrypted).
Reihenfolge: 00_DropExisting -> 01_CreateDatabase ->
02_CreateSchema -> 03_Seed.
*/
USE [master];
GO
IF DB_ID(N'EsbZertifikatManager') IS NOT NULL
BEGIN
THROW 50001,
N'Datenbank [EsbZertifikatManager] existiert noch. Zuerst 00_DropExisting.sql ausfuehren.',
1;
END;
GO
CREATE DATABASE [EsbZertifikatManager];
GO
ALTER DATABASE [EsbZertifikatManager] SET RECOVERY SIMPLE;
GO
ALTER DATABASE [EsbZertifikatManager] SET READ_WRITE;
GO
@@ -0,0 +1,606 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Schema
ACTION: CREATE
IDX: 002
DATE: 2026-07-29
USER: CUR
COMMENT: 6 Kern-Tabellen + Always Encrypted (CMK/CEK).
Vorher 00_DropExisting + 01_CreateDatabase ausfuehren.
CMK-KEY_PATH = CurrentUser/My/<Thumbprint aus appsettings>.
CEK-ENCRYPTED_VALUE ist an dieses Zertifikat gebunden.
Bei neuem Cert: CEK in SSMS neu erzeugen und ENCRYPTED_VALUE ersetzen.
Connection String muss Column Encryption Setting=Enabled enthalten.
*/
USE [EsbZertifikatManager];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
/* -------------------------------------------------------------------------- */
/* Reste bereinigen: Kind-Tabellen -> AE-Tabellen -> CEK -> CMK */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[CertificateBackup]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateBackup];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRunStep]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRunStep];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRunContainer]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRunContainer];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRun]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRun];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentAction]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentAction];
END;
GO
IF OBJECT_ID(N'[dbo].[TlsEndpoint]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[TlsEndpoint];
END;
GO
IF OBJECT_ID(N'[dbo].[CertificateTarget]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateTarget];
END;
GO
IF OBJECT_ID(N'[dbo].[CertificateArtifact]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateArtifact];
END;
GO
IF OBJECT_ID(N'[dbo].[AppUser]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[AppUser];
END;
GO
IF OBJECT_ID(N'[dbo].[SonicCredential]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicCredential];
END;
GO
IF OBJECT_ID(N'[dbo].[SonicContainer]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicContainer];
END;
GO
/* SonicConnection kann AE-Spalten haben — vor CEK-Drop entfernen */
IF OBJECT_ID(N'[dbo].[SonicConnection]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicConnection];
END;
GO
IF OBJECT_ID(N'[dbo].[EsbService]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[EsbService];
END;
GO
IF OBJECT_ID(N'[dbo].[Country]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Country];
END;
GO
IF OBJECT_ID(N'[dbo].[Environment]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Environment];
END;
GO
IF OBJECT_ID(N'[dbo].[Company]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Company];
END;
GO
IF EXISTS
(
SELECT 1
FROM [sys].[column_encryption_keys] AS cek
WHERE cek.[name] = N'CEK_ESB_ZertifikatManager'
)
BEGIN
DROP COLUMN ENCRYPTION KEY [CEK_ESB_ZertifikatManager];
END;
GO
IF EXISTS
(
SELECT 1
FROM [sys].[column_master_keys] AS cmk
WHERE cmk.[name] = N'CMK_ESB_ZertifikatManager'
)
BEGIN
DROP COLUMN MASTER KEY [CMK_ESB_ZertifikatManager];
END;
GO
/* -------------------------------------------------------------------------- */
/* Always Encrypted neu anlegen */
/* -------------------------------------------------------------------------- */
CREATE COLUMN MASTER KEY [CMK_ESB_ZertifikatManager]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/238AC7A32074D5B293C7EFB60E256907CB82C9C6'
);
GO
CREATE COLUMN ENCRYPTION KEY [CEK_ESB_ZertifikatManager]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK_ESB_ZertifikatManager],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F003200330038006100630037006100330032003000370034006400350062003200390033006300370065006600620036003000650032003500360039003000370063006200380032006300390063003600B8FEA92C9873A6A27B4EB33481926ABF719FB0C2EE7DF959AE38D48185CC7286E7779016745253EDB10C769773D03B614995E2373AE9F02428370727C5DF69B902A01EC0B93E8B8D114460455BAC0C1868DAE1A49AAF04480E8A3D05867395CA455CA9FADEF046437B8C25BA7DE945FCB45A50590DA555B1EB860F23B556B72A142992C085E3D4A97E92D2882D6849C6DABCF37653926A8FBC532A115CB9748179BF34DADDE268FD0DC8FF1CFBCB7192D3220238794D6D2FC7C4BDE501BABE5F6BB989853C6B5C510396069F41338CF64236A6D7A4C26F0DF7BBD2A1A5BD3BA2F7D75FECF7BE0AB0C69DDEEAAD72E2849D0A4F9C9A0C25E021A432EE399AA075AB3F0FB9929392D825E1EC991D017022982C17E72CFA9EEDB17DED9FCECF2B8651B72CA87EEAED5473B3806BD4C0A56539703B4EFF38D2889D255B95C9A6F1AD7D22A1FF2F71621B286FCAD8B708810E7BF50D027BF6A0A829422EE599D00CF2D8E6ADB7DE802971A5805073EC2E6F9793E9AEDE30E7B8EC865D50183EB64B9B8286E4A9BFFB98ACDAA9C9D8D149834DE1CA7FEF9C6A54AF4B30BDDF4AC41C36FB0EFD4EE783CEE1546D25A7CA75C549A2FB978907AFBD209197A9D60239F14F4CFAC202F435AA80DBF6A6494AB3E934F86C640DBFBB77879F2FFA8B1C9DD1BF3B14299C12943F57F1948CC5833986918EB7ACABBC1C4D642E1944BB07AB8446
);
GO
/* -------------------------------------------------------------------------- */
/* Company */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[Company]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Company]
(
[CompanyId] [int] IDENTITY(1, 1) NOT NULL,
[CompanyCode] [nvarchar](30) NOT NULL,
[CompanyName] [nvarchar](200) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_Company] PRIMARY KEY CLUSTERED ([CompanyId] ASC),
CONSTRAINT [uc_Company_CompanyCode] UNIQUE NONCLUSTERED ([CompanyCode] ASC)
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* Environment */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[Environment]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Environment]
(
[EnvironmentId] [int] IDENTITY(1, 1) NOT NULL,
[EnvironmentCode] [nvarchar](30) NOT NULL,
[EnvironmentName] [nvarchar](100) NOT NULL,
[SortOrder] [int] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_Environment] PRIMARY KEY CLUSTERED ([EnvironmentId] ASC),
CONSTRAINT [uc_Environment_EnvironmentCode] UNIQUE NONCLUSTERED ([EnvironmentCode] ASC),
CONSTRAINT [uc_Environment_SortOrder] UNIQUE NONCLUSTERED ([SortOrder] ASC)
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicConnection */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicConnection]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicConnection]
(
[SonicConnectionId] [int] IDENTITY(1, 1) NOT NULL,
[EnvironmentId] [int] NOT NULL,
[ConnectionName] [nvarchar](150) NOT NULL,
[ManagementHost] [nvarchar](255) NOT NULL,
[ManagementPort] [int] NOT NULL,
[DomainName] [nvarchar](150) NOT NULL,
[ConnectionProtocol] [nvarchar](20) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicConnection] PRIMARY KEY CLUSTERED ([SonicConnectionId] ASC),
CONSTRAINT [uc_SonicConnection_ConnectionName] UNIQUE NONCLUSTERED ([ConnectionName] ASC),
CONSTRAINT [fk_SonicConnection_EnvironmentId]
FOREIGN KEY ([EnvironmentId])
REFERENCES [dbo].[Environment] ([EnvironmentId]),
CONSTRAINT [cc_SonicConnection_ManagementPort]
CHECK ([ManagementPort] >= (1) AND [ManagementPort] <= (65535))
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_ConnectionProtocol]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_ConnectionProtocol]
DEFAULT (N'tcp') FOR [ConnectionProtocol];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicConnection]')
AND i.[name] = N'idx_SonicConnection_EnvironmentActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicConnection_EnvironmentActive]
ON [dbo].[SonicConnection]
(
[EnvironmentId] ASC,
[IsActive] ASC
);
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicCredential (Always Encrypted) */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicCredential]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicCredential]
(
[SonicCredentialId] [int] IDENTITY(1, 1) NOT NULL,
[SonicConnectionId] [int] NOT NULL,
[CredentialName] [nvarchar](150) NOT NULL,
[CredentialUserName] [nvarchar](256)
COLLATE Latin1_General_BIN2
ENCRYPTED WITH
(
COLUMN_ENCRYPTION_KEY = [CEK_ESB_ZertifikatManager],
ENCRYPTION_TYPE = Randomized,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'
) NULL,
[CredentialSecret] [nvarchar](512)
COLLATE Latin1_General_BIN2
ENCRYPTED WITH
(
COLUMN_ENCRYPTION_KEY = [CEK_ESB_ZertifikatManager],
ENCRYPTION_TYPE = Randomized,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'
) NULL,
[IsDefault] [bit] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicCredential] PRIMARY KEY CLUSTERED ([SonicCredentialId] ASC),
CONSTRAINT [uc_SonicCredential_CredentialName]
UNIQUE NONCLUSTERED ([SonicConnectionId] ASC, [CredentialName] ASC),
CONSTRAINT [fk_SonicCredential_SonicConnectionId]
FOREIGN KEY ([SonicConnectionId])
REFERENCES [dbo].[SonicConnection] ([SonicConnectionId])
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_IsDefault]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_IsDefault]
DEFAULT ((0)) FOR [IsDefault];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicContainer */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicContainer]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicContainer]
(
[SonicContainerId] [int] IDENTITY(1, 1) NOT NULL,
[CompanyId] [int] NOT NULL,
[SonicConnectionId] [int] NOT NULL,
[ContainerName] [nvarchar](250) NOT NULL,
[ContainerDisplayName] [nvarchar](250) NULL,
[RestartTimeoutSeconds] [int] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicContainer] PRIMARY KEY CLUSTERED ([SonicContainerId] ASC),
CONSTRAINT [uc_SonicContainer_CompanyContainer]
UNIQUE NONCLUSTERED ([CompanyId] ASC, [ContainerName] ASC),
CONSTRAINT [fk_SonicContainer_CompanyId]
FOREIGN KEY ([CompanyId])
REFERENCES [dbo].[Company] ([CompanyId]),
CONSTRAINT [fk_SonicContainer_SonicConnectionId]
FOREIGN KEY ([SonicConnectionId])
REFERENCES [dbo].[SonicConnection] ([SonicConnectionId]),
CONSTRAINT [cc_SonicContainer_RestartTimeoutSeconds]
CHECK ([RestartTimeoutSeconds] >= (10) AND [RestartTimeoutSeconds] <= (3600))
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_RestartTimeoutSeconds]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_RestartTimeoutSeconds]
DEFAULT ((180)) FOR [RestartTimeoutSeconds];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicContainer]')
AND i.[name] = N'idx_SonicContainer_ConnectionActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicContainer_ConnectionActive]
ON [dbo].[SonicContainer]
(
[SonicConnectionId] ASC,
[IsActive] ASC
);
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicContainer]')
AND i.[name] = N'idx_SonicContainer_CompanyActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicContainer_CompanyActive]
ON [dbo].[SonicContainer]
(
[CompanyId] ASC,
[IsActive] ASC
);
END;
GO
/* -------------------------------------------------------------------------- */
/* CertificateTarget */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[CertificateTarget]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[CertificateTarget]
(
[CertificateTargetId] [int] IDENTITY(1, 1) NOT NULL,
[SonicContainerId] [int] NOT NULL,
[TargetDirectory] [nvarchar](500) NOT NULL,
[TargetFileName] [nvarchar](260) NOT NULL,
[BackupEnabled] [bit] NOT NULL,
[BackupDirectoryName] [nvarchar](100) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_CertificateTarget] PRIMARY KEY CLUSTERED ([CertificateTargetId] ASC),
CONSTRAINT [uc_CertificateTarget_Destination]
UNIQUE NONCLUSTERED
(
[SonicContainerId] ASC,
[TargetDirectory] ASC,
[TargetFileName] ASC
),
CONSTRAINT [fk_CertificateTarget_SonicContainerId]
FOREIGN KEY ([SonicContainerId])
REFERENCES [dbo].[SonicContainer] ([SonicContainerId])
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_BackupEnabled]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_BackupEnabled]
DEFAULT ((1)) FOR [BackupEnabled];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_BackupDirectoryName]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_BackupDirectoryName]
DEFAULT (N'Backup') FOR [BackupDirectoryName];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[CertificateTarget]')
AND i.[name] = N'idx_CertificateTarget_ContainerActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_CertificateTarget_ContainerActive]
ON [dbo].[CertificateTarget]
(
[SonicContainerId] ASC,
[IsActive] ASC
);
END;
GO
@@ -0,0 +1,143 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Seed
ACTION: INSERT
IDX: 001
DATE: 2026-07-29
USER: CUR
COMMENT: Stammdaten Company (DE/PL/AT) und Environment (TEST2/TEST/PROD).
Nach 00_DropExisting / 01_CreateDatabase / 02_CreateSchema.
SonicConnection / SonicContainer / CertificateTarget / SonicCredential
manuell pflegen — keine Secrets; Credentials nur via AE/Setup.
*/
USE [EsbZertifikatManager];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'DE'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'DE', N'ZIEHL-ABEGG Deutschland', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'PL'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'PL', N'ZIEHL-ABEGG Polen', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'AT'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'AT', N'ZIEHL-ABEGG Oesterreich', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'TEST2'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'TEST2', N'Test 2', 1, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'TEST'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'TEST', N'Test', 2, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'PROD'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'PROD', N'Production', 3, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
@@ -0,0 +1,36 @@
# Datenbank EsbZertifikatManager
Schlankes Schema (6 Tabellen) für den ESB Certificate Manager.
## Hierarchie
- **Company** — DE / PL / AT
- **Environment** — TEST2 / TEST / PROD (an `SonicConnection`)
- **SonicConnection** — Management-Host/Port/Domain
- **SonicCredential** — Always-Encrypted-Zugangsdaten (einziger Credential-Speicher)
- **SonicContainer** — Container je Company + Connection
- **CertificateTarget** — Deploy-Pfad/Dateiname (+ lokales File-Backup)
## Deployment (bestehende befüllte DB ersetzen)
Alles wird gelöscht — keine Migration.
1. **Always-Encrypted-Zertifikat** prüfen (Thumbprint aus `appsettings.json``AlwaysEncrypted:CertificateThumbprint`) unter `CurrentUser\My` inkl. privatem Schlüssel.
2. Skripte **in dieser Reihenfolge** in SSMS ausführen:
1. `00_DropExisting.sql` — droppt `EsbZertifkatManager` (Alt) und `EsbZertifikatManager` inkl. CMK/CEK
2. `01_CreateDatabase.sql` — leere Ziel-DB
3. `02_CreateSchema.sql` — Always Encrypted (CMK/CEK) + 6 Tabellen
4. `03_Seed.sql` — Company + Environment
3. Connection String: `Database=EsbZertifikatManager` und `Column Encryption Setting=Enabled`.
4. Connections / Container / Targets manuell anlegen; Credentials **nur** über Setup-Wizard (`SonicCredential`, Always Encrypted).
5. App neu starten.
## Always Encrypted
| Objekt | Name |
|---|---|
| Column Master Key | `CMK_ESB_ZertifikatManager` |
| Column Encryption Key | `CEK_ESB_ZertifikatManager` |
| Verschlüsselte Spalten | `SonicCredential.CredentialUserName`, `SonicCredential.CredentialSecret` |
CMK zeigt auf `CurrentUser/My/<Thumbprint>`. Der `ENCRYPTED_VALUE` der CEK gilt nur für dieses Zertifikat — bei neuem Cert CEK in SSMS neu erzeugen und in `02_CreateSchema.sql` ersetzen.
+186 -23
View File
@@ -4,6 +4,7 @@ using System.Security.Cryptography.X509Certificates;
using ZA.CoreService.ESBCertificateManager.Configuration; using ZA.CoreService.ESBCertificateManager.Configuration;
using ZA.CoreService.ESBCertificateManager.Models; using ZA.CoreService.ESBCertificateManager.Models;
using ZA.CoreService.ESBCertificateManager.Services; using ZA.CoreService.ESBCertificateManager.Services;
using ZA.CoreService.ESBCertificateManager.Setup;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
namespace ZA.CoreService.ESBCertificateManager namespace ZA.CoreService.ESBCertificateManager
@@ -34,10 +35,17 @@ namespace ZA.CoreService.ESBCertificateManager
private DeploymentOrchestrator? _orchestrator; private DeploymentOrchestrator? _orchestrator;
private readonly SonicConnectionRepository _sonicConnectionRepository; private readonly SonicConnectionRepository _sonicConnectionRepository;
private readonly DeploymentTargetRepository _deploymentTargetRepository; private readonly DeploymentTargetRepository _deploymentTargetRepository;
private readonly SonicSetupRepository _sonicSetupRepository;
private readonly LocalSetupSelectionStore _localSetupSelectionStore;
private IReadOnlyList<DatabaseSonicConnection> private IReadOnlyList<DatabaseSonicConnection>
_databaseSonicConnections = []; _databaseSonicConnections = [];
private readonly Dictionary<int, SonicCredentialProfile>
_credentialsByConnectionId = new();
private LocalSetupSelection? _localSetupSelection;
private CertificateInfo? _loadedCertificateInfo; private CertificateInfo? _loadedCertificateInfo;
private IReadOnlyList<DeploymentTarget> _loadedTargets = []; private IReadOnlyList<DeploymentTarget> _loadedTargets = [];
private bool _isOperationRunning; private bool _isOperationRunning;
@@ -89,6 +97,11 @@ namespace ZA.CoreService.ESBCertificateManager
_deploymentTargetRepository = new DeploymentTargetRepository( _deploymentTargetRepository = new DeploymentTargetRepository(
_settings.Database.ConnectionString); _settings.Database.ConnectionString);
_sonicSetupRepository = new SonicSetupRepository(
_settings.Database.ConnectionString);
_localSetupSelectionStore = new LocalSetupSelectionStore();
BuildDesign(); BuildDesign();
Shown += async (_, _) => await InitializeApplicationAsync(); Shown += async (_, _) => await InitializeApplicationAsync();
@@ -135,6 +148,52 @@ namespace ZA.CoreService.ESBCertificateManager
dragStartPoint = e.Location; dragStartPoint = e.Location;
} }
} }
private async Task OpenSettingsAsync()
{
if (_isOperationRunning)
{
MessageBox.Show(
this,
"Während einer laufenden Aktion können die Einstellungen nicht geöffnet werden.",
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
try
{
SetupCoordinator coordinator = new(
_settings,
new SonicSetupRepository(
_settings.Database.ConnectionString));
using SetupWizardForm settingsForm = new(
coordinator,
isSettingsMode: true);
if (settingsForm.ShowDialog(this) != DialogResult.OK)
{
return;
}
SetStatus(
"Einstellungen übernommen. Anwendung wird aktualisiert ...",
isError: false);
await InitializeApplicationAsync();
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private async Task InitializeApplicationAsync() private async Task InitializeApplicationAsync()
{ {
@@ -163,16 +222,32 @@ namespace ZA.CoreService.ESBCertificateManager
} }
try try
{ {
_databaseSonicConnections = _localSetupSelection =
await _sonicConnectionRepository.GetActiveAsync(); _localSetupSelectionStore.Load();
List<SonicConnection> runtimeConnections = _databaseSonicConnections =
_databaseSonicConnections await _sonicConnectionRepository.GetActiveAsync(
.Where(connection => connection.HasCredentials) _settings.EnvironmentCode);
.Select(connection =>
await LoadCredentialsForConnectionsAsync();
List<SonicConnection> runtimeConnections = [];
foreach (DatabaseSonicConnection connection
in _databaseSonicConnections)
{
if (!_credentialsByConnectionId.TryGetValue(
connection.Id,
out SonicCredentialProfile? credential))
{
continue;
}
runtimeConnections.Add(
connection.ToRuntimeConnection( connection.ToRuntimeConnection(
_settings.Runtime)) _settings.Runtime,
.ToList(); credential));
}
_orchestrator = new DeploymentOrchestrator( _orchestrator = new DeploymentOrchestrator(
_settings, _settings,
@@ -195,12 +270,13 @@ namespace ZA.CoreService.ESBCertificateManager
SetStatus( SetStatus(
$"{_databaseSonicConnections.Count} aktive Sonic-Verbindung(en) " + $"{_databaseSonicConnections.Count} aktive Sonic-Verbindung(en) " +
$"aus SQL und {_loadedTargets.Count} lokale Testziele geladen.", $"aus SQL und {_loadedTargets.Count} Bereitstellungsziel(e) geladen.",
isError: false); isError: false);
} }
catch (SqlException ex) catch (SqlException ex)
{ {
_databaseSonicConnections = []; _databaseSonicConnections = [];
_credentialsByConnectionId.Clear();
SetStatus( SetStatus(
$"SQL-Zugriff fehlgeschlagen: {ex.Message}", $"SQL-Zugriff fehlgeschlagen: {ex.Message}",
@@ -485,12 +561,12 @@ namespace ZA.CoreService.ESBCertificateManager
progressPanel.Controls.Add(progressTitle); progressPanel.Controls.Add(progressTitle);
// ----------------------------- // -----------------------------
// 3. Footer unten: Version // 3. Footer unten: Einstellungen + Version
// ----------------------------- // -----------------------------
Panel footerPanel = new Panel Panel footerPanel = new Panel
{ {
Dock = DockStyle.Bottom, Dock = DockStyle.Bottom,
Height = 70, Height = 118,
Padding = new Padding(18, 8, 12, 8) Padding = new Padding(18, 8, 12, 8)
}; };
@@ -501,13 +577,36 @@ namespace ZA.CoreService.ESBCertificateManager
BackColor = BorderColor BackColor = BorderColor
}; };
Button btnSettings = new Button
{
Text = " Einstellungen",
Location = new Point(14, 14),
Size = new Size(176, 36),
FlatStyle = FlatStyle.Flat,
FlatAppearance =
{
BorderSize = 1,
BorderColor = BorderColor,
MouseOverBackColor = Color.FromArgb(37, 52, 74)
},
BackColor = SidebarColor,
ForeColor = TextColor,
Font = new Font("Segoe UI", 9, FontStyle.Bold),
TextAlign = ContentAlignment.MiddleLeft,
Cursor = Cursors.Hand,
Enabled = !_isOperationRunning
};
btnSettings.Click += async (_, _) =>
await OpenSettingsAsync();
Label versionLabel = new Label Label versionLabel = new Label
{ {
Text = "INTERNAL TOOL", Text = "INTERNAL TOOL",
ForeColor = MutedTextColor, ForeColor = MutedTextColor,
Font = new Font("Segoe UI", 7.5f, FontStyle.Bold), Font = new Font("Segoe UI", 7.5f, FontStyle.Bold),
AutoSize = true, AutoSize = true,
Location = new Point(18, 17) Location = new Point(18, 62)
}; };
Label versionNumber = new Label Label versionNumber = new Label
@@ -516,10 +615,11 @@ namespace ZA.CoreService.ESBCertificateManager
ForeColor = BlueColor, ForeColor = BlueColor,
Font = new Font("Segoe UI", 8), Font = new Font("Segoe UI", 8),
AutoSize = true, AutoSize = true,
Location = new Point(18, 37) Location = new Point(18, 82)
}; };
footerPanel.Controls.Add(footerSeparator); footerPanel.Controls.Add(footerSeparator);
footerPanel.Controls.Add(btnSettings);
footerPanel.Controls.Add(versionLabel); footerPanel.Controls.Add(versionLabel);
footerPanel.Controls.Add(versionNumber); footerPanel.Controls.Add(versionNumber);
@@ -1428,7 +1528,8 @@ namespace ZA.CoreService.ESBCertificateManager
isError: false); isError: false);
_loadedTargets = _loadedTargets =
await _deploymentTargetRepository.GetActiveAsync(); await _deploymentTargetRepository.GetActiveAsync(
_settings.EnvironmentCode);
BindTargetsToGrid(_loadedTargets); BindTargetsToGrid(_loadedTargets);
@@ -1943,6 +2044,42 @@ namespace ZA.CoreService.ESBCertificateManager
// --------------------------------------------------------------- // ---------------------------------------------------------------
// Sonic Status (KnownContainers aus appsettings) // Sonic Status (KnownContainers aus appsettings)
// --------------------------------------------------------------- // ---------------------------------------------------------------
private async Task LoadCredentialsForConnectionsAsync()
{
_credentialsByConnectionId.Clear();
foreach (DatabaseSonicConnection connection
in _databaseSonicConnections)
{
IReadOnlyList<SonicCredentialProfile> profiles =
await _sonicSetupRepository.GetCredentialsAsync(
connection.Id);
SonicCredentialProfile? selected = null;
if (_localSetupSelection is not null
&& _localSetupSelection.SonicConnectionId
== connection.Id
&& _localSetupSelection.SonicCredentialId > 0)
{
selected = profiles.FirstOrDefault(
profile =>
profile.SonicCredentialId
== _localSetupSelection.SonicCredentialId);
}
selected ??=
profiles.FirstOrDefault(profile => profile.IsDefault)
?? profiles.FirstOrDefault();
if (selected is not null && selected.IsComplete)
{
_credentialsByConnectionId[connection.Id] =
selected;
}
}
}
private void BindDatabaseSonicConnections() private void BindDatabaseSonicConnections()
{ {
cmbSonicConnection.Items.Clear(); cmbSonicConnection.Items.Clear();
@@ -1953,13 +2090,34 @@ namespace ZA.CoreService.ESBCertificateManager
cmbSonicConnection.Items.Add(connection.Name); cmbSonicConnection.Items.Add(connection.Name);
} }
if (cmbSonicConnection.Items.Count > 0) if (cmbSonicConnection.Items.Count == 0)
{ {
cmbSonicConnection.SelectedIndex = 0; RefreshSonicStatusLine();
return; return;
} }
RefreshSonicStatusLine(); int preferredIndex = 0;
if (_localSetupSelection is not null
&& !string.IsNullOrWhiteSpace(
_localSetupSelection.ConnectionName))
{
for (int index = 0;
index < cmbSonicConnection.Items.Count;
index++)
{
if (string.Equals(
cmbSonicConnection.Items[index]?.ToString(),
_localSetupSelection.ConnectionName,
StringComparison.OrdinalIgnoreCase))
{
preferredIndex = index;
break;
}
}
}
cmbSonicConnection.SelectedIndex = preferredIndex;
} }
private void BuildSonicDiscoveryRow(Panel card) private void BuildSonicDiscoveryRow(Panel card)
{ {
@@ -2036,14 +2194,19 @@ namespace ZA.CoreService.ESBCertificateManager
{ {
string connectionUrl = connection.BuildConnectionUrl(); string connectionUrl = connection.BuildConnectionUrl();
bool hasCredentialReference = bool hasCredential =
!string.IsNullOrWhiteSpace(connection.CredentialReference); _credentialsByConnectionId.TryGetValue(
connection.Id,
out SonicCredentialProfile? credential);
lblSonicStatus.Text = hasCredentialReference string credentialInfo = hasCredential
? $"{connection.Name}: {connectionUrl}, Credential-Referenz vorhanden" ? $"Profil '{credential!.CredentialName}'"
: $"{connection.Name}: {connectionUrl}, sichere Zugangsdaten noch nicht konfiguriert"; : "sichere Zugangsdaten noch nicht konfiguriert";
lblSonicStatus.ForeColor = hasCredentialReference lblSonicStatus.Text =
$"{connection.Name}: {connectionUrl}, {credentialInfo}";
lblSonicStatus.ForeColor = hasCredential
? GreenColor ? GreenColor
: GoldColor; : GoldColor;
} }
@@ -4,8 +4,7 @@ namespace ZA.CoreService.ESBCertificateManager.Models;
/// <summary> /// <summary>
/// Aus dbo.SonicConnection geladene Verbindungsdaten. /// Aus dbo.SonicConnection geladene Verbindungsdaten.
/// CredentialSecret wird durch Microsoft.Data.SqlClient /// Zugangsdaten liegen ausschließlich in dbo.SonicCredential.
/// clientseitig entschlüsselt.
/// </summary> /// </summary>
public sealed class DatabaseSonicConnection public sealed class DatabaseSonicConnection
{ {
@@ -15,34 +14,16 @@ public sealed class DatabaseSonicConnection
public required string ManagementHost { get; init; } public required string ManagementHost { get; init; }
public int? ManagementPort { get; init; } public int ManagementPort { get; init; }
public string? ConnectionProtocol { get; init; } public string? ConnectionProtocol { get; init; }
public string? DomainName { get; init; } public string? DomainName { get; init; }
public string? CredentialUserName { get; init; } public string? EnvironmentCode { get; init; }
public string? CredentialReference { get; init; }
/// <summary>
/// Nur zur Laufzeit verfügbar.
/// Darf niemals protokolliert oder angezeigt werden.
/// </summary>
public string? CredentialSecret { get; init; }
public string? JavaHomePath { get; init; }
public string? SonicHomePath { get; init; }
public string? Notes { get; init; }
public bool IsActive { get; init; } public bool IsActive { get; init; }
public bool HasCredentials =>
!string.IsNullOrWhiteSpace(CredentialUserName)
&& !string.IsNullOrEmpty(CredentialSecret);
public string BuildConnectionUrl() public string BuildConnectionUrl()
{ {
if (string.IsNullOrWhiteSpace(ConnectionProtocol)) if (string.IsNullOrWhiteSpace(ConnectionProtocol))
@@ -57,7 +38,7 @@ public sealed class DatabaseSonicConnection
$"Bei der Sonic-Verbindung '{Name}' fehlt ManagementHost."); $"Bei der Sonic-Verbindung '{Name}' fehlt ManagementHost.");
} }
if (ManagementPort is null or < 1 or > 65535) if (ManagementPort is < 1 or > 65535)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt ein gültiger ManagementPort."); $"Bei der Sonic-Verbindung '{Name}' fehlt ein gültiger ManagementPort.");
@@ -69,29 +50,26 @@ public sealed class DatabaseSonicConnection
string host = ManagementHost.Trim(); string host = ManagementHost.Trim();
return $"{protocol}://{host}:{ManagementPort.Value}"; return $"{protocol}://{host}:{ManagementPort}";
} }
public SonicConnection ToRuntimeConnection( public SonicConnection ToRuntimeConnection(
RuntimeSettings runtimeSettings) RuntimeSettings runtimeSettings,
SonicCredentialProfile credential)
{ {
ArgumentNullException.ThrowIfNull(credential);
if (string.IsNullOrWhiteSpace(DomainName)) if (string.IsNullOrWhiteSpace(DomainName))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt DomainName."); $"Bei der Sonic-Verbindung '{Name}' fehlt DomainName.");
} }
if (string.IsNullOrWhiteSpace(CredentialUserName)) if (!credential.IsComplete)
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt der Benutzername.");
}
if (string.IsNullOrEmpty(CredentialSecret))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlen die Zugangsdaten. " + $"Bei der Sonic-Verbindung '{Name}' fehlen die Zugangsdaten. " +
"Bitte das Setup ausführen."); "Bitte die Einstellungen öffnen und ein Benutzerprofil hinterlegen.");
} }
string javaExecutablePath = string javaExecutablePath =
@@ -119,10 +97,10 @@ public sealed class DatabaseSonicConnection
Name = Name, Name = Name,
DomainName = DomainName, DomainName = DomainName,
ConnectionUrl = BuildConnectionUrl(), ConnectionUrl = BuildConnectionUrl(),
Username = CredentialUserName, Username = credential.UserName,
// Nur Laufzeitwert, bereits clientseitig entschlüsselt. // Nur Laufzeitwert, bereits clientseitig entschlüsselt.
Password = CredentialSecret, Password = credential.Secret,
JavaPath = javaExecutablePath, JavaPath = javaExecutablePath,
JavaHome = javaHome, JavaHome = javaHome,
@@ -34,10 +34,12 @@ internal static class Program
.GetAwaiter() .GetAwaiter()
.GetResult(); .GetResult();
if (setupResult.IsReady) if (!setupResult.IsReady)
{ {
using SetupWizardForm setupForm = using SetupWizardForm setupForm =
new(coordinator); new(
coordinator,
isSettingsMode: false);
DialogResult setupDialogResult = DialogResult setupDialogResult =
setupForm.ShowDialog(); setupForm.ShowDialog();
@@ -57,7 +59,7 @@ internal static class Program
{ {
MessageBox.Show( MessageBox.Show(
"Die Einrichtung ist noch nicht vollständig.", "Die Einrichtung ist noch nicht vollständig.",
"Setup unvollständig", "Einstellungen unvollständig",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Warning); MessageBoxIcon.Warning);
@@ -1,4 +1,5 @@
using Microsoft.Data.SqlClient; using System.Data;
using Microsoft.Data.SqlClient;
using ZA.CoreService.ESBCertificateManager.Models; using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services; namespace ZA.CoreService.ESBCertificateManager.Services;
@@ -20,65 +21,57 @@ public sealed class DeploymentTargetRepository
} }
public async Task<IReadOnlyList<DeploymentTarget>> GetActiveAsync( public async Task<IReadOnlyList<DeploymentTarget>> GetActiveAsync(
string environmentCode,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
if (string.IsNullOrWhiteSpace(environmentCode))
{
throw new ArgumentException(
"Der EnvironmentCode fehlt.",
nameof(environmentCode));
}
const string sql = """ const string sql = """
SELECT SELECT
target.CertificateTargetId, target.[CertificateTargetId],
target.TargetDirectory, target.[TargetDirectory],
target.TargetFileName, target.[TargetFileName],
target.BackupEnabled, target.[BackupEnabled],
target.BackupDirectoryName, target.[BackupDirectoryName],
target.BackupRetentionDays, target.[IsActive] AS [TargetIsActive],
target.IsActive AS TargetIsActive,
container.SonicContainerId, container.[SonicContainerId],
container.ContainerName, container.[ContainerName],
container.ContainerDisplayName, container.[ContainerDisplayName],
container.RestartTimeoutSeconds, container.[RestartTimeoutSeconds],
connection.ConnectionName, connection.[ConnectionName],
service.ServiceCode, environment.[EnvironmentCode],
service.ServiceName, environment.[SortOrder] AS [EnvironmentSortOrder],
environment.EnvironmentCode, company.[CompanyCode]
environment.SortOrder AS EnvironmentSortOrder, FROM [dbo].[CertificateTarget] AS target
INNER JOIN [dbo].[SonicContainer] AS container
company.CompanyCode, ON container.[SonicContainerId] = target.[SonicContainerId]
country.CountryCode INNER JOIN [dbo].[SonicConnection] AS connection
FROM dbo.CertificateTarget AS target ON connection.[SonicConnectionId] = container.[SonicConnectionId]
INNER JOIN dbo.SonicContainer AS container INNER JOIN [dbo].[Environment] AS environment
ON container.SonicContainerId = target.SonicContainerId ON environment.[EnvironmentId] = connection.[EnvironmentId]
INNER JOIN dbo.SonicConnection AS connection INNER JOIN [dbo].[Company] AS company
ON connection.SonicConnectionId = container.SonicConnectionId ON company.[CompanyId] = container.[CompanyId]
INNER JOIN dbo.EsbService AS service WHERE target.[IsActive] = 1
ON service.EsbServiceId = container.EsbServiceId AND container.[IsActive] = 1
INNER JOIN dbo.Company AS company AND connection.[IsActive] = 1
ON company.CompanyId = service.CompanyId AND company.[IsActive] = 1
LEFT JOIN dbo.Country AS country AND environment.[IsActive] = 1
ON country.CountryId = service.CountryId AND environment.[EnvironmentCode] = @EnvironmentCode
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
)
ORDER BY ORDER BY
company.CompanyCode, company.[CompanyCode],
country.CountryCode, environment.[SortOrder],
environment.SortOrder, container.[ContainerName],
service.ServiceCode, target.[TargetDirectory],
container.ContainerName, target.[TargetFileName];
target.TargetDirectory,
target.TargetFileName;
"""; """;
List<DeploymentTarget> targets = []; List<DeploymentTarget> targets = [];
@@ -88,6 +81,17 @@ public sealed class DeploymentTargetRepository
await using SqlCommand command = new(sql, connection); 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 using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken); await command.ExecuteReaderAsync(cancellationToken);
@@ -106,9 +110,6 @@ public sealed class DeploymentTargetRepository
int backupDirectoryNameOrdinal = int backupDirectoryNameOrdinal =
reader.GetOrdinal("BackupDirectoryName"); reader.GetOrdinal("BackupDirectoryName");
int backupRetentionDaysOrdinal =
reader.GetOrdinal("BackupRetentionDays");
int targetIsActiveOrdinal = int targetIsActiveOrdinal =
reader.GetOrdinal("TargetIsActive"); reader.GetOrdinal("TargetIsActive");
@@ -124,12 +125,6 @@ public sealed class DeploymentTargetRepository
int connectionNameOrdinal = int connectionNameOrdinal =
reader.GetOrdinal("ConnectionName"); reader.GetOrdinal("ConnectionName");
int serviceCodeOrdinal =
reader.GetOrdinal("ServiceCode");
int serviceNameOrdinal =
reader.GetOrdinal("ServiceName");
int environmentCodeOrdinal = int environmentCodeOrdinal =
reader.GetOrdinal("EnvironmentCode"); reader.GetOrdinal("EnvironmentCode");
@@ -139,9 +134,6 @@ public sealed class DeploymentTargetRepository
int companyCodeOrdinal = int companyCodeOrdinal =
reader.GetOrdinal("CompanyCode"); reader.GetOrdinal("CompanyCode");
int countryCodeOrdinal =
reader.GetOrdinal("CountryCode");
while (await reader.ReadAsync(cancellationToken)) while (await reader.ReadAsync(cancellationToken))
{ {
string containerName = string containerName =
@@ -152,34 +144,19 @@ public sealed class DeploymentTargetRepository
reader, reader,
containerDisplayNameOrdinal); containerDisplayNameOrdinal);
string serviceCode =
reader.GetString(serviceCodeOrdinal);
string serviceName =
reader.GetString(serviceNameOrdinal);
string companyCode = string companyCode =
reader.GetString(companyCodeOrdinal); reader.GetString(companyCodeOrdinal);
string? countryCode =
ReadNullableString(reader, countryCodeOrdinal);
string displayName = string displayName =
string.IsNullOrWhiteSpace(containerDisplayName) string.IsNullOrWhiteSpace(containerDisplayName)
? containerName ? containerName
: containerDisplayName; : containerDisplayName;
string locationCode =
string.IsNullOrWhiteSpace(countryCode)
? companyCode
: $"{companyCode}/{countryCode}";
targets.Add(new DeploymentTarget targets.Add(new DeploymentTarget
{ {
Id = reader.GetInt32(targetIdOrdinal), Id = reader.GetInt32(targetIdOrdinal),
Name = Name = $"{companyCode} / {displayName}",
$"{locationCode} / {serviceName} / {displayName}",
Environment = Environment =
reader.GetString(environmentCodeOrdinal), reader.GetString(environmentCodeOrdinal),
@@ -199,10 +176,7 @@ public sealed class DeploymentTargetRepository
BackupDirectoryName = BackupDirectoryName =
reader.GetString(backupDirectoryNameOrdinal), reader.GetString(backupDirectoryNameOrdinal),
BackupRetentionDays = BackupRetentionDays = null,
ReadNullableInt32(
reader,
backupRetentionDaysOrdinal),
ContainerName = containerName, ContainerName = containerName,
@@ -238,13 +212,4 @@ public sealed class DeploymentTargetRepository
? null ? null
: reader.GetString(ordinal); : 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 catch
{ {
// Eine beschädigte lokale Auswahl darf den Start // Eine beschädigte lokale Auswahl darf den Start
// nicht verhindern. Das Setup wird erneut angezeigt. // nicht verhindern. Die Einstellungen werden erneut angezeigt.
return null; return null;
} }
} }
@@ -60,7 +60,7 @@ public sealed class LocalSetupSelectionStore
if (string.IsNullOrWhiteSpace(directory)) if (string.IsNullOrWhiteSpace(directory))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"Der lokale Setup-Ordner konnte nicht bestimmt werden."); "Der lokale Einstellungen-Ordner konnte nicht bestimmt werden.");
} }
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
@@ -23,26 +23,31 @@ public sealed class SonicConnectionRepository
public async Task<IReadOnlyList<DatabaseSonicConnection>> public async Task<IReadOnlyList<DatabaseSonicConnection>>
GetActiveAsync( GetActiveAsync(
string? environmentCode = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
const string sql = """ const string sql = """
SELECT SELECT
SonicConnectionId, connection.[SonicConnectionId],
ConnectionName, connection.[ConnectionName],
ManagementHost, connection.[ManagementHost],
ManagementPort, connection.[ManagementPort],
ConnectionProtocol, connection.[ConnectionProtocol],
DomainName, connection.[DomainName],
CredentialUserName, environment.[EnvironmentCode],
CredentialReference, connection.[IsActive]
CredentialSecret, FROM [dbo].[SonicConnection] AS connection
JavaHomePath, INNER JOIN [dbo].[Environment] AS environment
SonicHomePath, ON environment.[EnvironmentId] =
Notes, connection.[EnvironmentId]
IsActive WHERE connection.[IsActive] = 1
FROM dbo.SonicConnection AND environment.[IsActive] = 1
WHERE IsActive = 1 AND
ORDER BY ConnectionName; (
@EnvironmentCode IS NULL
OR environment.[EnvironmentCode] = @EnvironmentCode
)
ORDER BY connection.[ConnectionName];
"""; """;
List<DatabaseSonicConnection> connections = []; List<DatabaseSonicConnection> connections = [];
@@ -55,6 +60,22 @@ public sealed class SonicConnectionRepository
await using SqlCommand command = await using SqlCommand command =
new(sql, connection); 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 using SqlDataReader reader =
await command.ExecuteReaderAsync( await command.ExecuteReaderAsync(
cancellationToken); cancellationToken);
@@ -77,23 +98,8 @@ public sealed class SonicConnectionRepository
int domainOrdinal = int domainOrdinal =
reader.GetOrdinal("DomainName"); reader.GetOrdinal("DomainName");
int userNameOrdinal = int environmentCodeOrdinal =
reader.GetOrdinal("CredentialUserName"); reader.GetOrdinal("EnvironmentCode");
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 isActiveOrdinal = int isActiveOrdinal =
reader.GetOrdinal("IsActive"); reader.GetOrdinal("IsActive");
@@ -112,9 +118,7 @@ public sealed class SonicConnectionRepository
reader.GetString(hostOrdinal), reader.GetString(hostOrdinal),
ManagementPort = ManagementPort =
ReadNullableInt32( reader.GetInt32(portOrdinal),
reader,
portOrdinal),
ConnectionProtocol = ConnectionProtocol =
ReadNullableString( ReadNullableString(
@@ -126,35 +130,10 @@ public sealed class SonicConnectionRepository
reader, reader,
domainOrdinal), domainOrdinal),
CredentialUserName = EnvironmentCode =
ReadNullableString( ReadNullableString(
reader, reader,
userNameOrdinal), environmentCodeOrdinal),
CredentialReference =
ReadNullableString(
reader,
credentialReferenceOrdinal),
CredentialSecret =
ReadNullableString(
reader,
credentialSecretOrdinal),
JavaHomePath =
ReadNullableString(
reader,
javaHomeOrdinal),
SonicHomePath =
ReadNullableString(
reader,
sonicHomeOrdinal),
Notes =
ReadNullableString(
reader,
notesOrdinal),
IsActive = IsActive =
reader.GetBoolean( reader.GetBoolean(
@@ -165,154 +144,6 @@ public sealed class SonicConnectionRepository
return connections; 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( private static string? ReadNullableString(
SqlDataReader reader, SqlDataReader reader,
int ordinal) int ordinal)
@@ -321,13 +152,4 @@ public sealed class SonicConnectionRepository
? null ? null
: reader.GetString(ordinal); : 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, null,
"Für die Sonic-Verbindung sind keine " + "Für die Sonic-Verbindung sind keine " +
"vollständigen Zugangsdaten eingerichtet. " + "vollständigen Zugangsdaten eingerichtet. " +
"Bitte das Setup ausführen."); "Bitte die Einstellungen öffnen und Zugangsdaten hinterlegen.");
} }
if (string.IsNullOrWhiteSpace( if (string.IsNullOrWhiteSpace(
@@ -145,8 +145,8 @@ public sealed class SonicMfApiExecutor
* übergeben. Dadurch erscheint das Kennwort nicht in * übergeben. Dadurch erscheint das Kennwort nicht in
* der Prozessargumentliste. * der Prozessargumentliste.
* *
* CredentialSecret wurde zuvor durch Microsoft.Data.SqlClient * Das Kennwort stammt aus dbo.SonicCredential (Always Encrypted),
* clientseitig entschlüsselt und als Laufzeitwert in * wurde clientseitig entschlüsselt und als Laufzeitwert in
* SonicConnection.Password übernommen. * SonicConnection.Password übernommen.
*/ */
startInfo.Environment[ startInfo.Environment[
@@ -33,40 +33,36 @@ public sealed class SonicSetupRepository
const string sql = """ const string sql = """
SELECT SELECT
connection.SonicConnectionId, connection.[SonicConnectionId],
connection.ConnectionName, connection.[ConnectionName],
environment.EnvironmentCode, environment.[EnvironmentCode],
connection.DomainName, connection.[DomainName],
connection.ManagementHost, connection.[ManagementHost],
connection.ManagementPort, connection.[ManagementPort],
connection.ConnectionProtocol, connection.[ConnectionProtocol],
MIN(container.ContainerName) MIN(container.[ContainerName])
AS ValidationContainerName AS [ValidationContainerName]
FROM dbo.SonicConnection AS connection FROM [dbo].[SonicConnection] AS connection
INNER JOIN dbo.SonicContainer AS container INNER JOIN [dbo].[Environment] AS environment
ON container.SonicConnectionId = ON environment.[EnvironmentId] =
connection.SonicConnectionId connection.[EnvironmentId]
INNER JOIN dbo.EsbService AS service INNER JOIN [dbo].[SonicContainer] AS container
ON service.EsbServiceId = ON container.[SonicConnectionId] =
container.EsbServiceId connection.[SonicConnectionId]
INNER JOIN dbo.Environment AS environment WHERE connection.[IsActive] = 1
ON environment.EnvironmentId = AND container.[IsActive] = 1
service.EnvironmentId AND environment.[IsActive] = 1
WHERE connection.IsActive = 1 AND environment.[EnvironmentCode] =
AND container.IsActive = 1
AND service.IsActive = 1
AND environment.IsActive = 1
AND environment.EnvironmentCode =
@EnvironmentCode @EnvironmentCode
GROUP BY GROUP BY
connection.SonicConnectionId, connection.[SonicConnectionId],
connection.ConnectionName, connection.[ConnectionName],
environment.EnvironmentCode, environment.[EnvironmentCode],
connection.DomainName, connection.[DomainName],
connection.ManagementHost, connection.[ManagementHost],
connection.ManagementPort, connection.[ManagementPort],
connection.ConnectionProtocol connection.[ConnectionProtocol]
ORDER BY connection.ConnectionName; ORDER BY connection.[ConnectionName];
"""; """;
List<SonicSystemOption> systems = []; List<SonicSystemOption> systems = [];
@@ -119,11 +115,6 @@ public sealed class SonicSetupRepository
while (await reader.ReadAsync(cancellationToken)) while (await reader.ReadAsync(cancellationToken))
{ {
if (reader.IsDBNull(managementPortOrdinal))
{
continue;
}
systems.Add( systems.Add(
new SonicSystemOption new SonicSystemOption
{ {
@@ -248,13 +239,14 @@ public sealed class SonicSetupRepository
if (isDefault) if (isDefault)
{ {
const string clearDefaultSql = """ const string clearDefaultSql = """
UPDATE dbo.SonicCredential UPDATE [dbo].[SonicCredential]
SET SET
IsDefault = 0, [IsDefault] = 0,
UpdatedAtUtc = SYSUTCDATETIME() [ModifiedDateTime] = SYSUTCDATETIME(),
WHERE SonicConnectionId = [ModifiedBy] = SUSER_SNAME()
WHERE [SonicConnectionId] =
@SonicConnectionId @SonicConnectionId
AND IsDefault = 1; AND [IsDefault] = 1;
"""; """;
await using SqlCommand clearDefaultCommand = await using SqlCommand clearDefaultCommand =
@@ -276,18 +268,20 @@ public sealed class SonicSetupRepository
} }
const string insertSql = """ const string insertSql = """
INSERT INTO dbo.SonicCredential INSERT INTO [dbo].[SonicCredential]
( (
SonicConnectionId, [SonicConnectionId],
CredentialName, [CredentialName],
CredentialUserName, [CredentialUserName],
CredentialSecret, [CredentialSecret],
IsDefault, [IsDefault],
IsActive, [IsActive],
CreatedAtUtc, [CreationDateTime],
UpdatedAtUtc [CreatedBy],
[ModifiedDateTime],
[ModifiedBy]
) )
OUTPUT INSERTED.SonicCredentialId OUTPUT INSERTED.[SonicCredentialId]
VALUES VALUES
( (
@SonicConnectionId, @SonicConnectionId,
@@ -297,6 +291,8 @@ public sealed class SonicSetupRepository
@IsDefault, @IsDefault,
1, 1,
SYSUTCDATETIME(), SYSUTCDATETIME(),
SUSER_SNAME(),
NULL,
NULL NULL
); );
"""; """;
@@ -397,19 +393,19 @@ public sealed class SonicSetupRepository
const string sql = """ const string sql = """
SELECT SELECT
SonicCredentialId, [SonicCredentialId],
SonicConnectionId, [SonicConnectionId],
CredentialName, [CredentialName],
CredentialUserName, [CredentialUserName],
CredentialSecret, [CredentialSecret],
IsDefault, [IsDefault],
IsActive [IsActive]
FROM dbo.SonicCredential FROM [dbo].[SonicCredential]
WHERE SonicConnectionId = @SonicConnectionId WHERE [SonicConnectionId] = @SonicConnectionId
AND IsActive = 1 AND [IsActive] = 1
ORDER BY ORDER BY
IsDefault DESC, [IsDefault] DESC,
CredentialName; [CredentialName];
"""; """;
List<SonicCredentialProfile> profiles = []; List<SonicCredentialProfile> profiles = [];
@@ -502,4 +498,101 @@ public sealed class SonicSetupRepository
return profiles; 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"))
};
}
} }
@@ -5,6 +5,7 @@ namespace ZA.CoreService.ESBCertificateManager.Setup;
public sealed class SetupWizardForm : Form public sealed class SetupWizardForm : Form
{ {
private readonly SetupCoordinator _coordinator; private readonly SetupCoordinator _coordinator;
private readonly bool _isSettingsMode;
private readonly Color _backgroundColor = private readonly Color _backgroundColor =
Color.FromArgb(10, 18, 34); Color.FromArgb(10, 18, 34);
@@ -49,9 +50,11 @@ public sealed class SetupWizardForm : Form
private bool _running; private bool _running;
public SetupWizardForm( public SetupWizardForm(
SetupCoordinator coordinator) SetupCoordinator coordinator,
bool isSettingsMode = false)
{ {
_coordinator = coordinator; _coordinator = coordinator;
_isSettingsMode = isSettingsMode;
BuildInterface(); BuildInterface();
@@ -61,7 +64,7 @@ public sealed class SetupWizardForm : Form
private void BuildInterface() private void BuildInterface()
{ {
Text = "ESB Certificate Manager einrichten"; Text = "Einstellungen";
StartPosition = FormStartPosition.CenterScreen; StartPosition = FormStartPosition.CenterScreen;
Size = new Size(950, 690); Size = new Size(950, 690);
MinimumSize = new Size(900, 650); MinimumSize = new Size(900, 650);
@@ -73,7 +76,7 @@ public sealed class SetupWizardForm : Form
Label title = new() Label title = new()
{ {
Text = "Ersteinrichtung", Text = "Einstellungen",
AutoSize = true, AutoSize = true,
Location = new Point(38, 25), Location = new Point(38, 25),
ForeColor = _textColor, ForeColor = _textColor,
@@ -85,8 +88,9 @@ public sealed class SetupWizardForm : Form
Label subtitle = new() Label subtitle = new()
{ {
Text = Text = _isSettingsMode
"Runtime, Datenbank, Zertifikat und Systemauswahl prüfen.", ? "Runtime, Zertifikat, System und Benutzerprofil anpassen."
: "Runtime, Datenbank, Zertifikat und Systemauswahl prüfen.",
AutoSize = true, AutoSize = true,
Location = new Point(42, 70), Location = new Point(42, 70),
ForeColor = _mutedTextColor ForeColor = _mutedTextColor
@@ -244,7 +248,7 @@ public sealed class SetupWizardForm : Form
await InitializeAsync(); await InitializeAsync();
Button cancelButton = CreateButton( Button cancelButton = CreateButton(
"Abbrechen"); _isSettingsMode ? "Schließen" : "Abbrechen");
cancelButton.Location = cancelButton.Location =
new Point(675, 580); new Point(675, 580);
@@ -252,14 +256,30 @@ public sealed class SetupWizardForm : Form
cancelButton.Size = cancelButton.Size =
new Size(110, 42); new Size(110, 42);
cancelButton.Click += (_, _) => cancelButton.Click += async (_, _) =>
{
if (_isSettingsMode)
{
// Nach gespeicherter Auswahl die Hauptansicht aktualisieren.
SetupCheckResult check =
await _coordinator.CheckAsync();
DialogResult = check.IsReady
? DialogResult.OK
: DialogResult.Cancel;
}
else
{ {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
}
Close(); Close();
}; };
_btnFinish = CreateButton( _btnFinish = CreateButton(
"Setup abschließen"); _isSettingsMode
? "Übernehmen"
: "Fertig");
_btnFinish.Location = _btnFinish.Location =
new Point(795, 580); new Point(795, 580);
@@ -338,7 +358,7 @@ public sealed class SetupWizardForm : Form
MessageBox.Show( MessageBox.Show(
this, this,
ex.Message, ex.Message,
"Setup fehlgeschlagen", "Einstellungen fehlgeschlagen",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Error); MessageBoxIcon.Error);
} }
@@ -590,7 +610,7 @@ public sealed class SetupWizardForm : Form
MessageBox.Show( MessageBox.Show(
this, this,
"System und Benutzerprofil wurden ausgewählt.", "System und Benutzerprofil wurden ausgewählt.",
"Setup", "Einstellungen",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Information); MessageBoxIcon.Information);
@@ -707,7 +727,7 @@ public sealed class SetupWizardForm : Form
MessageBox.Show( MessageBox.Show(
this, this,
message, message,
"Setup", "Einstellungen",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Warning); MessageBoxIcon.Warning);
} }
@@ -1,9 +1,9 @@
{ {
//TEST, PROD // TEST2, TEST, PROD
"EnvironmentCode": "TEST", "EnvironmentCode": "TEST",
"LogDirectory": "Logs", "LogDirectory": "Logs",
"Database": { "Database": {
"ConnectionString": "Server=VWEBDEV01;Database=EsbZertifkatManager;Integrated Security=True;Encrypt=True;Column Encryption Setting=Enabled;Application Name=ZA.CoreService.ESBCertificateManager;TrustServerCertificate=True;" "ConnectionString": "Server=VWEBDEV01;Database=EsbZertifikatManager;Integrated Security=True;Encrypt=True;Column Encryption Setting=Enabled;Application Name=ZA.CoreService.ESBCertificateManager;TrustServerCertificate=True;"
}, },
"Runtime": { "Runtime": {
"JavaExecutablePath": "Runtime\\bin\\java.exe", "JavaExecutablePath": "Runtime\\bin\\java.exe",