using System; using System.Configuration; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web.Mvc; using System.Xml; using Neo.LegitimateLicences.Logic.eNatis; using Newtonsoft.Json; namespace Neo.LegitimateLicences.ApplicationRequest.Controllers { /// /// Standalone test controller for eNatis API connection testing /// Does not require authentication or database access /// public class ENatisTestController : Controller { #region eNatis Login Test private static string _cookie = string.Empty; private static string loginURL = ConfigurationManager.AppSettings["eNatisLoginURL"]; private static string eNatisQueryURL = ConfigurationManager.AppSettings["eNatisQueryURL"]; /// /// Test page to display eNatis login test interface /// /// [AllowAnonymous] public ActionResult Test() { return View(); } /// /// Execute eNatis login test - exact same logic as ApplicationRequestController.eNatisLogin() /// /// [HttpPost] [AllowAnonymous] public JsonResult TestLogin() { // Delegate to the shared ENatisSession so the test page uses the same // serialised, session-reusing path as the rest of the app. The try/finally // around ForceLogout guarantees we don't leak a session even if an // exception bubbles up — which was the primary leak source on the test page. try { string result; try { ENatisSession.EnsureSession(); result = "SUCCESS"; } catch (MaxSessionsInUseException) { result = "MAX_SESSIONS_IN_USE"; } var cookie = ENatisSession.Cookie; var cookieInfo = string.IsNullOrEmpty(cookie) ? "No cookie received" : "Cookie received: " + cookie.Substring(0, Math.Min(50, cookie.Length)) + "..."; return Json(new { Success = true, RequestState = result, CookieInfo = cookieInfo, Message = "eNatis login test completed successfully" }); } catch (Exception ex) { // Build comprehensive error message including inner exceptions var errorBuilder = new StringBuilder(); errorBuilder.AppendLine($"Error: {ex.Message}"); errorBuilder.AppendLine($"Type: {ex.GetType().Name}"); // Handle AggregateException (common with async operations) if (ex is AggregateException aggregateEx) { errorBuilder.AppendLine(); errorBuilder.AppendLine("Inner Exceptions:"); int innerIndex = 1; foreach (var innerEx in aggregateEx.InnerExceptions) { errorBuilder.AppendLine($" [{innerIndex}] {innerEx.GetType().Name}: {innerEx.Message}"); if (innerEx.InnerException != null) { errorBuilder.AppendLine($" -> {innerEx.InnerException.GetType().Name}: {innerEx.InnerException.Message}"); } innerIndex++; } } // Handle regular inner exceptions else if (ex.InnerException != null) { errorBuilder.AppendLine(); errorBuilder.AppendLine("Inner Exception:"); errorBuilder.AppendLine($" Type: {ex.InnerException.GetType().Name}"); errorBuilder.AppendLine($" Message: {ex.InnerException.Message}"); // Check for nested inner exceptions var currentInner = ex.InnerException.InnerException; int depth = 2; while (currentInner != null && depth < 5) // Limit depth to avoid infinite loops { errorBuilder.AppendLine($" [{depth}] {currentInner.GetType().Name}: {currentInner.Message}"); currentInner = currentInner.InnerException; depth++; } } var fullError = errorBuilder.ToString(); return Json(new { Success = false, Error = fullError, ErrorMessage = ex.Message, ErrorType = ex.GetType().Name, InnerException = ex.InnerException != null ? ex.InnerException.Message : null, InnerExceptionType = ex.InnerException != null ? ex.InnerException.GetType().Name : null, StackTrace = ex.StackTrace, Message = "eNatis login test failed" }); } finally { // Always release the eNatis session after the test. Without this, // every test click leaked a session, exhausting MAX_SESSIONS_IN_USE // after ~5 clicks. try { ENatisSession.ForceLogout(); } catch { /* swallow — best-effort */ } } } // Legacy login implementation kept below for reference; replaced by ENatisSession. #if FALSE private string eNatisLoginLegacy() { string certificatePath = ConfigurationManager.AppSettings["eNatisCertificatePath"]; string certificatePassword = ConfigurationManager.AppSettings["eNatisCertificatePassword"]; // Validate certificate path exists if (string.IsNullOrEmpty(certificatePath)) { throw new Exception("eNatisCertificatePath is not configured in web.config"); } if (!System.IO.File.Exists(certificatePath)) { throw new Exception($"Certificate file not found at path: {certificatePath}. Please verify the certificate path is correct and the file is accessible on the server."); } // Set up HttpClientHandler with the certificate var handler = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; // Load the certificate with error handling X509Certificate2 certificate; try { certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); } catch (Exception certEx) { throw new Exception($"Failed to load certificate from {certificatePath}. Error: {certEx.Message}. Please verify the certificate file is valid and the password is correct.", certEx); } handler.ClientCertificates.Add(certificate); // Support both TLS 1.1 and TLS 1.2 for eNatis connectivity System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11; // Allow all SSL certificate validation (for testing purposes) System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; // Set default connection limit System.Net.ServicePointManager.DefaultConnectionLimit = 10; // Ensure we're using the latest security protocols System.Net.ServicePointManager.Expect100Continue = false; // The XML content to send in the POST request var xmlContent = $@" {ConfigurationManager.AppSettings["eNatisOpModInd"]} {ConfigurationManager.AppSettings["eNatisESTxanID"]} {ConfigurationManager.AppSettings["eNatisESUserN"]} {ConfigurationManager.AppSettings["eNatisESUID"]} {ConfigurationManager.AppSettings["eNatisEStermID"]} {ConfigurationManager.AppSettings["eNatisUsername"]} {ConfigurationManager.AppSettings["eNatisPassword"]} {ConfigurationManager.AppSettings["eNatisTxanType"]} "; var content = new StringContent(xmlContent, Encoding.UTF8, "application/xml"); using (var client = new HttpClient(handler)) { // Example: Add Authorization token if needed var username = ConfigurationManager.AppSettings["eNatisUsername"]; var password = ConfigurationManager.AppSettings["eNatisPassword"]; var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); // Perform the POST request - try TLS 1.1 first, fallback to TLS 1.2 if it fails HttpResponseMessage response; try { response = client.PostAsync(loginURL, content).Result; } catch (Exception tlsEx) { // If TLS 1.1 fails, try TLS 1.2 if (tlsEx.Message.Contains("SSL/TLS") || tlsEx.Message.Contains("secure channel")) { System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // Create a new handler and client for TLS 1.2 var handler2 = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; var certificate2 = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); handler2.ClientCertificates.Add(certificate2); using (var client2 = new HttpClient(handler2)) { client2.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); response = client2.PostAsync(loginURL, content).Result; } } else { throw; } } // Check if the response contains the "Set-Cookie" header if (response.Headers.Contains("Set-Cookie")) { // Retrieve the cookies from the "Set-Cookie" header var cookies = response.Headers.GetValues("Set-Cookie"); foreach (var c in cookies) { _cookie = c; } } // Read and output the response var responseContent = response.Content.ReadAsStringAsync().Result; // Load the XML into an XmlDocument XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(responseContent); // Convert the XmlDocument to a JSON string string json = JsonConvert.SerializeXmlNode(xmlDoc); // Parse the JSON into a dynamic object dynamic jsonObj = JsonConvert.DeserializeObject(json); //Get the status of the request var requestState = jsonObj.X4000Resp.TxState.Value; return requestState; } } #endif #endregion } }