using System.Diagnostics; using System.Text; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; public sealed class SonicCredentialTester { private readonly RuntimeSettings _runtimeSettings; public SonicCredentialTester( RuntimeSettings runtimeSettings) { _runtimeSettings = runtimeSettings ?? throw new ArgumentNullException( nameof(runtimeSettings)); } public async Task TestAsync( SonicSystemOption system, string userName, string secret, string containerName, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(system); if (string.IsNullOrWhiteSpace(userName)) { return CredentialTestResult.Failed( "Der Sonic-Benutzername fehlt."); } if (string.IsNullOrEmpty(secret)) { return CredentialTestResult.Failed( "Das Sonic-Kennwort fehlt."); } if (string.IsNullOrWhiteSpace(containerName)) { return CredentialTestResult.Failed( "Für den Verbindungstest fehlt ein Container."); } string javaPath = PathResolver.ResolvePath( _runtimeSettings.JavaExecutablePath); string libraryPath = PathResolver.ResolvePath( _runtimeSettings.SonicClientLibraryPath); string? toolsPath = ResolveToolsDirectory(); if (!File.Exists(javaPath)) { return CredentialTestResult.Failed( $"Java wurde nicht gefunden: {javaPath}"); } if (!Directory.Exists(libraryPath)) { return CredentialTestResult.Failed( $"Der Sonic-JAR-Ordner wurde nicht gefunden: {libraryPath}"); } if (toolsPath is null) { return CredentialTestResult.Failed( "SonicMfContainerTool.class wurde nicht gefunden."); } string[] jars = Directory.GetFiles( libraryPath, "*.jar", SearchOption.TopDirectoryOnly); if (jars.Length == 0) { return CredentialTestResult.Failed( "Im Sonic-Client-Ordner wurden keine JAR-Dateien gefunden."); } string classpath = toolsPath + Path.PathSeparator + string.Join( Path.PathSeparator, jars.OrderBy( Path.GetFileName, StringComparer.OrdinalIgnoreCase)); ProcessStartInfo startInfo = new() { FileName = javaPath, WorkingDirectory = toolsPath, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, CreateNoWindow = true }; startInfo.ArgumentList.Add("-cp"); startInfo.ArgumentList.Add(classpath); startInfo.ArgumentList.Add( "SonicMfContainerTool"); startInfo.ArgumentList.Add("validate"); startInfo.ArgumentList.Add("--domain"); startInfo.ArgumentList.Add( system.DomainName); startInfo.ArgumentList.Add("--url"); startInfo.ArgumentList.Add( system.ConnectionUrl); startInfo.ArgumentList.Add("--user"); startInfo.ArgumentList.Add( userName.Trim()); startInfo.ArgumentList.Add("--container"); startInfo.ArgumentList.Add( containerName); startInfo.ArgumentList.Add("--timeout"); startInfo.ArgumentList.Add("30"); startInfo.Environment[ "ESB_SONIC_PASSWORD"] = secret; using Process process = new() { StartInfo = startInfo }; try { if (!process.Start()) { return CredentialTestResult.Failed( "Der Java-Prozess konnte nicht gestartet werden."); } } catch (Exception ex) { return CredentialTestResult.Failed( $"Java konnte nicht gestartet werden: {ex.Message}"); } Task standardOutputTask = process.StandardOutput.ReadToEndAsync(); Task standardErrorTask = process.StandardError.ReadToEndAsync(); using CancellationTokenSource timeoutSource = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken); timeoutSource.CancelAfter( TimeSpan.FromSeconds(35)); try { await process.WaitForExitAsync( timeoutSource.Token); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { TryKill(process); return CredentialTestResult.Failed( "Der Sonic-Verbindungstest hat das Zeitlimit überschritten."); } catch (OperationCanceledException) { TryKill(process); throw; } string standardOutput = (await standardOutputTask).Trim(); string standardError = (await standardErrorTask).Trim(); string combinedOutput = string.Join( Environment.NewLine, new[] { standardOutput, standardError } .Where(value => !string.IsNullOrWhiteSpace(value))); bool success = process.ExitCode == 0 && combinedOutput.Contains( "OK:ConnectionValidated", StringComparison.OrdinalIgnoreCase) && !combinedOutput.Contains( "ERROR:", StringComparison.OrdinalIgnoreCase); if (success) { return CredentialTestResult.Successful(); } string error = ExtractError(combinedOutput) ?? $"Sonic-Verbindungstest fehlgeschlagen, " + $"ExitCode={process.ExitCode}."; return CredentialTestResult.Failed(error); } private static string? ResolveToolsDirectory() { string[] candidates = [ Path.Combine( AppContext.BaseDirectory, "Tools"), Path.Combine( Directory.GetCurrentDirectory(), "Tools") ]; return candidates.FirstOrDefault( directory => File.Exists( Path.Combine( directory, "SonicMfContainerTool.class"))); } private static string? ExtractError( string output) { return output .Split( ['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) .FirstOrDefault( line => line.StartsWith( "ERROR:", StringComparison.OrdinalIgnoreCase)) ?["ERROR:".Length..] .Trim(); } private static void TryKill( Process process) { try { if (!process.HasExited) { process.Kill( entireProcessTree: true); } } catch { // Der ursprüngliche Testfehler bleibt erhalten. } } } public sealed record CredentialTestResult( bool Success, string Message) { public static CredentialTestResult Successful() { return new CredentialTestResult( true, "Sonic-Anmeldung und lesender Containerzugriff waren erfolgreich."); } public static CredentialTestResult Failed( string message) { return new CredentialTestResult( false, message); } }