Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,6 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Claude Code
.claude/
151 changes: 119 additions & 32 deletions SslStoreCaProxy/Client/KeyfactorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,94 @@ public sealed class KeyfactorClient: LoggingClientBase, IKeyfactorClient

public KeyfactorClient(ICAConnectorConfigProvider configProvider)
{
var keyfactorBaseUrl = new Uri(configProvider.CAConnectionData[Constants.KeyfactorApiUrl].ToString());
var keyfactorAuth = configProvider.CAConnectionData[Constants.KeyfactorApiUserId] + ":" + configProvider.CAConnectionData[Constants.KeyfactorApiPassword];
var plainTextBytes = Encoding.UTF8.GetBytes(keyfactorAuth);

var clientHandler = new WebRequestHandler();
RestClient = new HttpClient(clientHandler, true) { BaseAddress = keyfactorBaseUrl };
RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
RestClient.DefaultRequestHeaders.Add("x-keyfactor-requested-with", "APIClient");
RestClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(plainTextBytes));
Logger.Trace("KeyfactorClient constructor called.");
try
{
if (configProvider?.CAConnectionData == null)
{
Logger.Error("KeyfactorClient: configProvider or CAConnectionData is null.");
throw new ArgumentNullException(nameof(configProvider), "configProvider or CAConnectionData is null.");
}

if (!configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiUrl))
{
Logger.Error($"KeyfactorClient: Missing required config key '{Constants.KeyfactorApiUrl}'.");
throw new InvalidOperationException($"Missing required config key '{Constants.KeyfactorApiUrl}'.");
}

var apiUrlValue = configProvider.CAConnectionData[Constants.KeyfactorApiUrl]?.ToString();
Logger.Trace($"KeyfactorClient: KeyfactorApiUrl={apiUrlValue ?? "(null)"}");

if (string.IsNullOrEmpty(apiUrlValue))
{
Logger.Error("KeyfactorClient: KeyfactorApiUrl value is null or empty.");
throw new InvalidOperationException("KeyfactorApiUrl value is null or empty.");
}

var keyfactorBaseUrl = new Uri(apiUrlValue);

var userId = configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiUserId)
? configProvider.CAConnectionData[Constants.KeyfactorApiUserId]?.ToString() ?? ""
: "";
var password = configProvider.CAConnectionData.ContainsKey(Constants.KeyfactorApiPassword)
? configProvider.CAConnectionData[Constants.KeyfactorApiPassword]?.ToString() ?? ""
: "";

if (string.IsNullOrEmpty(userId))
Logger.Warn("KeyfactorClient: KeyfactorApiUserId is null or empty.");

Logger.Trace($"KeyfactorClient: Configuring with userId={userId}, BaseAddress={keyfactorBaseUrl}");

var keyfactorAuth = userId + ":" + password;
var plainTextBytes = Encoding.UTF8.GetBytes(keyfactorAuth);

var clientHandler = new WebRequestHandler();
RestClient = new HttpClient(clientHandler, true) { BaseAddress = keyfactorBaseUrl };
RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
RestClient.DefaultRequestHeaders.Add("x-keyfactor-requested-with", "APIClient");
RestClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(plainTextBytes));

Logger.Trace("KeyfactorClient: RestClient configured successfully.");
}
catch (Exception ex)
{
Logger.Error($"KeyfactorClient constructor failed: {ex.Message}\n{ex.StackTrace}");
throw;
}
}

public async Task<Template> SubmitUpdateTemplateAsync(Template templateRequest)
{
using (var resp = await RestClient.PutAsync("/KeyfactorApi/Templates", new StringContent(
JsonConvert.SerializeObject(templateRequest), Encoding.ASCII, "application/json")))
Logger.Trace("SubmitUpdateTemplateAsync called.");
try
{
try
Logger.Trace($"SubmitUpdateTemplateAsync Request JSON: {JsonConvert.SerializeObject(templateRequest)}");
using (var resp = await RestClient.PutAsync("/KeyfactorApi/Templates", new StringContent(
JsonConvert.SerializeObject(templateRequest), Encoding.ASCII, "application/json")))
{
Logger.Trace(JsonConvert.SerializeObject(templateRequest));
var responseBody = await resp.Content.ReadAsStringAsync();
Logger.Trace($"SubmitUpdateTemplateAsync Response StatusCode={resp.StatusCode}, Body={responseBody}");
resp.EnsureSuccessStatusCode();
var templateResponse =
JsonConvert.DeserializeObject<Template>(await resp.Content.ReadAsStringAsync());
var templateResponse = JsonConvert.DeserializeObject<Template>(responseBody);
if (templateResponse == null)
Logger.Warn("SubmitUpdateTemplateAsync: Deserialized response is null.");
return templateResponse;
}
catch(Exception e)
{
Logger.Error($"Keyfactor API Error Occured Updating Keyfactor Template: {e.Message}");
return new Template();
}

}
catch (Exception e)
{
Logger.Error($"Keyfactor API Error Occurred Updating Keyfactor Template: {e.Message}\n{e.StackTrace}");
if (e.InnerException != null)
Logger.Error($"Inner exception: {e.InnerException.Message}\n{e.InnerException.StackTrace}");
return new Template();
}
}

public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate> bc, CancellationToken ct,
RequestManager requestManager)
{
Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug);
Logger.Trace("SubmitQueryTemplatesRequestAsync starting...");
try
{
var itemsProcessed = 0;
Expand All @@ -69,6 +120,7 @@ public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate>
do
{
pageCounter++;
Logger.Trace($"SubmitQueryTemplatesRequestAsync: Requesting page {pageCounter}");
var batchItemsProcessed = 0;
using (var resp = await RestClient.GetAsync("/KeyfactorApi/Templates"))
{
Expand All @@ -80,66 +132,101 @@ public async Task SubmitQueryTemplatesRequestAsync(BlockingCollection<ITemplate>
retryCount++;
if (retryCount > 5)
throw new RetryCountExceededException(
$"5 consecutive failures to {resp.RequestMessage.RequestUri}");
$"5 consecutive failures to {resp.RequestMessage?.RequestUri}");

continue;
}

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

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

if (batchResponse == null)
{
Logger.Warn("SubmitQueryTemplatesRequestAsync: Deserialized batch response is null. Ending pagination.");
isComplete = true;
break;
}

var batchCount = batchResponse.Count;
Logger.Trace($"Processing {batchCount} templates in batch");

if (batchCount == 0)
{
Logger.Trace("SubmitQueryTemplatesRequestAsync: Empty batch received. Ending pagination.");
isComplete = true;
break;
}

Logger.Trace($"Processing {batchCount} items in batch");
do
{
var r = batchResponse[batchItemsProcessed];
if (r == null)
{
Logger.Warn($"SubmitQueryTemplatesRequestAsync: Null template at index {batchItemsProcessed}, skipping.");
batchItemsProcessed++;
continue;
}

if (bc.TryAdd(r, 10, ct))
{
Logger.Trace($"Added Template ID {r.Id} to Queue for processing");
Logger.Trace($"Added Template ID {r.Id}, CommonName={r.CommonName ?? "(null)"} to Queue for processing");
batchItemsProcessed++;
itemsProcessed++;
Logger.Trace($"Processed {batchItemsProcessed} of {batchCount}");
Logger.Trace($"Total Items Processed: {itemsProcessed}");
}
else
{
Logger.Trace($"Adding {r} blocked. Retry");
Logger.Trace($"Adding template {r.Id} blocked. Retry");
}
} while (batchItemsProcessed < batchCount); //batch loop

}

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

Logger.Trace($"SubmitQueryTemplatesRequestAsync: Pagination complete. Total templates processed={itemsProcessed}");
bc.CompleteAdding();
}
catch (OperationCanceledException cancelEx)
{
Logger.Warn($"Synchronize method was cancelled. Message: {cancelEx.Message}");
Logger.Warn($"SubmitQueryTemplatesRequestAsync was cancelled. Message: {cancelEx.Message}");
bc.CompleteAdding();
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
// ReSharper disable once PossibleIntendedRethrow
throw cancelEx;
throw;
}
catch (RetryCountExceededException retryEx)
{
Logger.Error($"Retries Failed: {retryEx.Message}");
Logger.Error($"Retries Failed: {retryEx.Message}\n{retryEx.StackTrace}");
bc.CompleteAdding();
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
}
catch (HttpRequestException ex)
{
Logger.Error($"HttpRequest Failed: {ex.Message}");
Logger.Error($"HttpRequest Failed: {ex.Message}\n{ex.StackTrace}");
if (ex.InnerException != null)
Logger.Error($"Inner exception: {ex.InnerException.Message}\n{ex.InnerException.StackTrace}");
bc.CompleteAdding();
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
}
catch (Exception ex)
{
Logger.Error($"SubmitQueryTemplatesRequestAsync unexpected error: {ex.Message}\n{ex.StackTrace}");
if (ex.InnerException != null)
Logger.Error($"Inner exception: {ex.InnerException.Message}\n{ex.InnerException.StackTrace}");
bc.CompleteAdding();
Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);
}

Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug);

}


Expand Down
Loading
Loading