Skip to content

Commit e9c7f02

Browse files
Add comprehensive logging and null reference protection
Add extensive trace/error logging and null guards across all project files to aid debugging and prevent NullReferenceExceptions: - SslStoreCaProxy.cs: Harden Synchronize, Enroll, Revoke, GetSingleRecord, Initialize, ValidateEmails, GetEnrollmentResult - SslStoreClient.cs: Add logging/null checks to all API methods, constructor, and query pagination - KeyfactorClient.cs: Add logging/null checks to constructor, template update, and template query pagination - RequestManager.cs: Add logging/null checks to all request builders, MapReturnStatus, GetCertificateContent, and contact helpers - .gitignore: Add .claude/ directory Fixes sync NullReferenceException and operator precedence bug. AB#84952 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dd70eb4 commit e9c7f02

File tree

5 files changed

+1214
-321
lines changed

5 files changed

+1214
-321
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,6 @@ MigrationBackup/
348348

349349
# Ionide (cross platform F# VS Code tools) working folder
350350
.ionide/
351+
352+
# Claude Code
353+
.claude/

SslStoreCaProxy/Client/KeyfactorClient.cs

Lines changed: 119 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,94 @@ public sealed class KeyfactorClient: LoggingClientBase, IKeyfactorClient
2323

2424
public KeyfactorClient(ICAConnectorConfigProvider configProvider)
2525
{
26-
var keyfactorBaseUrl = new Uri(configProvider.CAConnectionData[Constants.KeyfactorApiUrl].ToString());
27-
var keyfactorAuth = configProvider.CAConnectionData[Constants.KeyfactorApiUserId] + ":" + configProvider.CAConnectionData[Constants.KeyfactorApiPassword];
28-
var plainTextBytes = Encoding.UTF8.GetBytes(keyfactorAuth);
29-
30-
var clientHandler = new WebRequestHandler();
31-
RestClient = new HttpClient(clientHandler, true) { BaseAddress = keyfactorBaseUrl };
32-
RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
33-
RestClient.DefaultRequestHeaders.Add("x-keyfactor-requested-with", "APIClient");
34-
RestClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(plainTextBytes));
26+
Logger.Trace("KeyfactorClient constructor called.");
27+
try
28+
{
29+
if (configProvider?.CAConnectionData == null)
30+
{
31+
Logger.Error("KeyfactorClient: configProvider or CAConnectionData is null.");
32+
throw new ArgumentNullException(nameof(configProvider), "configProvider or CAConnectionData is null.");
33+
}
34+
35+
if (!configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiUrl))
36+
{
37+
Logger.Error($"KeyfactorClient: Missing required config key '{Constants.KeyfactorApiUrl}'.");
38+
throw new InvalidOperationException($"Missing required config key '{Constants.KeyfactorApiUrl}'.");
39+
}
40+
41+
var apiUrlValue = configProvider.CAConnectionData[Constants.KeyfactorApiUrl]?.ToString();
42+
Logger.Trace($"KeyfactorClient: KeyfactorApiUrl={apiUrlValue ?? "(null)"}");
43+
44+
if (string.IsNullOrEmpty(apiUrlValue))
45+
{
46+
Logger.Error("KeyfactorClient: KeyfactorApiUrl value is null or empty.");
47+
throw new InvalidOperationException("KeyfactorApiUrl value is null or empty.");
48+
}
49+
50+
var keyfactorBaseUrl = new Uri(apiUrlValue);
51+
52+
var userId = configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiUserId)
53+
? configProvider.CAConnectionData[Constants.KeyfactorApiUserId]?.ToString() ?? ""
54+
: "";
55+
var password = configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiPassword)
56+
? configProvider.CAConnectionData[Constants.KeyfactorApiPassword]?.ToString() ?? ""
57+
: "";
58+
59+
if (string.IsNullOrEmpty(userId))
60+
Logger.Warn("KeyfactorClient: KeyfactorApiUserId is null or empty.");
61+
62+
Logger.Trace($"KeyfactorClient: Configuring with userId={userId}, BaseAddress={keyfactorBaseUrl}");
63+
64+
var keyfactorAuth = userId + ":" + password;
65+
var plainTextBytes = Encoding.UTF8.GetBytes(keyfactorAuth);
66+
67+
var clientHandler = new WebRequestHandler();
68+
RestClient = new HttpClient(clientHandler, true) { BaseAddress = keyfactorBaseUrl };
69+
RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
70+
RestClient.DefaultRequestHeaders.Add("x-keyfactor-requested-with", "APIClient");
71+
RestClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(plainTextBytes));
72+
73+
Logger.Trace("KeyfactorClient: RestClient configured successfully.");
74+
}
75+
catch (Exception ex)
76+
{
77+
Logger.Error($"KeyfactorClient constructor failed: {ex.Message}\n{ex.StackTrace}");
78+
throw;
79+
}
3580
}
3681

3782
public async Task<Template> SubmitUpdateTemplateAsync(Template templateRequest)
3883
{
39-
using (var resp = await RestClient.PutAsync("/KeyfactorApi/Templates", new StringContent(
40-
JsonConvert.SerializeObject(templateRequest), Encoding.ASCII, "application/json")))
84+
Logger.Trace("SubmitUpdateTemplateAsync called.");
85+
try
4186
{
42-
try
87+
Logger.Trace($"SubmitUpdateTemplateAsync Request JSON: {JsonConvert.SerializeObject(templateRequest)}");
88+
using (var resp = await RestClient.PutAsync("/KeyfactorApi/Templates", new StringContent(
89+
JsonConvert.SerializeObject(templateRequest), Encoding.ASCII, "application/json")))
4390
{
44-
Logger.Trace(JsonConvert.SerializeObject(templateRequest));
91+
var responseBody = await resp.Content.ReadAsStringAsync();
92+
Logger.Trace($"SubmitUpdateTemplateAsync Response StatusCode={resp.StatusCode}, Body={responseBody}");
4593
resp.EnsureSuccessStatusCode();
46-
var templateResponse =
47-
JsonConvert.DeserializeObject<Template>(await resp.Content.ReadAsStringAsync());
94+
var templateResponse = JsonConvert.DeserializeObject<Template>(responseBody);
95+
if (templateResponse == null)
96+
Logger.Warn("SubmitUpdateTemplateAsync: Deserialized response is null.");
4897
return templateResponse;
4998
}
50-
catch(Exception e)
51-
{
52-
Logger.Error($"Keyfactor API Error Occured Updating Keyfactor Template: {e.Message}");
53-
return new Template();
54-
}
55-
99+
}
100+
catch (Exception e)
101+
{
102+
Logger.Error($"Keyfactor API Error Occurred Updating Keyfactor Template: {e.Message}\n{e.StackTrace}");
103+
if (e.InnerException != null)
104+
Logger.Error($"Inner exception: {e.InnerException.Message}\n{e.InnerException.StackTrace}");
105+
return new Template();
56106
}
57107
}
58108

59109
public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate> bc, CancellationToken ct,
60110
RequestManager requestManager)
61111
{
62112
Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug);
113+
Logger.Trace("SubmitQueryTemplatesRequestAsync starting...");
63114
try
64115
{
65116
var itemsProcessed = 0;
@@ -69,6 +120,7 @@ public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate>
69120
do
70121
{
71122
pageCounter++;
123+
Logger.Trace($"SubmitQueryTemplatesRequestAsync: Requesting page {pageCounter}");
72124
var batchItemsProcessed = 0;
73125
using (var resp = await RestClient.GetAsync("/KeyfactorApi/Templates"))
74126
{
@@ -80,66 +132,101 @@ public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate>
80132
retryCount++;
81133
if (retryCount > 5)
82134
throw new RetryCountExceededException(
83-
$"5 consecutive failures to {resp.RequestMessage.RequestUri}");
135+
$"5 consecutive failures to {resp.RequestMessage?.RequestUri}");
84136

85137
continue;
86138
}
87139

140+
retryCount = 0;
88141
var stringResponse = await resp.Content.ReadAsStringAsync();
142+
Logger.Trace($"SubmitQueryTemplatesRequestAsync: Response length={stringResponse?.Length ?? 0}");
89143

90144
var batchResponse =
91145
JsonConvert.DeserializeObject<List<Template>>(stringResponse);
92146

147+
if (batchResponse == null)
148+
{
149+
Logger.Warn("SubmitQueryTemplatesRequestAsync: Deserialized batch response is null. Ending pagination.");
150+
isComplete = true;
151+
break;
152+
}
153+
93154
var batchCount = batchResponse.Count;
155+
Logger.Trace($"Processing {batchCount} templates in batch");
156+
157+
if (batchCount == 0)
158+
{
159+
Logger.Trace("SubmitQueryTemplatesRequestAsync: Empty batch received. Ending pagination.");
160+
isComplete = true;
161+
break;
162+
}
94163

95-
Logger.Trace($"Processing {batchCount} items in batch");
96164
do
97165
{
98166
var r = batchResponse[batchItemsProcessed];
167+
if (r == null)
168+
{
169+
Logger.Warn($"SubmitQueryTemplatesRequestAsync: Null template at index {batchItemsProcessed}, skipping.");
170+
batchItemsProcessed++;
171+
continue;
172+
}
173+
99174
if (bc.TryAdd(r, 10, ct))
100175
{
101-
Logger.Trace($"Added Template ID {r.Id} to Queue for processing");
176+
Logger.Trace($"Added Template ID {r.Id}, CommonName={r.CommonName ?? "(null)"} to Queue for processing");
102177
batchItemsProcessed++;
103178
itemsProcessed++;
104179
Logger.Trace($"Processed {batchItemsProcessed} of {batchCount}");
105180
Logger.Trace($"Total Items Processed: {itemsProcessed}");
106181
}
107182
else
108183
{
109-
Logger.Trace($"Adding {r} blocked. Retry");
184+
Logger.Trace($"Adding template {r.Id} blocked. Retry");
110185
}
111186
} while (batchItemsProcessed < batchCount); //batch loop
112-
187+
113188
}
114189

115190
//assume that if we process less records than requested that we have reached the end of the certificate list
116191
if (batchItemsProcessed < PageSize)
117192
isComplete = true;
118193
} while (!isComplete); //page loop
119194

195+
Logger.Trace($"SubmitQueryTemplatesRequestAsync: Pagination complete. Total templates processed={itemsProcessed}");
120196
bc.CompleteAdding();
121197
}
122198
catch (OperationCanceledException cancelEx)
123199
{
124-
Logger.Warn($"Synchronize method was cancelled. Message: {cancelEx.Message}");
200+
Logger.Warn($"SubmitQueryTemplatesRequestAsync was cancelled. Message: {cancelEx.Message}");
125201
bc.CompleteAdding();
126202
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
127-
// ReSharper disable once PossibleIntendedRethrow
128-
throw cancelEx;
203+
throw;
129204
}
130205
catch (RetryCountExceededException retryEx)
131206
{
132-
Logger.Error($"Retries Failed: {retryEx.Message}");
207+
Logger.Error($"Retries Failed: {retryEx.Message}\n{retryEx.StackTrace}");
208+
bc.CompleteAdding();
133209
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
134210
}
135211
catch (HttpRequestException ex)
136212
{
137-
Logger.Error($"HttpRequest Failed: {ex.Message}");
213+
Logger.Error($"HttpRequest Failed: {ex.Message}\n{ex.StackTrace}");
214+
if (ex.InnerException != null)
215+
Logger.Error($"Inner exception: {ex.InnerException.Message}\n{ex.InnerException.StackTrace}");
216+
bc.CompleteAdding();
217+
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
218+
}
219+
catch (Exception ex)
220+
{
221+
Logger.Error($"SubmitQueryTemplatesRequestAsync unexpected error: {ex.Message}\n{ex.StackTrace}");
222+
if (ex.InnerException != null)
223+
Logger.Error($"Inner exception: {ex.InnerException.Message}\n{ex.InnerException.StackTrace}");
224+
bc.CompleteAdding();
138225
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
139226
}
140227

141228
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
142-
229+
143230
}
144231

145232

0 commit comments

Comments
 (0)