diff --git a/CHANGELOG.md b/CHANGELOG.md index c5bfbdb..3c0b34b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased - Infisical API error responses are now parsed to surface the server-side `message`, `error`, and `reqId` fields. The 4xx/5xx exception message includes the human-readable explanation (e.g. "The project is of type secret-manager") instead of an opaque `Infisical API returned 400 (Bad Request)`. The `InfisicalApiException` gains `ApiErrorMessage` and `ApiRequestId` properties; `InfisicalErrorDetails` carries the same fields so PowerShell error records and logger output expose them. +- `Get-InfisicalCertificateProfile` added with `List` (default) and `ById` parameter sets. List binds to `GET /api/v1/cert-manager/certificate-profiles` (optional `-Limit`, `-Offset`, `-IncludeConfigs`); ById binds to `GET /api/v1/cert-manager/certificate-profiles/{certificateProfileId}`. New `InfisicalCertificateProfile` model surfaces ca/policy ids, slug, enrollment type, per-profile defaults (ttl, key/extended key usages), and the embedded CA/policy/apiConfig summaries. ## 2026.06.04.1920 diff --git a/Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml b/Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml index 8540047..15a12f8 100644 --- a/Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml +++ b/Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml @@ -1128,6 +1128,48 @@ $GetInfisicalPkiSubscriberResult = Get-InfisicalPkiSubscriber @GetInfisicalPkiSu + + + Get-InfisicalCertificateProfile + Lists or retrieves Infisical certificate profiles in a project. + Get + InfisicalCertificateProfile + + + Default (List parameter set) returns every certificate profile configured on the project via /api/v1/cert-manager/certificate-profiles, with optional -Limit, -Offset, and -IncludeConfigs. When -ProfileId is supplied (ById parameter set) the cmdlet returns one profile by its id. -ProjectId defaults to the session-pinned project in both modes. + + + Notes + + Profiles bind a CA and a certificate policy and surface defaults (TtlDays, KeyAlgorithm, KeyUsages, ExtendedKeyUsages). Use the returned profile Id when wiring profile-based issuance against Request-InfisicalCertificate. + + + + + EXAMPLE 1 + Get-InfisicalCertificateProfile + Lists every certificate profile defined on the session-pinned project. + + + EXAMPLE 2 + Get-InfisicalCertificateProfile -ProfileId '8257641e-c808-454e-ac92-8dc920be865f' + Retrieves a single certificate profile by id from the session-pinned project. + + + EXAMPLE 3 + $GetInfisicalCertificateProfileListResult = Get-InfisicalCertificateProfile | Where-Object { $_.Slug -ieq 'codesigning' } + +$GetInfisicalCertificateProfileParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) +$GetInfisicalCertificateProfileParameters.ProfileId = $GetInfisicalCertificateProfileListResult[0].Id +$GetInfisicalCertificateProfileParameters.ProjectId = $ConnectInfisicalParameters.ProjectId +$GetInfisicalCertificateProfileParameters.Verbose = $True + +$GetInfisicalCertificateProfileResult = Get-InfisicalCertificateProfile @GetInfisicalCertificateProfileParameters + Filters profiles whose slug equals 'codesigning' and refetches the canonical record for the first match using a splatted parameter set. + + + + Search-InfisicalCertificate diff --git a/build.ps1 b/build.ps1 index 301e5ac..d5db70a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -131,6 +131,7 @@ function Write-Manifest { 'Remove-InfisicalTag', 'Get-InfisicalCertificateAuthority', 'Get-InfisicalPkiSubscriber', + 'Get-InfisicalCertificateProfile', 'Get-InfisicalCertificate', 'Search-InfisicalCertificate', 'Request-InfisicalCertificate', @@ -202,7 +203,7 @@ if (`$cmds.Count -eq 0) { throw "No cmdlets were exported by the PSInfisicalAPI module." } -`$expectedCmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','Copy-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag','Get-InfisicalCertificateAuthority','Get-InfisicalPkiSubscriber','Get-InfisicalCertificate','Search-InfisicalCertificate','Request-InfisicalCertificate','ConvertTo-InfisicalCertificate','Install-InfisicalCertificate','Uninstall-InfisicalCertificate','Export-InfisicalCertificate') +`$expectedCmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','Copy-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag','Get-InfisicalCertificateAuthority','Get-InfisicalPkiSubscriber','Get-InfisicalCertificateProfile','Get-InfisicalCertificate','Search-InfisicalCertificate','Request-InfisicalCertificate','ConvertTo-InfisicalCertificate','Install-InfisicalCertificate','Uninstall-InfisicalCertificate','Export-InfisicalCertificate') foreach (`$expected in `$expectedCmds) { if (-not (Get-Command -Name `$expected -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) { throw "Cmdlet not found: `$expected" diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalCertificateProfileCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalCertificateProfileCmdlet.cs new file mode 100644 index 0000000..7f20258 --- /dev/null +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalCertificateProfileCmdlet.cs @@ -0,0 +1,57 @@ +using System; +using System.Management.Automation; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Models; +using PSInfisicalAPI.Pki; + +namespace PSInfisicalAPI.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "InfisicalCertificateProfile", DefaultParameterSetName = "List")] + [OutputType(typeof(InfisicalCertificateProfile))] + public sealed class GetInfisicalCertificateProfileCmdlet : InfisicalCmdletBase + { + [Parameter(ParameterSetName = "ById", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Alias("Id", "CertificateProfileId")] + public string ProfileId { get; set; } + + [Parameter] public string ProjectId { get; set; } + + [Parameter(ParameterSetName = "List")] public int? Limit { get; set; } + + [Parameter(ParameterSetName = "List")] public int? Offset { get; set; } + + [Parameter(ParameterSetName = "List")] public SwitchParameter IncludeConfigs { get; set; } + + protected override void ProcessRecord() + { + try + { + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + + if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal)) + { + InfisicalCertificateProfile profile = client.GetCertificateProfile(connection, ProfileId, resolvedProjectId); + if (profile != null) + { + WriteObject(profile); + } + + return; + } + + bool? includeConfigs = MyInvocation.BoundParameters.ContainsKey("IncludeConfigs") ? (bool?)IncludeConfigs.IsPresent : null; + InfisicalCertificateProfile[] all = client.ListCertificateProfiles(connection, resolvedProjectId, Limit, Offset, includeConfigs); + foreach (InfisicalCertificateProfile profile in all) + { + WriteObject(profile); + } + } + catch (Exception exception) + { + ThrowTerminatingForException("GetInfisicalCertificateProfileCmdlet", "GetCertificateProfile", exception); + } + } + } +} diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs index a4b70de..779bba9 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs @@ -54,5 +54,8 @@ namespace PSInfisicalAPI.Endpoints public const string ListPkiSubscribers = "ListPkiSubscribers"; public const string GetPkiSubscriber = "GetPkiSubscriber"; + + public const string ListCertificateProfiles = "ListCertificateProfiles"; + public const string GetCertificateProfile = "GetCertificateProfile"; } } diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs index 24d9131..e1ae4fe 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs @@ -642,6 +642,26 @@ namespace PSInfisicalAPI.Endpoints Template = "/api/v1/pki/subscribers/{subscriberName}", RequiresAuthorization = true }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.ListCertificateProfiles, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/certificate-profiles", + RequiresAuthorization = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.GetCertificateProfile, + Resource = "Pki", + Version = "v1", + Method = "GET", + Template = "/api/v1/cert-manager/certificate-profiles/{certificateProfileId}", + RequiresAuthorization = true + }); } public static InfisicalEndpointDefinition Get(string name) diff --git a/src/PSInfisicalAPI/Models/InfisicalCertificateProfile.cs b/src/PSInfisicalAPI/Models/InfisicalCertificateProfile.cs new file mode 100644 index 0000000..0497414 --- /dev/null +++ b/src/PSInfisicalAPI/Models/InfisicalCertificateProfile.cs @@ -0,0 +1,58 @@ +using System; + +namespace PSInfisicalAPI.Models +{ + public sealed class InfisicalCertificateProfile + { + public string Id { get; set; } + public string ProjectId { get; set; } + public string CaId { get; set; } + public string CertificatePolicyId { get; set; } + public string Slug { get; set; } + public string Description { get; set; } + public string EnrollmentType { get; set; } + public string IssuerType { get; set; } + public string EstConfigId { get; set; } + public string ApiConfigId { get; set; } + public string AcmeConfigId { get; set; } + public string ScepConfigId { get; set; } + public DateTimeOffset? CreatedAtUtc { get; set; } + public DateTimeOffset? UpdatedAtUtc { get; set; } + public InfisicalCertificateProfileDefaults Defaults { get; set; } + public InfisicalCertificateAuthoritySummary CertificateAuthority { get; set; } + public InfisicalCertificatePolicySummary CertificatePolicy { get; set; } + public InfisicalCertificateProfileApiConfig ApiConfig { get; set; } + } + + public sealed class InfisicalCertificateProfileDefaults + { + public int? TtlDays { get; set; } + public string KeyAlgorithm { get; set; } + public string SignatureAlgorithm { get; set; } + public string[] KeyUsages { get; set; } + public string[] ExtendedKeyUsages { get; set; } + } + + public sealed class InfisicalCertificateAuthoritySummary + { + public string Id { get; set; } + public string Status { get; set; } + public string Name { get; set; } + public bool? IsExternal { get; set; } + public string ExternalType { get; set; } + } + + public sealed class InfisicalCertificatePolicySummary + { + public string Id { get; set; } + public string ProjectId { get; set; } + public string Name { get; set; } + } + + public sealed class InfisicalCertificateProfileApiConfig + { + public string Id { get; set; } + public bool? AutoRenew { get; set; } + public int? RenewBeforeDays { get; set; } + } +} diff --git a/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileDtos.cs b/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileDtos.cs new file mode 100644 index 0000000..8c9fb32 --- /dev/null +++ b/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileDtos.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PSInfisicalAPI.Pki +{ + internal sealed class InfisicalCertificateProfileResponseDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("projectId")] public string ProjectId { get; set; } + [JsonProperty("caId")] public string CaId { get; set; } + [JsonProperty("certificatePolicyId")] public string CertificatePolicyId { get; set; } + [JsonProperty("slug")] public string Slug { get; set; } + [JsonProperty("description")] public string Description { get; set; } + [JsonProperty("enrollmentType")] public string EnrollmentType { get; set; } + [JsonProperty("issuerType")] public string IssuerType { get; set; } + [JsonProperty("estConfigId")] public string EstConfigId { get; set; } + [JsonProperty("apiConfigId")] public string ApiConfigId { 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; } + [JsonProperty("defaults")] public InfisicalCertificateProfileDefaultsDto Defaults { get; set; } + [JsonProperty("certificateAuthority")] public InfisicalCertificateAuthoritySummaryDto CertificateAuthority { get; set; } + [JsonProperty("certificatePolicy")] public InfisicalCertificatePolicySummaryDto CertificatePolicy { get; set; } + [JsonProperty("apiConfig")] public InfisicalCertificateProfileApiConfigDto ApiConfig { get; set; } + } + + internal sealed class InfisicalCertificateProfileDefaultsDto + { + [JsonProperty("ttlDays")] public int? TtlDays { get; set; } + [JsonProperty("keyAlgorithm")] public string KeyAlgorithm { get; set; } + [JsonProperty("signatureAlgorithm")] public string SignatureAlgorithm { get; set; } + [JsonProperty("keyUsages")] public JToken KeyUsagesRaw { get; set; } + [JsonProperty("extendedKeyUsages")] public JToken ExtendedKeyUsagesRaw { get; set; } + } + + internal sealed class InfisicalCertificateAuthoritySummaryDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("status")] public string Status { get; set; } + [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("isExternal")] public bool? IsExternal { get; set; } + [JsonProperty("externalType")] public string ExternalType { get; set; } + } + + internal sealed class InfisicalCertificatePolicySummaryDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("projectId")] public string ProjectId { get; set; } + [JsonProperty("name")] public string Name { get; set; } + } + + internal sealed class InfisicalCertificateProfileApiConfigDto + { + [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("autoRenew")] public bool? AutoRenew { get; set; } + [JsonProperty("renewBeforeDays")] public int? RenewBeforeDays { get; set; } + } + + internal sealed class InfisicalCertificateProfileListResponseDto + { + [JsonProperty("certificateProfiles")] public List CertificateProfiles { get; set; } + [JsonProperty("totalCount")] public int? TotalCount { get; set; } + } +} diff --git a/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileMapper.cs b/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileMapper.cs new file mode 100644 index 0000000..7550ff1 --- /dev/null +++ b/src/PSInfisicalAPI/Pki/InfisicalCertificateProfileMapper.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json.Linq; +using PSInfisicalAPI.Models; + +namespace PSInfisicalAPI.Pki +{ + internal static class InfisicalCertificateProfileMapper + { + public static InfisicalCertificateProfile Map(InfisicalCertificateProfileResponseDto dto, string fallbackProjectId) + { + if (dto == null) + { + return null; + } + + return new InfisicalCertificateProfile + { + Id = dto.Id, + ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId, + CaId = dto.CaId, + CertificatePolicyId = dto.CertificatePolicyId, + Slug = dto.Slug, + Description = dto.Description, + EnrollmentType = dto.EnrollmentType, + IssuerType = dto.IssuerType, + EstConfigId = dto.EstConfigId, + ApiConfigId = dto.ApiConfigId, + AcmeConfigId = dto.AcmeConfigId, + ScepConfigId = dto.ScepConfigId, + CreatedAtUtc = ParseTimestamp(dto.CreatedAt), + UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt), + Defaults = MapDefaults(dto.Defaults), + CertificateAuthority = MapCa(dto.CertificateAuthority), + CertificatePolicy = MapPolicy(dto.CertificatePolicy), + ApiConfig = MapApiConfig(dto.ApiConfig) + }; + } + + public static InfisicalCertificateProfile[] MapMany(IEnumerable items, string fallbackProjectId) + { + if (items == null) + { + return Array.Empty(); + } + + List results = new List(); + foreach (InfisicalCertificateProfileResponseDto dto in items) + { + InfisicalCertificateProfile mapped = Map(dto, fallbackProjectId); + if (mapped != null) + { + results.Add(mapped); + } + } + + return results.ToArray(); + } + + private static InfisicalCertificateProfileDefaults MapDefaults(InfisicalCertificateProfileDefaultsDto dto) + { + if (dto == null) + { + return null; + } + + return new InfisicalCertificateProfileDefaults + { + TtlDays = dto.TtlDays, + KeyAlgorithm = dto.KeyAlgorithm, + SignatureAlgorithm = dto.SignatureAlgorithm, + KeyUsages = FlattenStringOrStringArray(dto.KeyUsagesRaw), + ExtendedKeyUsages = FlattenStringOrStringArray(dto.ExtendedKeyUsagesRaw) + }; + } + + private static InfisicalCertificateAuthoritySummary MapCa(InfisicalCertificateAuthoritySummaryDto dto) + { + if (dto == null) + { + return null; + } + + return new InfisicalCertificateAuthoritySummary + { + Id = dto.Id, + Status = dto.Status, + Name = dto.Name, + IsExternal = dto.IsExternal, + ExternalType = dto.ExternalType + }; + } + + private static InfisicalCertificatePolicySummary MapPolicy(InfisicalCertificatePolicySummaryDto dto) + { + if (dto == null) + { + return null; + } + + return new InfisicalCertificatePolicySummary + { + Id = dto.Id, + ProjectId = dto.ProjectId, + Name = dto.Name + }; + } + + private static InfisicalCertificateProfileApiConfig MapApiConfig(InfisicalCertificateProfileApiConfigDto dto) + { + if (dto == null) + { + return null; + } + + return new InfisicalCertificateProfileApiConfig + { + Id = dto.Id, + AutoRenew = dto.AutoRenew, + RenewBeforeDays = dto.RenewBeforeDays + }; + } + + internal static string[] FlattenStringOrStringArray(JToken token) + { + if (token == null || token.Type == JTokenType.Null) { return null; } + if (token.Type == JTokenType.String) { return new[] { (string)token }; } + if (token.Type == JTokenType.Array) + { + List items = new List(); + foreach (JToken child in (JArray)token) + { + if (child != null && child.Type == JTokenType.String) { items.Add((string)child); } + } + + return items.ToArray(); + } + + return null; + } + + 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 a098cf0..da03488 100644 --- a/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs +++ b/src/PSInfisicalAPI/Pki/InfisicalPkiClient.cs @@ -338,6 +338,95 @@ namespace PSInfisicalAPI.Pki return wrapper != null ? wrapper.Subscribers : null; } + public InfisicalCertificateProfile[] ListCertificateProfiles(InfisicalConnection connection, string projectId, int? limit, int? offset, bool? includeConfigs) + { + 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> + { + new KeyValuePair("projectId", resolvedProjectId) + }; + 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))); } + if (includeConfigs.HasValue) { query.Add(new KeyValuePair("includeConfigs", includeConfigs.Value ? "true" : "false")); } + + try + { + _logger.Information(Component, "Attempting to list Infisical certificate profiles. Please Wait..."); + InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListCertificateProfiles, "ListCertificateProfiles", null, query, null); + string body = response.Body; + response.Clear(); + + List source = ParseCertificateProfileListBody(body); + InfisicalCertificateProfile[] mapped = InfisicalCertificateProfileMapper.MapMany(source, resolvedProjectId); + _logger.Information(Component, "Infisical certificate profile list retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate profile list retrieval failed."); + throw; + } + } + + public InfisicalCertificateProfile GetCertificateProfile(InfisicalConnection connection, string certificateProfileId, string projectId) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (string.IsNullOrEmpty(certificateProfileId)) { throw new InfisicalConfigurationException("CertificateProfileId is required."); } + + Dictionary pathParameters = new Dictionary { { "certificateProfileId", certificateProfileId } }; + List> query = null; + if (!string.IsNullOrEmpty(projectId)) + { + query = new List> { new KeyValuePair("projectId", projectId) }; + } + + try + { + _logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate profile '", certificateProfileId, "'. Please Wait...")); + InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.GetCertificateProfile, "GetCertificateProfile", pathParameters, query, null); + string body = response.Body; + response.Clear(); + + InfisicalCertificateProfileResponseDto inner = ParseCertificateProfileSingleBody(body); + string fallbackProjectId = !string.IsNullOrEmpty(projectId) ? projectId : connection.ProjectId; + InfisicalCertificateProfile mapped = InfisicalCertificateProfileMapper.Map(inner, fallbackProjectId); + _logger.Information(Component, "Infisical certificate profile retrieval was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical certificate profile retrieval failed."); + throw; + } + } + + private List ParseCertificateProfileListBody(string body) + { + if (string.IsNullOrEmpty(body)) { return null; } + JToken token = JToken.Parse(body); + if (token.Type == JTokenType.Array) + { + return token.ToObject>(); + } + + InfisicalCertificateProfileListResponseDto wrapper = token.ToObject(); + return wrapper != null ? wrapper.CertificateProfiles : null; + } + + private InfisicalCertificateProfileResponseDto ParseCertificateProfileSingleBody(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["certificateProfile"] is JObject inner) { return inner.ToObject(); } + return obj.ToObject(); + } + public InfisicalCertificateBundle GetCertificateBundle(InfisicalConnection connection, string serialNumber) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); }