From dcfcb5289d3fdded238b2ee9be8a2333179c89cb Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 29 Jul 2026 08:23:55 +0200 Subject: [PATCH] Simplify DB to six tables and load credentials via Always Encrypted SonicCredential. Co-authored-by: Cursor --- .../Database/00_DropExisting.sql | 36 ++ .../Database/01_CreateDatabase.sql | 31 + .../Database/02_CreateSchema.sql | 606 ++++++++++++++++++ .../Database/03_Seed.sql | 143 +++++ .../Database/README.md | 36 ++ ZA.CoreService.ESBCertificateManager/Form1.cs | 209 +++++- .../Models/DatabaseSonicConnection.cs | 50 +- .../Program.cs | 10 +- .../Services/DeployTargetRepository.cs | 151 ++--- .../Services/LocalSetupSelectionStore.cs | 4 +- .../Services/SonicConncectionRepository.cs | 264 ++------ .../Services/SonicMfApiExecutor.cs | 6 +- .../Services/SonicSetupRepository.cs | 223 +++++-- .../Setup/SetupWizardForm.cs | 44 +- .../appsettings.json | 4 +- 15 files changed, 1356 insertions(+), 461 deletions(-) create mode 100644 ZA.CoreService.ESBCertificateManager/Database/00_DropExisting.sql create mode 100644 ZA.CoreService.ESBCertificateManager/Database/01_CreateDatabase.sql create mode 100644 ZA.CoreService.ESBCertificateManager/Database/02_CreateSchema.sql create mode 100644 ZA.CoreService.ESBCertificateManager/Database/03_Seed.sql create mode 100644 ZA.CoreService.ESBCertificateManager/Database/README.md diff --git a/ZA.CoreService.ESBCertificateManager/Database/00_DropExisting.sql b/ZA.CoreService.ESBCertificateManager/Database/00_DropExisting.sql new file mode 100644 index 0000000..33eadfd --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Database/00_DropExisting.sql @@ -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 diff --git a/ZA.CoreService.ESBCertificateManager/Database/01_CreateDatabase.sql b/ZA.CoreService.ESBCertificateManager/Database/01_CreateDatabase.sql new file mode 100644 index 0000000..3b14692 --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Database/01_CreateDatabase.sql @@ -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 diff --git a/ZA.CoreService.ESBCertificateManager/Database/02_CreateSchema.sql b/ZA.CoreService.ESBCertificateManager/Database/02_CreateSchema.sql new file mode 100644 index 0000000..a565e65 --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Database/02_CreateSchema.sql @@ -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/. + 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 diff --git a/ZA.CoreService.ESBCertificateManager/Database/03_Seed.sql b/ZA.CoreService.ESBCertificateManager/Database/03_Seed.sql new file mode 100644 index 0000000..9006e70 --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Database/03_Seed.sql @@ -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 diff --git a/ZA.CoreService.ESBCertificateManager/Database/README.md b/ZA.CoreService.ESBCertificateManager/Database/README.md new file mode 100644 index 0000000..8d5a976 --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Database/README.md @@ -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/`. 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. diff --git a/ZA.CoreService.ESBCertificateManager/Form1.cs b/ZA.CoreService.ESBCertificateManager/Form1.cs index cf2b43b..013fbc3 100644 --- a/ZA.CoreService.ESBCertificateManager/Form1.cs +++ b/ZA.CoreService.ESBCertificateManager/Form1.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography.X509Certificates; using ZA.CoreService.ESBCertificateManager.Configuration; using ZA.CoreService.ESBCertificateManager.Models; using ZA.CoreService.ESBCertificateManager.Services; +using ZA.CoreService.ESBCertificateManager.Setup; using Microsoft.Data.SqlClient; namespace ZA.CoreService.ESBCertificateManager @@ -34,10 +35,17 @@ namespace ZA.CoreService.ESBCertificateManager private DeploymentOrchestrator? _orchestrator; private readonly SonicConnectionRepository _sonicConnectionRepository; private readonly DeploymentTargetRepository _deploymentTargetRepository; + private readonly SonicSetupRepository _sonicSetupRepository; + private readonly LocalSetupSelectionStore _localSetupSelectionStore; private IReadOnlyList _databaseSonicConnections = []; + private readonly Dictionary + _credentialsByConnectionId = new(); + + private LocalSetupSelection? _localSetupSelection; + private CertificateInfo? _loadedCertificateInfo; private IReadOnlyList _loadedTargets = []; private bool _isOperationRunning; @@ -89,6 +97,11 @@ namespace ZA.CoreService.ESBCertificateManager _deploymentTargetRepository = new DeploymentTargetRepository( _settings.Database.ConnectionString); + _sonicSetupRepository = new SonicSetupRepository( + _settings.Database.ConnectionString); + + _localSetupSelectionStore = new LocalSetupSelectionStore(); + BuildDesign(); Shown += async (_, _) => await InitializeApplicationAsync(); @@ -135,6 +148,52 @@ namespace ZA.CoreService.ESBCertificateManager 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() { @@ -163,16 +222,32 @@ namespace ZA.CoreService.ESBCertificateManager } try { - _databaseSonicConnections = - await _sonicConnectionRepository.GetActiveAsync(); + _localSetupSelection = + _localSetupSelectionStore.Load(); - List runtimeConnections = - _databaseSonicConnections - .Where(connection => connection.HasCredentials) - .Select(connection => + _databaseSonicConnections = + await _sonicConnectionRepository.GetActiveAsync( + _settings.EnvironmentCode); + + await LoadCredentialsForConnectionsAsync(); + + List runtimeConnections = []; + + foreach (DatabaseSonicConnection connection + in _databaseSonicConnections) + { + if (!_credentialsByConnectionId.TryGetValue( + connection.Id, + out SonicCredentialProfile? credential)) + { + continue; + } + + runtimeConnections.Add( connection.ToRuntimeConnection( - _settings.Runtime)) - .ToList(); + _settings.Runtime, + credential)); + } _orchestrator = new DeploymentOrchestrator( _settings, @@ -195,12 +270,13 @@ namespace ZA.CoreService.ESBCertificateManager SetStatus( $"{_databaseSonicConnections.Count} aktive Sonic-Verbindung(en) " + - $"aus SQL und {_loadedTargets.Count} lokale Testziele geladen.", + $"aus SQL und {_loadedTargets.Count} Bereitstellungsziel(e) geladen.", isError: false); } catch (SqlException ex) { _databaseSonicConnections = []; + _credentialsByConnectionId.Clear(); SetStatus( $"SQL-Zugriff fehlgeschlagen: {ex.Message}", @@ -485,12 +561,12 @@ namespace ZA.CoreService.ESBCertificateManager progressPanel.Controls.Add(progressTitle); // ----------------------------- - // 3. Footer unten: Version + // 3. Footer unten: Einstellungen + Version // ----------------------------- Panel footerPanel = new Panel { Dock = DockStyle.Bottom, - Height = 70, + Height = 118, Padding = new Padding(18, 8, 12, 8) }; @@ -501,13 +577,36 @@ namespace ZA.CoreService.ESBCertificateManager 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 { Text = "INTERNAL TOOL", ForeColor = MutedTextColor, Font = new Font("Segoe UI", 7.5f, FontStyle.Bold), AutoSize = true, - Location = new Point(18, 17) + Location = new Point(18, 62) }; Label versionNumber = new Label @@ -516,10 +615,11 @@ namespace ZA.CoreService.ESBCertificateManager ForeColor = BlueColor, Font = new Font("Segoe UI", 8), AutoSize = true, - Location = new Point(18, 37) + Location = new Point(18, 82) }; footerPanel.Controls.Add(footerSeparator); + footerPanel.Controls.Add(btnSettings); footerPanel.Controls.Add(versionLabel); footerPanel.Controls.Add(versionNumber); @@ -1428,7 +1528,8 @@ namespace ZA.CoreService.ESBCertificateManager isError: false); _loadedTargets = - await _deploymentTargetRepository.GetActiveAsync(); + await _deploymentTargetRepository.GetActiveAsync( + _settings.EnvironmentCode); BindTargetsToGrid(_loadedTargets); @@ -1943,6 +2044,42 @@ namespace ZA.CoreService.ESBCertificateManager // --------------------------------------------------------------- // Sonic – Status (KnownContainers aus appsettings) // --------------------------------------------------------------- + private async Task LoadCredentialsForConnectionsAsync() + { + _credentialsByConnectionId.Clear(); + + foreach (DatabaseSonicConnection connection + in _databaseSonicConnections) + { + IReadOnlyList 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() { cmbSonicConnection.Items.Clear(); @@ -1953,13 +2090,34 @@ namespace ZA.CoreService.ESBCertificateManager cmbSonicConnection.Items.Add(connection.Name); } - if (cmbSonicConnection.Items.Count > 0) + if (cmbSonicConnection.Items.Count == 0) { - cmbSonicConnection.SelectedIndex = 0; + RefreshSonicStatusLine(); 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) { @@ -2036,14 +2194,19 @@ namespace ZA.CoreService.ESBCertificateManager { string connectionUrl = connection.BuildConnectionUrl(); - bool hasCredentialReference = - !string.IsNullOrWhiteSpace(connection.CredentialReference); + bool hasCredential = + _credentialsByConnectionId.TryGetValue( + connection.Id, + out SonicCredentialProfile? credential); - lblSonicStatus.Text = hasCredentialReference - ? $"{connection.Name}: {connectionUrl}, Credential-Referenz vorhanden" - : $"{connection.Name}: {connectionUrl}, sichere Zugangsdaten noch nicht konfiguriert"; + string credentialInfo = hasCredential + ? $"Profil '{credential!.CredentialName}'" + : "sichere Zugangsdaten noch nicht konfiguriert"; - lblSonicStatus.ForeColor = hasCredentialReference + lblSonicStatus.Text = + $"{connection.Name}: {connectionUrl}, {credentialInfo}"; + + lblSonicStatus.ForeColor = hasCredential ? GreenColor : GoldColor; } diff --git a/ZA.CoreService.ESBCertificateManager/Models/DatabaseSonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/DatabaseSonicConnection.cs index f84c5bb..b7fdbd8 100644 --- a/ZA.CoreService.ESBCertificateManager/Models/DatabaseSonicConnection.cs +++ b/ZA.CoreService.ESBCertificateManager/Models/DatabaseSonicConnection.cs @@ -4,8 +4,7 @@ namespace ZA.CoreService.ESBCertificateManager.Models; /// /// Aus dbo.SonicConnection geladene Verbindungsdaten. -/// CredentialSecret wird durch Microsoft.Data.SqlClient -/// clientseitig entschlüsselt. +/// Zugangsdaten liegen ausschließlich in dbo.SonicCredential. /// public sealed class DatabaseSonicConnection { @@ -15,34 +14,16 @@ public sealed class DatabaseSonicConnection public required string ManagementHost { get; init; } - public int? ManagementPort { get; init; } + public int ManagementPort { get; init; } public string? ConnectionProtocol { get; init; } public string? DomainName { get; init; } - public string? CredentialUserName { get; init; } - - public string? CredentialReference { get; init; } - - /// - /// Nur zur Laufzeit verfügbar. - /// Darf niemals protokolliert oder angezeigt werden. - /// - public string? CredentialSecret { get; init; } - - public string? JavaHomePath { get; init; } - - public string? SonicHomePath { get; init; } - - public string? Notes { get; init; } + public string? EnvironmentCode { get; init; } public bool IsActive { get; init; } - public bool HasCredentials => - !string.IsNullOrWhiteSpace(CredentialUserName) - && !string.IsNullOrEmpty(CredentialSecret); - public string BuildConnectionUrl() { if (string.IsNullOrWhiteSpace(ConnectionProtocol)) @@ -57,7 +38,7 @@ public sealed class DatabaseSonicConnection $"Bei der Sonic-Verbindung '{Name}' fehlt ManagementHost."); } - if (ManagementPort is null or < 1 or > 65535) + if (ManagementPort is < 1 or > 65535) { throw new InvalidOperationException( $"Bei der Sonic-Verbindung '{Name}' fehlt ein gültiger ManagementPort."); @@ -69,29 +50,26 @@ public sealed class DatabaseSonicConnection string host = ManagementHost.Trim(); - return $"{protocol}://{host}:{ManagementPort.Value}"; + return $"{protocol}://{host}:{ManagementPort}"; } public SonicConnection ToRuntimeConnection( - RuntimeSettings runtimeSettings) + RuntimeSettings runtimeSettings, + SonicCredentialProfile credential) { + ArgumentNullException.ThrowIfNull(credential); + if (string.IsNullOrWhiteSpace(DomainName)) { throw new InvalidOperationException( $"Bei der Sonic-Verbindung '{Name}' fehlt DomainName."); } - if (string.IsNullOrWhiteSpace(CredentialUserName)) - { - throw new InvalidOperationException( - $"Bei der Sonic-Verbindung '{Name}' fehlt der Benutzername."); - } - - if (string.IsNullOrEmpty(CredentialSecret)) + if (!credential.IsComplete) { throw new InvalidOperationException( $"Bei der Sonic-Verbindung '{Name}' fehlen die Zugangsdaten. " + - "Bitte das Setup ausführen."); + "Bitte die Einstellungen öffnen und ein Benutzerprofil hinterlegen."); } string javaExecutablePath = @@ -119,10 +97,10 @@ public sealed class DatabaseSonicConnection Name = Name, DomainName = DomainName, ConnectionUrl = BuildConnectionUrl(), - Username = CredentialUserName, + Username = credential.UserName, // Nur Laufzeitwert, bereits clientseitig entschlüsselt. - Password = CredentialSecret, + Password = credential.Secret, JavaPath = javaExecutablePath, JavaHome = javaHome, @@ -133,4 +111,4 @@ public sealed class DatabaseSonicConnection PostRestartDelaySeconds = 20 }; } -} \ No newline at end of file +} diff --git a/ZA.CoreService.ESBCertificateManager/Program.cs b/ZA.CoreService.ESBCertificateManager/Program.cs index 42ae1bc..60fa9f3 100644 --- a/ZA.CoreService.ESBCertificateManager/Program.cs +++ b/ZA.CoreService.ESBCertificateManager/Program.cs @@ -34,10 +34,12 @@ internal static class Program .GetAwaiter() .GetResult(); - if (setupResult.IsReady) + if (!setupResult.IsReady) { using SetupWizardForm setupForm = - new(coordinator); + new( + coordinator, + isSettingsMode: false); DialogResult setupDialogResult = setupForm.ShowDialog(); @@ -57,7 +59,7 @@ internal static class Program { MessageBox.Show( "Die Einrichtung ist noch nicht vollständig.", - "Setup unvollständig", + "Einstellungen unvollständig", MessageBoxButtons.OK, MessageBoxIcon.Warning); @@ -77,4 +79,4 @@ internal static class Program MessageBoxIcon.Error); } } -} \ No newline at end of file +} diff --git a/ZA.CoreService.ESBCertificateManager/Services/DeployTargetRepository.cs b/ZA.CoreService.ESBCertificateManager/Services/DeployTargetRepository.cs index 54e8d9e..a9daaa1 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/DeployTargetRepository.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/DeployTargetRepository.cs @@ -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> 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 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); - } -} \ No newline at end of file +} diff --git a/ZA.CoreService.ESBCertificateManager/Services/LocalSetupSelectionStore.cs b/ZA.CoreService.ESBCertificateManager/Services/LocalSetupSelectionStore.cs index 7218798..2c47c97 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/LocalSetupSelectionStore.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/LocalSetupSelectionStore.cs @@ -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); diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicConncectionRepository.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicConncectionRepository.cs index 1cd0d29..2cf7ffd 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicConncectionRepository.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicConncectionRepository.cs @@ -23,26 +23,31 @@ public sealed class SonicConnectionRepository public async Task> 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 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); - } -} \ No newline at end of file +} diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs index 7dcb732..2fa318a 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs @@ -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[ diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs index 153c8fe..23bbeb7 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs @@ -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 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 profiles = []; @@ -502,4 +498,101 @@ public sealed class SonicSetupRepository return profiles; } -} \ No newline at end of file + + public async Task 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")) + }; + } +} diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs index 7cd0786..534d674 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs @@ -5,6 +5,7 @@ namespace ZA.CoreService.ESBCertificateManager.Setup; public sealed class SetupWizardForm : Form { private readonly SetupCoordinator _coordinator; + private readonly bool _isSettingsMode; private readonly Color _backgroundColor = Color.FromArgb(10, 18, 34); @@ -49,9 +50,11 @@ public sealed class SetupWizardForm : Form private bool _running; public SetupWizardForm( - SetupCoordinator coordinator) + SetupCoordinator coordinator, + bool isSettingsMode = false) { _coordinator = coordinator; + _isSettingsMode = isSettingsMode; BuildInterface(); @@ -61,7 +64,7 @@ public sealed class SetupWizardForm : Form private void BuildInterface() { - Text = "ESB Certificate Manager einrichten"; + Text = "Einstellungen"; StartPosition = FormStartPosition.CenterScreen; Size = new Size(950, 690); MinimumSize = new Size(900, 650); @@ -73,7 +76,7 @@ public sealed class SetupWizardForm : Form Label title = new() { - Text = "Ersteinrichtung", + Text = "Einstellungen", AutoSize = true, Location = new Point(38, 25), ForeColor = _textColor, @@ -85,8 +88,9 @@ public sealed class SetupWizardForm : Form Label subtitle = new() { - Text = - "Runtime, Datenbank, Zertifikat und Systemauswahl prüfen.", + Text = _isSettingsMode + ? "Runtime, Zertifikat, System und Benutzerprofil anpassen." + : "Runtime, Datenbank, Zertifikat und Systemauswahl prüfen.", AutoSize = true, Location = new Point(42, 70), ForeColor = _mutedTextColor @@ -244,7 +248,7 @@ public sealed class SetupWizardForm : Form await InitializeAsync(); Button cancelButton = CreateButton( - "Abbrechen"); + _isSettingsMode ? "Schließen" : "Abbrechen"); cancelButton.Location = new Point(675, 580); @@ -252,14 +256,30 @@ public sealed class SetupWizardForm : Form cancelButton.Size = new Size(110, 42); - cancelButton.Click += (_, _) => + cancelButton.Click += async (_, _) => { - DialogResult = DialogResult.Cancel; + if (_isSettingsMode) + { + // Nach gespeicherter Auswahl die Hauptansicht aktualisieren. + SetupCheckResult check = + await _coordinator.CheckAsync(); + + DialogResult = check.IsReady + ? DialogResult.OK + : DialogResult.Cancel; + } + else + { + DialogResult = DialogResult.Cancel; + } + Close(); }; _btnFinish = CreateButton( - "Setup abschließen"); + _isSettingsMode + ? "Übernehmen" + : "Fertig"); _btnFinish.Location = new Point(795, 580); @@ -338,7 +358,7 @@ public sealed class SetupWizardForm : Form MessageBox.Show( this, ex.Message, - "Setup fehlgeschlagen", + "Einstellungen fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error); } @@ -590,7 +610,7 @@ public sealed class SetupWizardForm : Form MessageBox.Show( this, "System und Benutzerprofil wurden ausgewählt.", - "Setup", + "Einstellungen", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -707,7 +727,7 @@ public sealed class SetupWizardForm : Form MessageBox.Show( this, message, - "Setup", + "Einstellungen", MessageBoxButtons.OK, MessageBoxIcon.Warning); } diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json index e668f97..4bb3ca6 100644 --- a/ZA.CoreService.ESBCertificateManager/appsettings.json +++ b/ZA.CoreService.ESBCertificateManager/appsettings.json @@ -1,9 +1,9 @@ { - //TEST, PROD + // TEST2, TEST, PROD "EnvironmentCode": "TEST", "LogDirectory": "Logs", "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": { "JavaExecutablePath": "Runtime\\bin\\java.exe",