using System.Data; using Microsoft.Data.SqlClient; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; public sealed class SonicSetupRepository { private readonly string _connectionString; public SonicSetupRepository(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentException( "Der SQL-Connection-String fehlt.", nameof(connectionString)); } _connectionString = connectionString; } public async Task> GetSystemsAsync( string environmentCode, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(environmentCode)) { throw new ArgumentException( "Der EnvironmentCode fehlt.", nameof(environmentCode)); } 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].[Environment] AS environment ON environment.[EnvironmentId] = connection.[EnvironmentId] LEFT JOIN [dbo].[SonicContainer] AS container ON container.[SonicConnectionId] = connection.[SonicConnectionId] AND container.[IsActive] = 1 WHERE connection.[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]; """; List systems = []; await using SqlConnection connection = new(_connectionString); await connection.OpenAsync(cancellationToken); 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); int sonicConnectionIdOrdinal = reader.GetOrdinal("SonicConnectionId"); int connectionNameOrdinal = reader.GetOrdinal("ConnectionName"); int environmentCodeOrdinal = reader.GetOrdinal("EnvironmentCode"); int domainNameOrdinal = reader.GetOrdinal("DomainName"); int managementHostOrdinal = reader.GetOrdinal("ManagementHost"); int managementPortOrdinal = reader.GetOrdinal("ManagementPort"); int connectionProtocolOrdinal = reader.GetOrdinal("ConnectionProtocol"); int validationContainerNameOrdinal = reader.GetOrdinal("ValidationContainerName"); while (await reader.ReadAsync(cancellationToken)) { systems.Add( new SonicSystemOption { SonicConnectionId = reader.GetInt32( sonicConnectionIdOrdinal), ConnectionName = reader.GetString( connectionNameOrdinal), EnvironmentCode = reader.GetString( environmentCodeOrdinal), DomainName = reader.IsDBNull(domainNameOrdinal) ? string.Empty : reader.GetString( domainNameOrdinal), ManagementHost = reader.GetString( managementHostOrdinal), ManagementPort = reader.GetInt32( managementPortOrdinal), ConnectionProtocol = reader.IsDBNull(connectionProtocolOrdinal) ? "tcp" : reader.GetString( connectionProtocolOrdinal), ValidationContainerName = reader.IsDBNull( validationContainerNameOrdinal) ? string.Empty : reader.GetString( validationContainerNameOrdinal) }); } return systems; } public async Task AddCredentialAsync( int sonicConnectionId, string credentialName, string userName, string secret, bool isDefault, CancellationToken cancellationToken = default) { if (sonicConnectionId <= 0) { throw new ArgumentOutOfRangeException( nameof(sonicConnectionId)); } if (string.IsNullOrWhiteSpace(credentialName)) { throw new ArgumentException( "Der Profilname fehlt.", nameof(credentialName)); } if (string.IsNullOrWhiteSpace(userName)) { throw new ArgumentException( "Der Sonic-Benutzername fehlt.", nameof(userName)); } if (string.IsNullOrEmpty(secret)) { throw new ArgumentException( "Das Sonic-Kennwort fehlt.", nameof(secret)); } string normalizedCredentialName = credentialName.Trim(); string normalizedUserName = userName.Trim(); if (normalizedCredentialName.Length > 150) { throw new ArgumentException( "Der Profilname darf maximal 150 Zeichen enthalten.", nameof(credentialName)); } if (normalizedUserName.Length > 256) { throw new ArgumentException( "Der Benutzername darf maximal 256 Zeichen enthalten.", nameof(userName)); } if (secret.Length > 512) { throw new ArgumentException( "Das Kennwort darf maximal 512 Zeichen enthalten.", nameof(secret)); } await using SqlConnection connection = new(_connectionString); await connection.OpenAsync(cancellationToken); await using SqlTransaction transaction = (SqlTransaction) await connection.BeginTransactionAsync( cancellationToken); try { if (isDefault) { const string clearDefaultSql = """ UPDATE [dbo].[SonicCredential] SET [IsDefault] = 0, [ModifiedDateTime] = SYSUTCDATETIME(), [ModifiedBy] = SUSER_SNAME() WHERE [SonicConnectionId] = @SonicConnectionId AND [IsDefault] = 1; """; await using SqlCommand clearDefaultCommand = new( clearDefaultSql, connection, transaction); clearDefaultCommand.Parameters.Add( new SqlParameter( "@SonicConnectionId", SqlDbType.Int) { Value = sonicConnectionId }); await clearDefaultCommand.ExecuteNonQueryAsync( cancellationToken); } const string insertSql = """ INSERT INTO [dbo].[SonicCredential] ( [SonicConnectionId], [CredentialName], [CredentialUserName], [CredentialSecret], [IsDefault], [IsActive], [CreationDateTime], [CreatedBy], [ModifiedDateTime], [ModifiedBy] ) OUTPUT INSERTED.[SonicCredentialId] VALUES ( @SonicConnectionId, @CredentialName, @CredentialUserName, @CredentialSecret, @IsDefault, 1, SYSUTCDATETIME(), SUSER_SNAME(), NULL, NULL ); """; await using SqlCommand insertCommand = new( insertSql, connection, transaction); insertCommand.Parameters.Add( new SqlParameter( "@SonicConnectionId", SqlDbType.Int) { Value = sonicConnectionId }); insertCommand.Parameters.Add( new SqlParameter( "@CredentialName", SqlDbType.NVarChar, 150) { Value = normalizedCredentialName }); insertCommand.Parameters.Add( new SqlParameter( "@CredentialUserName", SqlDbType.NVarChar, 256) { Value = normalizedUserName }); insertCommand.Parameters.Add( new SqlParameter( "@CredentialSecret", SqlDbType.NVarChar, 512) { Value = secret }); insertCommand.Parameters.Add( new SqlParameter( "@IsDefault", SqlDbType.Bit) { Value = isDefault }); object? result = await insertCommand.ExecuteScalarAsync( cancellationToken); if (result is null || result is DBNull) { throw new InvalidOperationException( "Das Benutzerprofil konnte nicht angelegt werden."); } int credentialId = Convert.ToInt32(result); await transaction.CommitAsync( cancellationToken); return credentialId; } catch { try { await transaction.RollbackAsync( CancellationToken.None); } catch { // Die ursprüngliche Exception soll erhalten bleiben. } throw; } } public async Task> GetCredentialsAsync( int sonicConnectionId, CancellationToken cancellationToken = default) { if (sonicConnectionId <= 0) { throw new ArgumentOutOfRangeException( nameof(sonicConnectionId)); } const string sql = """ SELECT [SonicCredentialId], [SonicConnectionId], [CredentialName], [CredentialUserName], [CredentialSecret], [IsDefault], [IsActive] FROM [dbo].[SonicCredential] WHERE [SonicConnectionId] = @SonicConnectionId AND [IsActive] = 1 ORDER BY [IsDefault] DESC, [CredentialName]; """; List profiles = []; 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 }); await using SqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken); int credentialIdOrdinal = reader.GetOrdinal("SonicCredentialId"); int connectionIdOrdinal = reader.GetOrdinal("SonicConnectionId"); int credentialNameOrdinal = reader.GetOrdinal("CredentialName"); int userNameOrdinal = reader.GetOrdinal("CredentialUserName"); int secretOrdinal = reader.GetOrdinal("CredentialSecret"); int isDefaultOrdinal = reader.GetOrdinal("IsDefault"); int isActiveOrdinal = reader.GetOrdinal("IsActive"); while (await reader.ReadAsync(cancellationToken)) { 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)) { continue; } profiles.Add( new SonicCredentialProfile { SonicCredentialId = reader.GetInt32( credentialIdOrdinal), SonicConnectionId = reader.GetInt32( connectionIdOrdinal), CredentialName = reader.GetString( credentialNameOrdinal), UserName = userName, Secret = secret, IsDefault = reader.GetBoolean( isDefaultOrdinal), IsActive = reader.GetBoolean( isActiveOrdinal) }); } return profiles; } 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")) }; } }