diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs index fa32617..678514f 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs @@ -63,5 +63,13 @@ namespace PSInfisicalAPI.Endpoints public const string GetCertificatePolicy = "GetCertificatePolicy"; public const string ListCertificateAuthorities = "ListCertificateAuthorities"; + + public const string ListCertificateApplications = "ListCertificateApplications"; + public const string GetCertificateApplication = "GetCertificateApplication"; + public const string GetCertificateApplicationByName = "GetCertificateApplicationByName"; + public const string ListCertificateApplicationProfiles = "ListCertificateApplicationProfiles"; + public const string GetCertificateApplicationEnrollment = "GetCertificateApplicationEnrollment"; + + public const string GenerateScepDynamicChallenge = "GenerateScepDynamicChallenge"; } } diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs index 242a202..e3f5ac4 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs @@ -703,6 +703,67 @@ namespace PSInfisicalAPI.Endpoints Template = "/api/v1/cert-manager/ca", RequiresAuthorization = true }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.ListCertificateApplications, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/applications", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.GetCertificateApplication, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/applications/{applicationId}", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.GetCertificateApplicationByName, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/applications/by-name/{name}", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.ListCertificateApplicationProfiles, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/applications/{applicationId}/profiles", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.GetCertificateApplicationEnrollment, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/applications/{applicationId}/profiles/{profileId}/enrollment", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.GenerateScepDynamicChallenge, + Resource = "Pki", + Version = "v1", + Method = "POST", + Template = "/scep/applications/{applicationId}/profiles/{profileId}/challenge", + RequiresAuthorization = true, + ContainsSecretMaterialInResponse = true + }); } public static InfisicalEndpointDefinition Get(string name) diff --git a/src/PSInfisicalAPI/Http/InfisicalApiInvoker.cs b/src/PSInfisicalAPI/Http/InfisicalApiInvoker.cs index a3e2b1a..7e88cf7 100644 --- a/src/PSInfisicalAPI/Http/InfisicalApiInvoker.cs +++ b/src/PSInfisicalAPI/Http/InfisicalApiInvoker.cs @@ -23,7 +23,8 @@ namespace PSInfisicalAPI.Http string operationName, IDictionary pathParameters, IEnumerable> queryParameters, - string body) + string body, + IDictionary extraHeaders = null) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); } @@ -31,7 +32,7 @@ namespace PSInfisicalAPI.Http InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName); Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters); - InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body); + InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body, extraHeaders); if (response.StatusCode >= 200 && response.StatusCode < 300) { @@ -49,7 +50,8 @@ namespace PSInfisicalAPI.Http string operationName, IDictionary pathParameters, IEnumerable> queryParameters, - string body) + string body, + IDictionary extraHeaders = null) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); } @@ -61,7 +63,7 @@ namespace PSInfisicalAPI.Http { InfisicalEndpointDefinition definition = candidates[index]; Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters); - InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body); + InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body, extraHeaders); if (response.StatusCode >= 200 && response.StatusCode < 300) { @@ -95,7 +97,8 @@ namespace PSInfisicalAPI.Http InfisicalEndpointDefinition definition, string operationName, Uri uri, - string body) + string body, + IDictionary extraHeaders = null) { Dictionary headers = new Dictionary(StringComparer.OrdinalIgnoreCase); headers["Accept"] = "application/json"; @@ -118,6 +121,15 @@ namespace PSInfisicalAPI.Http }); } + if (extraHeaders != null) + { + foreach (KeyValuePair entry in extraHeaders) + { + if (string.IsNullOrEmpty(entry.Key)) { continue; } + headers[entry.Key] = entry.Value; + } + } + InfisicalHttpRequest request = new InfisicalHttpRequest { OperationName = operationName, diff --git a/src/PSInfisicalAPI/Models/InfisicalCertificateApplication.cs b/src/PSInfisicalAPI/Models/InfisicalCertificateApplication.cs new file mode 100644 index 0000000..170e366 --- /dev/null +++ b/src/PSInfisicalAPI/Models/InfisicalCertificateApplication.cs @@ -0,0 +1,31 @@ +using System; + +namespace PSInfisicalAPI.Models +{ + public sealed class InfisicalCertificateApplication + { + public string Id { get; set; } + public string ProjectId { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public int? ProfileCount { get; set; } + public int? MemberCount { get; set; } + public int? CertificateCount { get; set; } + public DateTimeOffset? CreatedAtUtc { get; set; } + public DateTimeOffset? UpdatedAtUtc { get; set; } + } + + public sealed class InfisicalCertificateApplicationProfileAttachment + { + public string ApplicationId { get; set; } + public string ProfileId { get; set; } + public string ProfileSlug { get; set; } + public string ProfileDescription { get; set; } + public string ApiConfigId { get; set; } + public string EstConfigId { get; set; } + public string AcmeConfigId { get; set; } + public string ScepConfigId { get; set; } + public DateTimeOffset? CreatedAtUtc { get; set; } + public DateTimeOffset? UpdatedAtUtc { get; set; } + } +} diff --git a/src/PSInfisicalAPI/Models/InfisicalCertificateApplicationEnrollment.cs b/src/PSInfisicalAPI/Models/InfisicalCertificateApplicationEnrollment.cs new file mode 100644 index 0000000..1a5a9ef --- /dev/null +++ b/src/PSInfisicalAPI/Models/InfisicalCertificateApplicationEnrollment.cs @@ -0,0 +1,55 @@ +using System; + +namespace PSInfisicalAPI.Models +{ + public sealed class InfisicalCertificateApplicationEnrollment + { + public string ApplicationId { get; set; } + public string ProfileId { get; set; } + public InfisicalCertificateApplicationApiEnrollment Api { get; set; } + public InfisicalCertificateApplicationEstEnrollment Est { get; set; } + public InfisicalCertificateApplicationAcmeEnrollment Acme { get; set; } + public InfisicalCertificateApplicationScepEnrollment Scep { get; set; } + public bool ApiConfigured { get { return Api != null; } } + public bool EstConfigured { get; set; } + public bool AcmeConfigured { get; set; } + public bool ScepConfigured { get; set; } + } + + public sealed class InfisicalCertificateApplicationApiEnrollment + { + public string Id { get; set; } + public bool? AutoRenew { get; set; } + public int? RenewBeforeDays { get; set; } + } + + public sealed class InfisicalCertificateApplicationEstEnrollment + { + public string Id { get; set; } + public bool? DisableBootstrapCaValidation { get; set; } + public string EstEndpointUrl { get; set; } + } + + public sealed class InfisicalCertificateApplicationAcmeEnrollment + { + public string Id { get; set; } + public bool? SkipDnsOwnershipVerification { get; set; } + public bool? SkipEabBinding { get; set; } + public string DirectoryUrl { get; set; } + } + + public sealed class InfisicalCertificateApplicationScepEnrollment + { + public string Id { get; set; } + public string ChallengeType { get; set; } + public bool? IncludeCaCertInResponse { get; set; } + public bool? AllowCertBasedRenewal { get; set; } + public int? DynamicChallengeExpiryMinutes { get; set; } + public int? DynamicChallengeMaxPending { get; set; } + public string ScepEndpointUrl { get; set; } + public string ChallengeEndpointUrl { get; set; } + public string RaCertificatePem { get; set; } + public string RaCertificateThumbprint { get; set; } + public DateTimeOffset? RaCertExpiresAtUtc { get; set; } + } +} diff --git a/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationDtos.cs b/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationDtos.cs new file mode 100644 index 0000000..356c616 --- /dev/null +++ b/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationDtos.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace PSInfisicalAPI.Pki +{ + internal sealed class InfisicalCertificateApplicationResponseDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("projectId")] public string ProjectId { get; set; } + [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("description")] public string Description { get; set; } + [JsonProperty("profileCount")] public int? ProfileCount { get; set; } + [JsonProperty("memberCount")] public int? MemberCount { get; set; } + [JsonProperty("certificateCount")] public int? CertificateCount { get; set; } + [JsonProperty("createdAt")] public string CreatedAt { get; set; } + [JsonProperty("updatedAt")] public string UpdatedAt { get; set; } + } + + internal sealed class InfisicalCertificateApplicationListResponseDto + { + [JsonProperty("applications")] public List Applications { get; set; } + [JsonProperty("total")] public int? Total { get; set; } + } + + internal sealed class InfisicalCertificateApplicationProfileAttachmentDto + { + [JsonProperty("applicationId")] public string ApplicationId { get; set; } + [JsonProperty("profileId")] public string ProfileId { get; set; } + [JsonProperty("profileSlug")] public string ProfileSlug { get; set; } + [JsonProperty("profileDescription")] public string ProfileDescription { get; set; } + [JsonProperty("apiConfigId")] public string ApiConfigId { get; set; } + [JsonProperty("estConfigId")] public string EstConfigId { get; set; } + [JsonProperty("acmeConfigId")] public string AcmeConfigId { get; set; } + [JsonProperty("scepConfigId")] public string ScepConfigId { get; set; } + [JsonProperty("createdAt")] public string CreatedAt { get; set; } + [JsonProperty("updatedAt")] public string UpdatedAt { get; set; } + } + + internal sealed class InfisicalCertificateApplicationProfilesResponseDto + { + [JsonProperty("profiles")] public List Profiles { get; set; } + } + + internal sealed class InfisicalCertificateApplicationEnrollmentResponseDto + { + [JsonProperty("applicationId")] public string ApplicationId { get; set; } + [JsonProperty("profileId")] public string ProfileId { get; set; } + [JsonProperty("api")] public InfisicalCertificateApplicationApiEnrollmentDto Api { get; set; } + [JsonProperty("est")] public InfisicalCertificateApplicationEstEnrollmentDto Est { get; set; } + [JsonProperty("acme")] public InfisicalCertificateApplicationAcmeEnrollmentDto Acme { get; set; } + [JsonProperty("scep")] public InfisicalCertificateApplicationScepEnrollmentDto Scep { get; set; } + [JsonProperty("estConfigured")] public bool? EstConfigured { get; set; } + [JsonProperty("acmeConfigured")] public bool? AcmeConfigured { get; set; } + [JsonProperty("scepConfigured")] public bool? ScepConfigured { get; set; } + } + + internal sealed class InfisicalCertificateApplicationApiEnrollmentDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("autoRenew")] public bool? AutoRenew { get; set; } + [JsonProperty("renewBeforeDays")] public int? RenewBeforeDays { get; set; } + } + + internal sealed class InfisicalCertificateApplicationEstEnrollmentDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("disableBootstrapCaValidation")] public bool? DisableBootstrapCaValidation { get; set; } + [JsonProperty("estEndpointUrl")] public string EstEndpointUrl { get; set; } + } + + internal sealed class InfisicalCertificateApplicationAcmeEnrollmentDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("skipDnsOwnershipVerification")] public bool? SkipDnsOwnershipVerification { get; set; } + [JsonProperty("skipEabBinding")] public bool? SkipEabBinding { get; set; } + [JsonProperty("directoryUrl")] public string DirectoryUrl { get; set; } + } + + internal sealed class InfisicalCertificateApplicationScepEnrollmentDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("challengeType")] public string ChallengeType { get; set; } + [JsonProperty("includeCaCertInResponse")] public bool? IncludeCaCertInResponse { get; set; } + [JsonProperty("allowCertBasedRenewal")] public bool? AllowCertBasedRenewal { get; set; } + [JsonProperty("dynamicChallengeExpiryMinutes")] public int? DynamicChallengeExpiryMinutes { get; set; } + [JsonProperty("dynamicChallengeMaxPending")] public int? DynamicChallengeMaxPending { get; set; } + [JsonProperty("scepEndpointUrl")] public string ScepEndpointUrl { get; set; } + [JsonProperty("challengeEndpointUrl")] public string ChallengeEndpointUrl { get; set; } + [JsonProperty("raCertificatePem")] public string RaCertificatePem { get; set; } + [JsonProperty("raCertExpiresAt")] public string RaCertExpiresAt { get; set; } + } +} diff --git a/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationMapper.cs b/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationMapper.cs new file mode 100644 index 0000000..7798243 --- /dev/null +++ b/src/PSInfisicalAPI/Pki/InfisicalCertificateApplicationMapper.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using PSInfisicalAPI.Models; + +namespace PSInfisicalAPI.Pki +{ + internal static class InfisicalCertificateApplicationMapper + { + public static InfisicalCertificateApplication Map(InfisicalCertificateApplicationResponseDto dto, string fallbackProjectId) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplication + { + Id = dto.Id, + ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId, + Name = dto.Name, + Description = dto.Description, + ProfileCount = dto.ProfileCount, + MemberCount = dto.MemberCount, + CertificateCount = dto.CertificateCount, + CreatedAtUtc = ParseTimestamp(dto.CreatedAt), + UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt) + }; + } + + public static InfisicalCertificateApplication[] MapMany(IEnumerable items, string fallbackProjectId) + { + if (items == null) { return Array.Empty(); } + List results = new List(); + foreach (InfisicalCertificateApplicationResponseDto dto in items) + { + InfisicalCertificateApplication mapped = Map(dto, fallbackProjectId); + if (mapped != null) { results.Add(mapped); } + } + + return results.ToArray(); + } + + public static InfisicalCertificateApplicationProfileAttachment MapAttachment(InfisicalCertificateApplicationProfileAttachmentDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationProfileAttachment + { + ApplicationId = dto.ApplicationId, + ProfileId = dto.ProfileId, + ProfileSlug = dto.ProfileSlug, + ProfileDescription = dto.ProfileDescription, + ApiConfigId = dto.ApiConfigId, + EstConfigId = dto.EstConfigId, + AcmeConfigId = dto.AcmeConfigId, + ScepConfigId = dto.ScepConfigId, + CreatedAtUtc = ParseTimestamp(dto.CreatedAt), + UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt) + }; + } + + public static InfisicalCertificateApplicationProfileAttachment[] MapAttachments(IEnumerable items) + { + if (items == null) { return Array.Empty(); } + List results = new List(); + foreach (InfisicalCertificateApplicationProfileAttachmentDto dto in items) + { + InfisicalCertificateApplicationProfileAttachment mapped = MapAttachment(dto); + if (mapped != null) { results.Add(mapped); } + } + + return results.ToArray(); + } + + public static InfisicalCertificateApplicationEnrollment MapEnrollment(InfisicalCertificateApplicationEnrollmentResponseDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationEnrollment + { + ApplicationId = dto.ApplicationId, + ProfileId = dto.ProfileId, + Api = MapApi(dto.Api), + Est = MapEst(dto.Est), + Acme = MapAcme(dto.Acme), + Scep = MapScep(dto.Scep), + EstConfigured = dto.EstConfigured.GetValueOrDefault(), + AcmeConfigured = dto.AcmeConfigured.GetValueOrDefault(), + ScepConfigured = dto.ScepConfigured.GetValueOrDefault() + }; + } + + private static InfisicalCertificateApplicationApiEnrollment MapApi(InfisicalCertificateApplicationApiEnrollmentDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationApiEnrollment { Id = dto.Id, AutoRenew = dto.AutoRenew, RenewBeforeDays = dto.RenewBeforeDays }; + } + + private static InfisicalCertificateApplicationEstEnrollment MapEst(InfisicalCertificateApplicationEstEnrollmentDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationEstEnrollment { Id = dto.Id, DisableBootstrapCaValidation = dto.DisableBootstrapCaValidation, EstEndpointUrl = dto.EstEndpointUrl }; + } + + private static InfisicalCertificateApplicationAcmeEnrollment MapAcme(InfisicalCertificateApplicationAcmeEnrollmentDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationAcmeEnrollment { Id = dto.Id, SkipDnsOwnershipVerification = dto.SkipDnsOwnershipVerification, SkipEabBinding = dto.SkipEabBinding, DirectoryUrl = dto.DirectoryUrl }; + } + + private static InfisicalCertificateApplicationScepEnrollment MapScep(InfisicalCertificateApplicationScepEnrollmentDto dto) + { + if (dto == null) { return null; } + return new InfisicalCertificateApplicationScepEnrollment + { + Id = dto.Id, + ChallengeType = dto.ChallengeType, + IncludeCaCertInResponse = dto.IncludeCaCertInResponse, + AllowCertBasedRenewal = dto.AllowCertBasedRenewal, + DynamicChallengeExpiryMinutes = dto.DynamicChallengeExpiryMinutes, + DynamicChallengeMaxPending = dto.DynamicChallengeMaxPending, + ScepEndpointUrl = dto.ScepEndpointUrl, + ChallengeEndpointUrl = dto.ChallengeEndpointUrl, + RaCertificatePem = dto.RaCertificatePem, + RaCertificateThumbprint = ComputeThumbprint(dto.RaCertificatePem), + RaCertExpiresAtUtc = ParseTimestamp(dto.RaCertExpiresAt) + }; + } + + internal static string ComputeThumbprint(string pem) + { + if (string.IsNullOrEmpty(pem)) { return null; } + try + { + byte[] der = Convert.FromBase64String(StripPemArmor(pem)); + using (X509Certificate2 cert = new X509Certificate2(der)) + { + return cert.Thumbprint; + } + } + catch + { + return null; + } + } + + private static string StripPemArmor(string pem) + { + StringBuilder sb = new StringBuilder(pem.Length); + using (StringReader reader = new StringReader(pem)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + string trimmed = line.Trim(); + if (trimmed.Length == 0) { continue; } + if (trimmed.StartsWith("-----", StringComparison.Ordinal)) { continue; } + sb.Append(trimmed); + } + } + + return sb.ToString(); + } + + private static DateTimeOffset? ParseTimestamp(string value) + { + if (string.IsNullOrEmpty(value)) { return null; } + DateTimeOffset parsed; + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed)) + { + return parsed; + } + + return null; + } + } +} diff --git a/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs b/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs index e3e06b6..cda0afc 100644 --- a/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs +++ b/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs @@ -640,6 +640,233 @@ namespace PSInfisicalAPI.Pki } } + public InfisicalCertificateApplication[] ListCertificateApplications(InfisicalConnection connection, string projectId, int? limit, int? offset) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + + List> query = new List>(); + if (limit.HasValue) { query.Add(new KeyValuePair("limit", limit.Value.ToString(CultureInfo.InvariantCulture))); } + if (offset.HasValue) { query.Add(new KeyValuePair("offset", offset.Value.ToString(CultureInfo.InvariantCulture))); } + + Dictionary headers = BuildProjectHeader(resolvedProjectId); + + try + { + _logger.Information(Component, "Attempting to list Infisical certificate applications. Please Wait..."); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListCertificateApplications, "ListCertificateApplications", null, query, null, headers); + string body = response.Body; + response.Clear(); + + List source = ParseApplicationListBody(body); + InfisicalCertificateApplication[] mapped = InfisicalCertificateApplicationMapper.MapMany(source, resolvedProjectId); + _logger.Information(Component, "Infisical certificate application list retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate application list retrieval failed."); + throw; + } + } + + public InfisicalCertificateApplication GetCertificateApplication(InfisicalConnection connection, string applicationId, string projectId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); } + + Dictionary pathParameters = new Dictionary { { "applicationId", applicationId } }; + string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId); + Dictionary headers = !string.IsNullOrEmpty(resolvedProjectId) ? BuildProjectHeader(resolvedProjectId) : null; + + try + { + _logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate application '", applicationId, "'. Please Wait...")); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplication, "GetCertificateApplication", pathParameters, null, null, headers); + string body = response.Body; + response.Clear(); + + InfisicalCertificateApplicationResponseDto inner = ParseApplicationSingleBody(body); + InfisicalCertificateApplication mapped = InfisicalCertificateApplicationMapper.Map(inner, resolvedProjectId); + _logger.Information(Component, "Infisical certificate application retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate application retrieval failed."); + throw; + } + } + + public InfisicalCertificateApplication GetCertificateApplicationByName(InfisicalConnection connection, string name, string projectId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("ApplicationName is required."); } + string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + + Dictionary pathParameters = new Dictionary { { "name", name } }; + Dictionary headers = BuildProjectHeader(resolvedProjectId); + + try + { + _logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate application '", name, "' by name. Please Wait...")); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplicationByName, "GetCertificateApplicationByName", pathParameters, null, null, headers); + string body = response.Body; + response.Clear(); + + InfisicalCertificateApplicationResponseDto inner = ParseApplicationSingleBody(body); + InfisicalCertificateApplication mapped = InfisicalCertificateApplicationMapper.Map(inner, resolvedProjectId); + _logger.Information(Component, "Infisical certificate application (by name) retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate application (by name) retrieval failed."); + throw; + } + } + + public InfisicalCertificateApplicationProfileAttachment[] ListCertificateApplicationProfiles(InfisicalConnection connection, string applicationId, string projectId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); } + + Dictionary pathParameters = new Dictionary { { "applicationId", applicationId } }; + string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId); + Dictionary headers = !string.IsNullOrEmpty(resolvedProjectId) ? BuildProjectHeader(resolvedProjectId) : null; + + try + { + _logger.Information(Component, string.Concat("Attempting to list profile attachments for Infisical certificate application '", applicationId, "'. Please Wait...")); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListCertificateApplicationProfiles, "ListCertificateApplicationProfiles", pathParameters, null, null, headers); + string body = response.Body; + response.Clear(); + + List source = ParseApplicationProfilesBody(body); + InfisicalCertificateApplicationProfileAttachment[] mapped = InfisicalCertificateApplicationMapper.MapAttachments(source); + _logger.Information(Component, "Infisical certificate application profile attachment listing was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate application profile attachment listing failed."); + throw; + } + } + + public InfisicalCertificateApplicationEnrollment GetCertificateApplicationEnrollment(InfisicalConnection connection, string applicationId, string profileId, string projectId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); } + if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); } + + Dictionary pathParameters = new Dictionary + { + { "applicationId", applicationId }, + { "profileId", profileId } + }; + string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId); + Dictionary headers = !string.IsNullOrEmpty(resolvedProjectId) ? BuildProjectHeader(resolvedProjectId) : null; + + try + { + _logger.Information(Component, string.Concat("Attempting to retrieve enrollment for application '", applicationId, "' / profile '", profileId, "'. Please Wait...")); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplicationEnrollment, "GetCertificateApplicationEnrollment", pathParameters, null, null, headers); + InfisicalCertificateApplicationEnrollmentResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalCertificateApplicationEnrollment mapped = InfisicalCertificateApplicationMapper.MapEnrollment(dto); + _logger.Information(Component, "Infisical certificate application enrollment retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate application enrollment retrieval failed."); + throw; + } + } + + public string GenerateScepDynamicChallenge(InfisicalConnection connection, string applicationId, string profileId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); } + if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); } + + Dictionary pathParameters = new Dictionary + { + { "applicationId", applicationId }, + { "profileId", profileId } + }; + Dictionary headers = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Accept", "text/plain" } + }; + + try + { + _logger.Information(Component, string.Concat("Attempting to generate SCEP dynamic challenge for application '", applicationId, "' / profile '", profileId, "'. Please Wait...")); + InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GenerateScepDynamicChallenge, "GenerateScepDynamicChallenge", pathParameters, null, string.Empty, headers); + string body = response.Body != null ? response.Body.Trim() : null; + response.Clear(); + + if (string.IsNullOrEmpty(body)) { throw new InfisicalApiException("SCEP dynamic challenge response was empty."); } + _logger.Information(Component, "Infisical SCEP dynamic challenge generation was successful."); + return body; + } + catch (Exception) + { + _logger.Error(Component, "Infisical SCEP dynamic challenge generation failed."); + throw; + } + } + + private static Dictionary BuildProjectHeader(string projectId) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "x-infisical-project-id", projectId } + }; + } + + private List ParseApplicationListBody(string body) + { + if (string.IsNullOrEmpty(body)) { return null; } + JToken token = JToken.Parse(body); + if (token.Type == JTokenType.Array) + { + return token.ToObject>(); + } + + InfisicalCertificateApplicationListResponseDto wrapper = token.ToObject(); + return wrapper != null ? wrapper.Applications : null; + } + + private InfisicalCertificateApplicationResponseDto ParseApplicationSingleBody(string body) + { + if (string.IsNullOrEmpty(body)) { return null; } + JToken token = JToken.Parse(body); + if (token.Type != JTokenType.Object) { return null; } + JObject obj = (JObject)token; + + if (obj["application"] is JObject inner) { return inner.ToObject(); } + return obj.ToObject(); + } + + private List ParseApplicationProfilesBody(string body) + { + if (string.IsNullOrEmpty(body)) { return null; } + JToken token = JToken.Parse(body); + if (token.Type == JTokenType.Array) + { + return token.ToObject>(); + } + + InfisicalCertificateApplicationProfilesResponseDto wrapper = token.ToObject(); + return wrapper != null ? wrapper.Profiles : null; + } + internal static InfisicalCertificateSearchRequestDto BuildSearchRequest(InfisicalCertificateSearchQuery query) { return new InfisicalCertificateSearchRequestDto