Tests: roll forward to latest major .NET runtime #3

Merged
gsadmin merged 12 commits from dev into main 2026-06-04 00:47:39 +00:00
76 changed files with 5638 additions and 126 deletions
+47
View File
@@ -6,6 +6,53 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased ## Unreleased
## 2026.06.04.0020
- Build produced from commit 211fbcf34dbb.
## Unreleased (carried forward)
## 2026.06.04.0005
- Build produced from commit e0a6ef02df3e.
## Unreleased (carried forward)
- **Bulk v4 batch routes**: Endpoint registry now registers `POST|PATCH|DELETE /api/v4/secrets/batch` as the preferred candidates for `BulkCreateSecret`/`BulkUpdateSecret`/`BulkDeleteSecret`; the existing v3 raw routes (`/api/v3/secrets/batch/raw`) remain as automatic fallback. Batch request DTOs serialize both `projectId` (required by v4) and `workspaceId` (accepted by v3) when populated.
- **Strongly-typed bulk input**: `-Secrets` on `New-InfisicalSecret` and `Update-InfisicalSecret` is now `IDictionary<string, string>[]` instead of `Hashtable[]`. `InfisicalBulkSecretConverter` accepts `IEnumerable<IDictionary<string, string>>` and parses `TagIds` from a comma-separated string. Nested `Metadata`/`SecretMetadata` dictionaries are no longer accepted in the bulk hashtable surface (set `SecretMetadata` programmatically on `InfisicalBulkCreateSecretItem`/`InfisicalBulkUpdateSecretItem` if needed).
## 2026.06.03.2207
- Build produced from commit 09c3d5c68bbc.
- **M9 — Bulk, Duplicate & Inheritance**:
- **Bulk parameter sets** added to `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret` accepting `-Secrets Hashtable[]`; client methods `CreateBatch`/`UpdateBatch`/`DeleteBatch` wrap `POST|PATCH|DELETE /api/v3/secrets/batch/raw`.
- **`Copy-InfisicalSecret`** cmdlet added, wrapping `POST /api/v4/secrets/duplicate` with source/destination environment + path parameters and per-attribute copy toggles.
- **Connection inheritance** centralized in `InfisicalCmdletBase` (`ResolveProjectId`/`ResolveEnvironment`/`ResolveSecretPath`/`ResolveApiVersion`/`ResolveOrganizationId`). Explicit parameters always win; missing values fall back to the active connection and emit a `-Verbose` line.
- Project/Environment/Folder/Tag and all secret cmdlets refactored to use the inheritance helpers; existing explicit-parameter behavior is preserved.
- `InfisicalBulkSecretConverter` accepts flexible key aliases (`SecretName`/`Name`/`Key`, `SecretValue`/`Value`, `SecretComment`/`Comment`, `Metadata`/`SecretMetadata`).
- Test count: 161 (up from 139). Added coverage for bulk DTO shapes, the converter, the duplicate request DTO, registry entries for the four new endpoints, and the resolution helpers.
## Unreleased (carried forward)
## 2026.06.03.2206
- Build produced from commit 09c3d5c68bbc.
## Unreleased (carried forward)
## 2026.06.03.2136
- Build produced from commit d9822aab7a4a.
- **Resource CRUD expansion**: Added full Get/New/Update/Remove cmdlet families for Projects, Environments, Folders, and Tags (20 new cmdlets):
- Projects: `Get-InfisicalProjects`, `Get-InfisicalProject`, `New-InfisicalProject`, `Update-InfisicalProject`, `Remove-InfisicalProject`.
- Environments: `Get-InfisicalEnvironments`, `Get-InfisicalEnvironment`, `New-InfisicalEnvironment`, `Update-InfisicalEnvironment`, `Remove-InfisicalEnvironment`.
- Folders: `Get-InfisicalFolders`, `Get-InfisicalFolder`, `New-InfisicalFolder`, `Update-InfisicalFolder`, `Remove-InfisicalFolder`.
- Tags: `Get-InfisicalTags`, `Get-InfisicalTag`, `New-InfisicalTag`, `Update-InfisicalTag`, `Remove-InfisicalTag`.
- **Secret mutation cmdlets**: Added `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret`; extended `InfisicalSecretsClient` with corresponding create/update/delete operations.
- **Additional auth providers**: `Connect-Infisical` now supports JWT (`-Jwt -IdentityId`), OIDC (`-Jwt -IdentityId`), LDAP (`-Username -Password`), Azure (`-Jwt -IdentityId`), and GCP IAM (`-Jwt -IdentityId`) via dedicated parameter sets. Common identity-login flow is centralized in `IdentityLoginExecutor`.
- Endpoint registry expanded with login routes (`/api/v1/auth/{jwt|oidc|ldap|azure|gcp}-auth/login`) and CRUD routes for projects (v2), environments, folders, tags, and secret mutations.
- Test suite expanded to 139 passing tests, including mapper round-trips for projects/environments/folders/tags, secret mutation DTO shapes, and request-body validation for each new auth provider.
## 2026.06.03.0131 ## 2026.06.03.0131
- Build produced from commit 7be0b7b42008. - Build produced from commit 7be0b7b42008.
+27 -3
View File
@@ -1,6 +1,6 @@
@{ @{
RootModule = 'PSInfisicalAPI.psm1' RootModule = 'PSInfisicalAPI.psm1'
ModuleVersion = '2026.06.03.0131' ModuleVersion = '2026.06.04.0020'
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
Author = 'Grace Solutions' Author = 'Grace Solutions'
CompanyName = 'Grace Solutions' CompanyName = 'Grace Solutions'
@@ -14,8 +14,32 @@
'Disconnect-Infisical', 'Disconnect-Infisical',
'Get-InfisicalSecrets', 'Get-InfisicalSecrets',
'Get-InfisicalSecret', 'Get-InfisicalSecret',
'New-InfisicalSecret',
'Update-InfisicalSecret',
'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets' 'Export-InfisicalSecrets',
'Get-InfisicalProjects',
'Get-InfisicalProject',
'New-InfisicalProject',
'Update-InfisicalProject',
'Remove-InfisicalProject',
'Get-InfisicalEnvironments',
'Get-InfisicalEnvironment',
'New-InfisicalEnvironment',
'Update-InfisicalEnvironment',
'Remove-InfisicalEnvironment',
'Get-InfisicalFolders',
'Get-InfisicalFolder',
'New-InfisicalFolder',
'Update-InfisicalFolder',
'Remove-InfisicalFolder',
'Get-InfisicalTags',
'Get-InfisicalTag',
'New-InfisicalTag',
'Update-InfisicalTag',
'Remove-InfisicalTag'
) )
AliasesToExport = @() AliasesToExport = @()
VariablesToExport = @() VariablesToExport = @()
@@ -27,7 +51,7 @@
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
CommitHash = '7be0b7b42008' CommitHash = '211fbcf34dbb'
} }
} }
} }
Binary file not shown.
+26 -2
View File
@@ -102,8 +102,32 @@ function Write-Manifest {
'Disconnect-Infisical', 'Disconnect-Infisical',
'Get-InfisicalSecrets', 'Get-InfisicalSecrets',
'Get-InfisicalSecret', 'Get-InfisicalSecret',
'New-InfisicalSecret',
'Update-InfisicalSecret',
'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets' 'Export-InfisicalSecrets',
'Get-InfisicalProjects',
'Get-InfisicalProject',
'New-InfisicalProject',
'Update-InfisicalProject',
'Remove-InfisicalProject',
'Get-InfisicalEnvironments',
'Get-InfisicalEnvironment',
'New-InfisicalEnvironment',
'Update-InfisicalEnvironment',
'Remove-InfisicalEnvironment',
'Get-InfisicalFolders',
'Get-InfisicalFolder',
'New-InfisicalFolder',
'Update-InfisicalFolder',
'Remove-InfisicalFolder',
'Get-InfisicalTags',
'Get-InfisicalTag',
'New-InfisicalTag',
'Update-InfisicalTag',
'Remove-InfisicalTag'
) )
AliasesToExport = @() AliasesToExport = @()
VariablesToExport = @() VariablesToExport = @()
@@ -163,7 +187,7 @@ if (`$null -eq `$manifest) {
Import-Module -Name '$($ModuleDirectory.FullName)' -Force Import-Module -Name '$($ModuleDirectory.FullName)' -Force
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets') `$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProjects','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironments','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolders','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTags','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag')
foreach (`$c in `$cmds) { foreach (`$c in `$cmds) {
if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) { if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
throw "Cmdlet not found: `$c" throw "Cmdlet not found: `$c"
+59 -11
View File
@@ -6,15 +6,39 @@
The goal is to establish a strong, reusable, secure framework first, then initially implement secret retrieval and export workflows. The goal is to establish a strong, reusable, secure framework first, then initially implement secret retrieval and export workflows.
Initial public cmdlets: Public cmdlets:
```powershell ```powershell
Connect-Infisical Connect-Infisical
Disconnect-Infisical Disconnect-Infisical
Get-InfisicalSecrets Get-InfisicalSecrets
Get-InfisicalSecret Get-InfisicalSecret
New-InfisicalSecret
Update-InfisicalSecret
Remove-InfisicalSecret
Copy-InfisicalSecret
ConvertTo-InfisicalSecretDictionary ConvertTo-InfisicalSecretDictionary
Export-InfisicalSecrets Export-InfisicalSecrets
Get-InfisicalProjects
Get-InfisicalProject
New-InfisicalProject
Update-InfisicalProject
Remove-InfisicalProject
Get-InfisicalEnvironments
Get-InfisicalEnvironment
New-InfisicalEnvironment
Update-InfisicalEnvironment
Remove-InfisicalEnvironment
Get-InfisicalFolders
Get-InfisicalFolder
New-InfisicalFolder
Update-InfisicalFolder
Remove-InfisicalFolder
Get-InfisicalTags
Get-InfisicalTag
New-InfisicalTag
Update-InfisicalTag
Remove-InfisicalTag
``` ```
Infisicals public API is REST-based and provides programmatic access for managing secrets and related resources. Current Infisical documentation shows the list-secrets endpoint under `/api/v4/secrets`, the single-secret retrieval endpoint under `/api/v4/secrets/{secretName}`, and Universal Auth login under `/api/v1/auth/universal-auth/login`. The implementation must centralize API endpoint definitions because Infisical uses different API versions across resource families. ([Infisical Blog][1]) Infisicals public API is REST-based and provides programmatic access for managing secrets and related resources. Current Infisical documentation shows the list-secrets endpoint under `/api/v4/secrets`, the single-secret retrieval endpoint under `/api/v4/secrets/{secretName}`, and Universal Auth login under `/api/v1/auth/universal-auth/login`. The implementation must centralize API endpoint definitions because Infisical uses different API versions across resource families. ([Infisical Blog][1])
@@ -198,8 +222,32 @@ Example shape:
'Disconnect-Infisical', 'Disconnect-Infisical',
'Get-InfisicalSecrets', 'Get-InfisicalSecrets',
'Get-InfisicalSecret', 'Get-InfisicalSecret',
'New-InfisicalSecret',
'Update-InfisicalSecret',
'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets' 'Export-InfisicalSecrets',
'Get-InfisicalProjects',
'Get-InfisicalProject',
'New-InfisicalProject',
'Update-InfisicalProject',
'Remove-InfisicalProject',
'Get-InfisicalEnvironments',
'Get-InfisicalEnvironment',
'New-InfisicalEnvironment',
'Update-InfisicalEnvironment',
'Remove-InfisicalEnvironment',
'Get-InfisicalFolders',
'Get-InfisicalFolder',
'New-InfisicalFolder',
'Update-InfisicalFolder',
'Remove-InfisicalFolder',
'Get-InfisicalTags',
'Get-InfisicalTag',
'New-InfisicalTag',
'Update-InfisicalTag',
'Remove-InfisicalTag'
) )
AliasesToExport = @() AliasesToExport = @()
PrivateData = @{ PrivateData = @{
@@ -698,29 +746,29 @@ Internal implementation must still use proper typed path handling.
# 12. Authentication Design # 12. Authentication Design
## 12.1 Supported Initial Auth Types ## 12.1 Supported Auth Types
Initial implementation: Currently implemented:
```text ```text
Universal Auth Universal Auth
Token Auth Token Auth
JWT Auth
OIDC Auth
LDAP Auth
Azure Auth
GCP IAM Auth
``` ```
Infisical documents identity authentication modes such as Universal Auth and Token Auth for API access, and API interaction requires an access token. ([Infisical Blog][3]) Each implemented provider is exposed as a dedicated `Connect-Infisical` parameter set. Identity-based providers (JWT, OIDC, Azure, GCP IAM) share a common login flow via `IdentityLoginExecutor` and POST to `/api/v1/auth/{provider}-auth/login`. Infisical documents identity authentication modes such as Universal Auth and Token Auth for API access, and API interaction requires an access token. ([Infisical Blog][3])
## 12.2 Future Auth Types ## 12.2 Future Auth Types
Design must allow future support for: Design must allow future support for:
```text ```text
AWS Auth AWS IAM Auth
Azure Auth
GCP Auth
Kubernetes Auth Kubernetes Auth
OIDC Auth
JWT Auth
LDAP Auth
TLS Certificate Auth TLS Certificate Auth
Alibaba Cloud Auth Alibaba Cloud Auth
OCI Auth OCI Auth
@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PSInfisicalAPI.Authentication;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Security;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class AuthProviderTests
{
private sealed class CapturingHttpClient : IInfisicalHttpClient
{
public InfisicalHttpRequest CapturedRequest { get; private set; }
public string ResponseBody { get; set; } = "{\"accessToken\":\"abc.def.ghi\",\"expiresIn\":3600,\"tokenType\":\"Bearer\"}";
public InfisicalHttpResponse Send(InfisicalHttpRequest request)
{
CapturedRequest = request;
return new InfisicalHttpResponse
{
StatusCode = 200,
Body = ResponseBody,
Headers = new Dictionary<string, string>()
};
}
}
private static InfisicalAuthenticationRequest BaseRequest()
{
return new InfisicalAuthenticationRequest
{
BaseUri = new Uri("https://example.invalid"),
ApiVersion = "v1"
};
}
[Fact]
public void JwtAuthProvider_Posts_IdentityId_And_Jwt()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.IdentityId = "identity-1";
request.Jwt = SecureStringUtility.ToReadOnlySecureString("token.value");
InfisicalAuthenticationResult result = new JwtAuthProvider().Authenticate(request, http, null);
Assert.NotNull(result);
Assert.NotNull(http.CapturedRequest);
Assert.Equal("POST", http.CapturedRequest.Method);
Assert.EndsWith("/api/v1/auth/jwt-auth/login", http.CapturedRequest.Uri.AbsolutePath);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("identity-1", (string)body["identityId"]);
Assert.Equal("token.value", (string)body["jwt"]);
}
[Fact]
public void OidcAuthProvider_Posts_IdentityId_And_Jwt_To_Oidc_Endpoint()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.IdentityId = "identity-2";
request.Jwt = SecureStringUtility.ToReadOnlySecureString("oidc.token");
new OidcAuthProvider().Authenticate(request, http, null);
Assert.EndsWith("/api/v1/auth/oidc-auth/login", http.CapturedRequest.Uri.AbsolutePath);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("identity-2", (string)body["identityId"]);
Assert.Equal("oidc.token", (string)body["jwt"]);
}
[Fact]
public void LdapAuthProvider_Posts_Username_And_Password_To_Ldap_Endpoint()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.Username = "svc.account";
request.Password = SecureStringUtility.ToReadOnlySecureString("P@ssw0rd!");
new LdapAuthProvider().Authenticate(request, http, null);
Assert.EndsWith("/api/v1/auth/ldap-auth/login", http.CapturedRequest.Uri.AbsolutePath);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("svc.account", (string)body["username"]);
Assert.Equal("P@ssw0rd!", (string)body["password"]);
Assert.False(body.ContainsKey("identityId"));
}
[Fact]
public void LdapAuthProvider_Includes_IdentityId_When_Supplied()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.Username = "u";
request.Password = SecureStringUtility.ToReadOnlySecureString("p");
request.IdentityId = "id-ldap";
new LdapAuthProvider().Authenticate(request, http, null);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("id-ldap", (string)body["identityId"]);
}
[Fact]
public void AzureAuthProvider_Posts_IdentityId_And_Jwt_To_Azure_Endpoint()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.IdentityId = "identity-az";
request.Jwt = SecureStringUtility.ToReadOnlySecureString("az.token");
new AzureAuthProvider().Authenticate(request, http, null);
Assert.EndsWith("/api/v1/auth/azure-auth/login", http.CapturedRequest.Uri.AbsolutePath);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("identity-az", (string)body["identityId"]);
Assert.Equal("az.token", (string)body["jwt"]);
}
[Fact]
public void GcpIamAuthProvider_Posts_IdentityId_And_Jwt_To_Gcp_Endpoint()
{
CapturingHttpClient http = new CapturingHttpClient();
InfisicalAuthenticationRequest request = BaseRequest();
request.IdentityId = "identity-gcp";
request.Jwt = SecureStringUtility.ToReadOnlySecureString("gcp.token");
new GcpIamAuthProvider().Authenticate(request, http, null);
Assert.EndsWith("/api/v1/auth/gcp-auth/login", http.CapturedRequest.Uri.AbsolutePath);
JObject body = JObject.Parse(http.CapturedRequest.Body);
Assert.Equal("identity-gcp", (string)body["identityId"]);
Assert.Equal("gcp.token", (string)body["jwt"]);
}
[Fact]
public void JwtAuthProvider_Throws_When_IdentityId_Missing()
{
InfisicalAuthenticationRequest request = BaseRequest();
request.Jwt = SecureStringUtility.ToReadOnlySecureString("x");
Assert.Throws<InfisicalAuthenticationException>(() =>
new JwtAuthProvider().Authenticate(request, new CapturingHttpClient(), null));
}
[Fact]
public void LdapAuthProvider_Throws_When_Password_Missing()
{
InfisicalAuthenticationRequest request = BaseRequest();
request.Username = "u";
Assert.Throws<InfisicalAuthenticationException>(() =>
new LdapAuthProvider().Authenticate(request, new CapturingHttpClient(), null));
}
}
}
@@ -0,0 +1,209 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Management.Automation;
using PSInfisicalAPI.Cmdlets;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Secrets;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class BulkSecretConverterTests
{
[Fact]
public void ToCreateItems_Maps_Standard_Keys()
{
Dictionary<string, string> entry = new Dictionary<string, string>
{
{ "SecretName", "API_KEY" },
{ "SecretValue", "abc" },
{ "SecretComment", "primary" },
{ "SkipMultilineEncoding", "true" },
{ "TagIds", "tag-1,tag-2" }
};
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry });
Assert.Single(items);
Assert.Equal("API_KEY", items[0].SecretName);
Assert.Equal("abc", items[0].SecretValue);
Assert.Equal("primary", items[0].SecretComment);
Assert.True(items[0].SkipMultilineEncoding);
Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds);
}
[Fact]
public void ToCreateItems_Accepts_Name_Alias_For_SecretName()
{
Dictionary<string, string> entry = new Dictionary<string, string>
{
{ "Name", "API_KEY" },
{ "Value", "abc" }
};
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry });
Assert.Single(items);
Assert.Equal("API_KEY", items[0].SecretName);
Assert.Equal("abc", items[0].SecretValue);
}
[Fact]
public void ToCreateItems_Without_SecretName_Throws()
{
Dictionary<string, string> entry = new Dictionary<string, string> { { "Value", "abc" } };
Assert.Throws<InfisicalConfigurationException>(() =>
InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry }));
}
[Fact]
public void ToUpdateItems_Maps_NewSecretName()
{
Dictionary<string, string> entry = new Dictionary<string, string>
{
{ "SecretName", "API_KEY" },
{ "NewSecretName", "RENAMED" },
{ "SecretValue", "new-value" }
};
InfisicalBulkUpdateSecretItem[] items = InfisicalBulkSecretConverter.ToUpdateItems(new IDictionary<string, string>[] { entry });
Assert.Single(items);
Assert.Equal("API_KEY", items[0].SecretName);
Assert.Equal("RENAMED", items[0].NewSecretName);
Assert.Equal("new-value", items[0].SecretValue);
}
[Fact]
public void ToCreateItems_Trims_TagId_Whitespace_And_Skips_Empty()
{
Dictionary<string, string> entry = new Dictionary<string, string>
{
{ "SecretName", "API_KEY" },
{ "SecretValue", "abc" },
{ "TagIds", " tag-1 , , tag-2 " }
};
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry });
Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds);
}
[Fact]
public void ToCreateItems_Empty_Input_Returns_Empty()
{
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(null);
Assert.Empty(items);
}
}
public class BulkSecretsTransformationAttributeTests
{
private static IDictionary<string, string>[] Transform(object input)
{
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
object result = attribute.Transform(null, input);
return (IDictionary<string, string>[])result;
}
[Fact]
public void Transform_Single_Hashtable_Wraps_Into_Array()
{
Hashtable input = new Hashtable { { "SecretName", "API_KEY" }, { "SecretValue", "abc" } };
IDictionary<string, string>[] result = Transform(input);
Assert.Single(result);
Assert.Equal("API_KEY", result[0]["SecretName"]);
Assert.Equal("abc", result[0]["SecretValue"]);
}
[Fact]
public void Transform_Hashtable_Array_Preserves_Order()
{
Hashtable a = new Hashtable { { "SecretName", "A" }, { "SecretValue", "1" } };
Hashtable b = new Hashtable { { "SecretName", "B" }, { "SecretValue", "2" } };
IDictionary<string, string>[] result = Transform(new object[] { a, b });
Assert.Equal(2, result.Length);
Assert.Equal("A", result[0]["SecretName"]);
Assert.Equal("B", result[1]["SecretName"]);
}
[Fact]
public void Transform_OrderedDictionary_Converts_To_StringString()
{
OrderedDictionary input = new OrderedDictionary();
input.Add("SecretName", "API_KEY");
input.Add("SkipMultilineEncoding", true);
IDictionary<string, string>[] result = Transform(input);
Assert.Single(result);
Assert.Equal("API_KEY", result[0]["SecretName"]);
Assert.Equal("true", result[0]["SkipMultilineEncoding"]);
}
[Fact]
public void Transform_Array_Values_Are_Joined_Comma_Separated()
{
Hashtable input = new Hashtable
{
{ "SecretName", "API_KEY" },
{ "TagIds", new[] { "tag-1", "tag-2" } }
};
IDictionary<string, string>[] result = Transform(input);
Assert.Equal("tag-1,tag-2", result[0]["TagIds"]);
}
[Fact]
public void Transform_Already_Typed_Array_Passes_Through()
{
IDictionary<string, string>[] input = new IDictionary<string, string>[]
{
new Dictionary<string, string> { { "SecretName", "A" }, { "SecretValue", "1" } }
};
IDictionary<string, string>[] result = Transform(input);
Assert.Same(input, result);
}
[Fact]
public void Transform_Null_Input_Returns_Null()
{
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
Assert.Null(attribute.Transform(null, null));
}
[Fact]
public void Transform_Invalid_Element_Throws_ArgumentTransformationMetadataException()
{
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
Assert.Throws<ArgumentTransformationMetadataException>(() =>
attribute.Transform(null, new object[] { "not-a-dictionary" }));
}
[Fact]
public void Transform_End_To_End_With_Converter_Produces_Bulk_Items()
{
Hashtable entry = new Hashtable
{
{ "SecretName", "API_KEY" },
{ "SecretValue", "abc" },
{ "TagIds", new[] { "tag-1", "tag-2" } },
{ "SkipMultilineEncoding", true }
};
IDictionary<string, string>[] transformed = Transform(new object[] { entry });
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(transformed);
Assert.Single(items);
Assert.Equal("API_KEY", items[0].SecretName);
Assert.Equal("abc", items[0].SecretValue);
Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds);
Assert.True(items[0].SkipMultilineEncoding);
}
}
}
@@ -0,0 +1,166 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class BulkSecretDtoTests
{
private static readonly System.Reflection.Assembly ModuleAssembly =
typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
private static object MakeDto(string typeName)
{
System.Type t = ModuleAssembly.GetType(typeName, true);
return System.Activator.CreateInstance(t);
}
[Fact]
public void BatchCreateItem_Serializes_With_Expected_Field_Names()
{
object item = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateItemDto");
item.GetType().GetProperty("SecretKey").SetValue(item, "API_KEY");
item.GetType().GetProperty("SecretValue").SetValue(item, "abc");
JObject json = JObject.Parse(JsonConvert.SerializeObject(item));
Assert.Equal("API_KEY", (string)json["secretKey"]);
Assert.Equal("abc", (string)json["secretValue"]);
Assert.False(json.ContainsKey("skipMultilineEncoding"));
Assert.False(json.ContainsKey("tagIds"));
Assert.False(json.ContainsKey("secretMetadata"));
}
[Fact]
public void BatchUpdateItem_Omits_Null_Optional_Fields()
{
object item = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchUpdateItemDto");
item.GetType().GetProperty("SecretKey").SetValue(item, "API_KEY");
item.GetType().GetProperty("NewSecretName").SetValue(item, "RENAMED");
JObject json = JObject.Parse(JsonConvert.SerializeObject(item));
Assert.Equal("API_KEY", (string)json["secretKey"]);
Assert.Equal("RENAMED", (string)json["newSecretName"]);
Assert.False(json.ContainsKey("secretValue"));
Assert.False(json.ContainsKey("secretComment"));
}
[Fact]
public void BatchCreateRequest_Serializes_With_Expected_Envelope()
{
System.Type itemType = ModuleAssembly.GetType("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateItemDto", true);
object item = System.Activator.CreateInstance(itemType);
itemType.GetProperty("SecretKey").SetValue(item, "K1");
itemType.GetProperty("SecretValue").SetValue(item, "V1");
System.Type listType = typeof(List<>).MakeGenericType(itemType);
object list = System.Activator.CreateInstance(listType);
listType.GetMethod("Add").Invoke(list, new object[] { item });
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db");
dto.GetType().GetProperty("Secrets").SetValue(dto, list);
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["workspaceId"]);
Assert.Equal("wks-1", (string)json["projectId"]);
Assert.Equal("prod", (string)json["environment"]);
Assert.Equal("/db", (string)json["secretPath"]);
JArray secrets = (JArray)json["secrets"];
Assert.Single(secrets);
Assert.Equal("K1", (string)secrets[0]["secretKey"]);
Assert.Equal("V1", (string)secrets[0]["secretValue"]);
}
[Fact]
public void BatchCreateRequest_Omits_Null_WorkspaceId_When_Only_ProjectId_Set()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateRequestDto");
dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.False(json.ContainsKey("workspaceId"));
Assert.Equal("wks-1", (string)json["projectId"]);
}
[Fact]
public void BatchUpdateRequest_Includes_ProjectId_Alongside_WorkspaceId()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchUpdateRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["workspaceId"]);
Assert.Equal("wks-1", (string)json["projectId"]);
}
[Fact]
public void BatchDeleteRequest_Serializes_With_Secret_Keys()
{
System.Type itemType = ModuleAssembly.GetType("PSInfisicalAPI.Secrets.InfisicalSecretBatchDeleteItemDto", true);
object item = System.Activator.CreateInstance(itemType);
itemType.GetProperty("SecretKey").SetValue(item, "K1");
System.Type listType = typeof(List<>).MakeGenericType(itemType);
object list = System.Activator.CreateInstance(listType);
listType.GetMethod("Add").Invoke(list, new object[] { item });
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchDeleteRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
dto.GetType().GetProperty("Secrets").SetValue(dto, list);
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["workspaceId"]);
Assert.Equal("wks-1", (string)json["projectId"]);
JArray secrets = (JArray)json["secrets"];
Assert.Single(secrets);
Assert.Equal("K1", (string)secrets[0]["secretKey"]);
}
[Fact]
public void DuplicateRequest_Serializes_With_Expected_Field_Names()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDuplicateRequestDto");
dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("SourceEnvironment").SetValue(dto, "dev");
dto.GetType().GetProperty("DestinationEnvironment").SetValue(dto, "prod");
dto.GetType().GetProperty("SourceSecretPath").SetValue(dto, "/db");
dto.GetType().GetProperty("DestinationSecretPath").SetValue(dto, "/db");
dto.GetType().GetProperty("SecretIds").SetValue(dto, new[] { "id-1", "id-2" });
dto.GetType().GetProperty("OverwriteExisting").SetValue(dto, true);
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["projectId"]);
Assert.Equal("dev", (string)json["sourceEnvironment"]);
Assert.Equal("prod", (string)json["destinationEnvironment"]);
Assert.Equal("/db", (string)json["sourceSecretPath"]);
Assert.Equal("/db", (string)json["destinationSecretPath"]);
Assert.True((bool)json["overwriteExisting"]);
JArray ids = (JArray)json["secretIds"];
Assert.Equal(2, ids.Count);
Assert.Equal("id-1", (string)ids[0]);
Assert.False(json.ContainsKey("attributesToCopy"));
}
[Fact]
public void DuplicateAttributes_Omits_Null_Toggles()
{
object attrs = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDuplicateAttributesDto");
attrs.GetType().GetProperty("SecretValue").SetValue(attrs, true);
JObject json = JObject.Parse(JsonConvert.SerializeObject(attrs));
Assert.True((bool)json["secretValue"]);
Assert.False(json.ContainsKey("secretComment"));
Assert.False(json.ContainsKey("tags"));
Assert.False(json.ContainsKey("metadata"));
}
}
}
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Reflection;
using PSInfisicalAPI.Cmdlets;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class CmdletBaseInheritanceTests
{
private sealed class RecordingLogger : IInfisicalLogger
{
public List<string> VerboseEntries { get; } = new List<string>();
public void Information(string component, string message) { }
public void Verbose(string component, string message) { VerboseEntries.Add(message); }
public void Debug(string component, string message) { }
public void Warning(string component, string message) { }
public void Error(string component, string message) { }
}
[Cmdlet(VerbsCommon.Get, "TestCmdlet")]
private sealed class TestCmdlet : InfisicalCmdletBase
{
public string CallResolveProjectId(InfisicalConnection connection, string explicitValue)
{
return ResolveProjectId(connection, explicitValue);
}
public string CallResolveEnvironment(InfisicalConnection connection, string explicitValue)
{
return ResolveEnvironment(connection, explicitValue);
}
public string CallResolveSecretPath(InfisicalConnection connection, string explicitValue)
{
return ResolveSecretPath(connection, explicitValue);
}
public string CallResolveApiVersion(InfisicalConnection connection, string explicitValue)
{
return ResolveApiVersion(connection, explicitValue);
}
public string CallResolveOrganizationId(InfisicalConnection connection, string explicitValue)
{
return ResolveOrganizationId(connection, explicitValue);
}
}
private static TestCmdlet CreateCmdletWith(RecordingLogger logger)
{
TestCmdlet cmdlet = new TestCmdlet();
FieldInfo field = typeof(InfisicalCmdletBase).GetField("_logger", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(cmdlet, logger);
return cmdlet;
}
private static InfisicalConnection ConnectionWithDefaults()
{
return new InfisicalConnection
{
BaseUri = new Uri("https://app.example.com"),
ProjectId = "proj-conn",
Environment = "prod-conn",
DefaultSecretPath = "/db",
OrganizationId = "org-conn",
PinnedApiVersion = "v3"
};
}
[Fact]
public void Explicit_Value_Overrides_Connection_And_Does_Not_Log()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), "explicit-proj");
Assert.Equal("explicit-proj", resolved);
Assert.Empty(logger.VerboseEntries);
}
[Fact]
public void Missing_Value_Inherits_From_Connection_And_Logs()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), null);
Assert.Equal("proj-conn", resolved);
Assert.Single(logger.VerboseEntries);
Assert.Contains("Inherited ProjectId", logger.VerboseEntries[0]);
Assert.Contains("proj-conn", logger.VerboseEntries[0]);
}
[Fact]
public void ResolveSecretPath_Defaults_To_Root_When_Connection_Has_No_Default()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
InfisicalConnection bareConnection = new InfisicalConnection { BaseUri = new Uri("https://app.example.com") };
string resolved = cmdlet.CallResolveSecretPath(bareConnection, null);
Assert.Equal("/", resolved);
}
[Fact]
public void ResolveSecretPath_Inherits_From_Connection_When_Set()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveSecretPath(ConnectionWithDefaults(), null);
Assert.Equal("/db", resolved);
Assert.Contains(logger.VerboseEntries, v => v.Contains("SecretPath") && v.Contains("/db"));
}
[Fact]
public void ResolveApiVersion_Prefers_PinnedApiVersion_From_Connection()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveApiVersion(ConnectionWithDefaults(), null);
Assert.Equal("v3", resolved);
}
[Fact]
public void ResolveEnvironment_And_ResolveOrganizationId_Inherit()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
Assert.Equal("prod-conn", cmdlet.CallResolveEnvironment(ConnectionWithDefaults(), null));
Assert.Equal("org-conn", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), null));
Assert.Equal(2, logger.VerboseEntries.Count);
}
}
}
@@ -45,5 +45,55 @@ namespace PSInfisicalAPI.Tests
{ {
Assert.Throws<InfisicalConfigurationException>(() => InfisicalEndpointRegistry.Get("NotARealEndpoint")); Assert.Throws<InfisicalConfigurationException>(() => InfisicalEndpointRegistry.Get("NotARealEndpoint"));
} }
[Theory]
[InlineData(InfisicalEndpointNames.CreateSecret, "POST", "/api/v3/secrets/raw/{secretName}")]
[InlineData(InfisicalEndpointNames.UpdateSecret, "PATCH", "/api/v3/secrets/raw/{secretName}")]
[InlineData(InfisicalEndpointNames.DeleteSecret, "DELETE", "/api/v3/secrets/raw/{secretName}")]
[InlineData(InfisicalEndpointNames.ListProjects, "GET", "/api/v1/workspace")]
[InlineData(InfisicalEndpointNames.RetrieveProject, "GET", "/api/v1/workspace/{projectId}")]
[InlineData(InfisicalEndpointNames.CreateProject, "POST", "/api/v2/workspace")]
[InlineData(InfisicalEndpointNames.UpdateProject, "PATCH", "/api/v1/workspace/{projectId}")]
[InlineData(InfisicalEndpointNames.DeleteProject, "DELETE", "/api/v1/workspace/{projectId}")]
[InlineData(InfisicalEndpointNames.CreateEnvironment, "POST", "/api/v1/workspace/{projectId}/environments")]
[InlineData(InfisicalEndpointNames.UpdateEnvironment, "PATCH", "/api/v1/workspace/{projectId}/environments/{environmentId}")]
[InlineData(InfisicalEndpointNames.DeleteEnvironment, "DELETE", "/api/v1/workspace/{projectId}/environments/{environmentId}")]
[InlineData(InfisicalEndpointNames.ListFolders, "GET", "/api/v1/folders")]
[InlineData(InfisicalEndpointNames.CreateFolder, "POST", "/api/v1/folders")]
[InlineData(InfisicalEndpointNames.UpdateFolder, "PATCH", "/api/v1/folders/{folderId}")]
[InlineData(InfisicalEndpointNames.DeleteFolder, "DELETE", "/api/v1/folders/{folderId}")]
[InlineData(InfisicalEndpointNames.ListTags, "GET", "/api/v1/workspace/{projectId}/tags")]
[InlineData(InfisicalEndpointNames.CreateTag, "POST", "/api/v1/workspace/{projectId}/tags")]
[InlineData(InfisicalEndpointNames.UpdateTag, "PATCH", "/api/v1/workspace/{projectId}/tags/{tagId}")]
[InlineData(InfisicalEndpointNames.DeleteTag, "DELETE", "/api/v1/workspace/{projectId}/tags/{tagId}")]
[InlineData(InfisicalEndpointNames.JwtAuthLogin, "POST", "/api/v1/auth/jwt-auth/login")]
[InlineData(InfisicalEndpointNames.OidcAuthLogin, "POST", "/api/v1/auth/oidc-auth/login")]
[InlineData(InfisicalEndpointNames.LdapAuthLogin, "POST", "/api/v1/auth/ldap-auth/login")]
[InlineData(InfisicalEndpointNames.AzureAuthLogin, "POST", "/api/v1/auth/azure-auth/login")]
[InlineData(InfisicalEndpointNames.GcpIamAuthLogin, "POST", "/api/v1/auth/gcp-auth/login")]
[InlineData(InfisicalEndpointNames.BulkCreateSecret, "POST", "/api/v4/secrets/batch")]
[InlineData(InfisicalEndpointNames.BulkUpdateSecret, "PATCH", "/api/v4/secrets/batch")]
[InlineData(InfisicalEndpointNames.BulkDeleteSecret, "DELETE", "/api/v4/secrets/batch")]
[InlineData(InfisicalEndpointNames.DuplicateSecret, "POST", "/api/v4/secrets/duplicate")]
public void Registered_Endpoints_Have_Expected_Shape(string name, string method, string template)
{
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(name);
Assert.Equal(method, definition.Method);
Assert.Equal(template, definition.Template);
}
[Theory]
[InlineData(InfisicalEndpointNames.BulkCreateSecret, "POST", "/api/v3/secrets/batch/raw")]
[InlineData(InfisicalEndpointNames.BulkUpdateSecret, "PATCH", "/api/v3/secrets/batch/raw")]
[InlineData(InfisicalEndpointNames.BulkDeleteSecret, "DELETE", "/api/v3/secrets/batch/raw")]
public void Bulk_Endpoints_Retain_V3_Fallback_Candidate(string name, string method, string template)
{
System.Collections.Generic.IReadOnlyList<InfisicalEndpointDefinition> candidates = InfisicalEndpointRegistry.GetCandidates(name);
Assert.Equal(2, candidates.Count);
Assert.Equal("v4", candidates[0].Version);
Assert.Equal("v3", candidates[1].Version);
Assert.Equal(method, candidates[1].Method);
Assert.Equal(template, candidates[1].Template);
}
} }
} }
@@ -0,0 +1,77 @@
using System.Reflection;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class EnvironmentMapperTests
{
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Environments.InfisicalEnvironmentMapper", true);
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Environments.InfisicalEnvironmentResponseDto", true);
private static InfisicalEnvironment InvokeMap(object dto, string fallbackProjectId)
{
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
return (InfisicalEnvironment)map.Invoke(null, new object[] { dto, fallbackProjectId });
}
[Fact]
public void Map_Null_Returns_Null()
{
Assert.Null(InvokeMap(null, "proj-x"));
}
[Fact]
public void Map_Populates_Fields_With_Explicit_ProjectId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "env-001");
DtoType.GetProperty("Name").SetValue(dto, "Production");
DtoType.GetProperty("Slug").SetValue(dto, "prod");
DtoType.GetProperty("Position").SetValue(dto, 1);
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
Assert.Equal("env-001", env.Id);
Assert.Equal("Production", env.Name);
Assert.Equal("prod", env.Slug);
Assert.Equal(1, env.Position);
Assert.Equal("proj-001", env.ProjectId);
}
[Fact]
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "env-002");
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
Assert.Equal("wks-002", env.ProjectId);
}
[Fact]
public void Map_Uses_Fallback_When_No_ProjectId_Or_WorkspaceId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "env-003");
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
Assert.Equal("fallback-proj", env.ProjectId);
}
[Fact]
public void Map_Falls_Back_To_InternalId_For_Id()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("InternalId").SetValue(dto, "internal-env");
InfisicalEnvironment env = InvokeMap(dto, "p");
Assert.Equal("internal-env", env.Id);
}
}
}
@@ -0,0 +1,80 @@
using System.Reflection;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class FolderMapperTests
{
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Folders.InfisicalFolderMapper", true);
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Folders.InfisicalFolderResponseDto", true);
private static InfisicalFolder InvokeMap(object dto, string fallbackProjectId, string fallbackEnvironment)
{
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
return (InfisicalFolder)map.Invoke(null, new object[] { dto, fallbackProjectId, fallbackEnvironment });
}
[Fact]
public void Map_Null_Returns_Null()
{
Assert.Null(InvokeMap(null, "proj-x", "dev"));
}
[Fact]
public void Map_Populates_Fields_With_Explicit_ProjectId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "fld-001");
DtoType.GetProperty("Name").SetValue(dto, "config");
DtoType.GetProperty("Path").SetValue(dto, "/app/config");
DtoType.GetProperty("ParentId").SetValue(dto, "fld-root");
DtoType.GetProperty("Environment").SetValue(dto, "prod");
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
Assert.Equal("fld-001", folder.Id);
Assert.Equal("config", folder.Name);
Assert.Equal("/app/config", folder.Path);
Assert.Equal("fld-root", folder.ParentId);
Assert.Equal("prod", folder.Environment);
Assert.Equal("proj-001", folder.ProjectId);
}
[Fact]
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "fld-002");
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
Assert.Equal("wks-002", folder.ProjectId);
}
[Fact]
public void Map_Uses_Fallback_When_No_ProjectId_Or_Environment()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "fld-003");
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
Assert.Equal("fallback-proj", folder.ProjectId);
Assert.Equal("fallback-env", folder.Environment);
}
[Fact]
public void Map_Falls_Back_To_InternalId_For_Id()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("InternalId").SetValue(dto, "internal-fld");
InfisicalFolder folder = InvokeMap(dto, "p", "e");
Assert.Equal("internal-fld", folder.Id);
}
}
}
@@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RollForward>LatestMajor</RollForward>
<LangVersion>9.0</LangVersion> <LangVersion>9.0</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<AssemblyName>PSInfisicalAPI.Tests</AssemblyName> <AssemblyName>PSInfisicalAPI.Tests</AssemblyName>
@@ -0,0 +1,108 @@
using System.Collections;
using System.Reflection;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class ProjectMapperTests
{
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Projects.InfisicalProjectMapper", true);
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Projects.InfisicalProjectResponseDto", true);
private static readonly System.Type EnvDtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Projects.InfisicalProjectEnvironmentDto", true);
private static InfisicalProject InvokeMap(object dto)
{
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
return (InfisicalProject)map.Invoke(null, new[] { dto });
}
[Fact]
public void Map_Null_Dto_Returns_Null()
{
Assert.Null(InvokeMap(null));
}
[Fact]
public void Map_Populates_Core_Fields()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "proj-001");
DtoType.GetProperty("Name").SetValue(dto, "DevOps");
DtoType.GetProperty("Slug").SetValue(dto, "devops");
DtoType.GetProperty("Description").SetValue(dto, "Internal DevOps project");
DtoType.GetProperty("Organization").SetValue(dto, "org-abc");
DtoType.GetProperty("Type").SetValue(dto, "secret-manager");
DtoType.GetProperty("AutoCapitalization").SetValue(dto, true);
DtoType.GetProperty("CreatedAt").SetValue(dto, "2026-01-15T12:34:56Z");
DtoType.GetProperty("UpdatedAt").SetValue(dto, "2026-02-20T09:00:00Z");
InfisicalProject project = InvokeMap(dto);
Assert.Equal("proj-001", project.Id);
Assert.Equal("DevOps", project.Name);
Assert.Equal("devops", project.Slug);
Assert.Equal("Internal DevOps project", project.Description);
Assert.Equal("org-abc", project.OrganizationId);
Assert.Equal("secret-manager", project.Type);
Assert.True(project.AutoCapitalization);
Assert.NotNull(project.CreatedAtUtc);
Assert.NotNull(project.UpdatedAtUtc);
Assert.Empty(project.EnvironmentSlugs);
}
[Fact]
public void Map_Falls_Back_To_InternalId_And_OrgId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("InternalId").SetValue(dto, "internal-id-1");
DtoType.GetProperty("OrgId").SetValue(dto, "org-fallback");
InfisicalProject project = InvokeMap(dto);
Assert.Equal("internal-id-1", project.Id);
Assert.Equal("org-fallback", project.OrganizationId);
}
[Fact]
public void Map_Extracts_EnvironmentSlugs()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "proj-002");
System.Type listType = typeof(System.Collections.Generic.List<>).MakeGenericType(EnvDtoType);
IList envs = (IList)System.Activator.CreateInstance(listType);
object env1 = System.Activator.CreateInstance(EnvDtoType);
EnvDtoType.GetProperty("Slug").SetValue(env1, "dev");
EnvDtoType.GetProperty("Name").SetValue(env1, "Development");
envs.Add(env1);
object env2 = System.Activator.CreateInstance(EnvDtoType);
EnvDtoType.GetProperty("Slug").SetValue(env2, "prod");
envs.Add(env2);
DtoType.GetProperty("Environments").SetValue(dto, envs);
InfisicalProject project = InvokeMap(dto);
Assert.Equal(2, project.EnvironmentSlugs.Length);
Assert.Contains("dev", project.EnvironmentSlugs);
Assert.Contains("prod", project.EnvironmentSlugs);
}
[Fact]
public void MapMany_Null_Returns_Empty()
{
MethodInfo mapMany = MapperType.GetMethod("MapMany", BindingFlags.Public | BindingFlags.Static);
InfisicalProject[] result = (InfisicalProject[])mapMany.Invoke(null, new object[] { null });
Assert.NotNull(result);
Assert.Empty(result);
}
}
}
@@ -0,0 +1,73 @@
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class SecretMutationDtoTests
{
private static readonly System.Reflection.Assembly ModuleAssembly =
typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
private static object MakeDto(string typeName)
{
System.Type t = ModuleAssembly.GetType(typeName, true);
return System.Activator.CreateInstance(t);
}
[Fact]
public void CreateRequestDto_Serializes_With_Expected_Field_Names()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretCreateRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db");
dto.GetType().GetProperty("Type").SetValue(dto, "shared");
dto.GetType().GetProperty("SecretValue").SetValue(dto, "p@ss");
dto.GetType().GetProperty("SecretComment").SetValue(dto, "comment");
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["workspaceId"]);
Assert.Equal("prod", (string)json["environment"]);
Assert.Equal("/db", (string)json["secretPath"]);
Assert.Equal("shared", (string)json["type"]);
Assert.Equal("p@ss", (string)json["secretValue"]);
Assert.Equal("comment", (string)json["secretComment"]);
Assert.False(json.ContainsKey("skipMultilineEncoding"));
Assert.False(json.ContainsKey("tagIds"));
}
[Fact]
public void UpdateRequestDto_Omits_Null_Optional_Fields()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretUpdateRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
dto.GetType().GetProperty("NewSecretName").SetValue(dto, "renamed");
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("renamed", (string)json["newSecretName"]);
Assert.False(json.ContainsKey("secretValue"));
Assert.False(json.ContainsKey("secretComment"));
Assert.False(json.ContainsKey("type"));
Assert.False(json.ContainsKey("secretPath"));
}
[Fact]
public void DeleteRequestDto_Serializes_With_Expected_Field_Names()
{
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDeleteRequestDto");
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db");
dto.GetType().GetProperty("Type").SetValue(dto, "shared");
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
Assert.Equal("wks-1", (string)json["workspaceId"]);
Assert.Equal("prod", (string)json["environment"]);
Assert.Equal("/db", (string)json["secretPath"]);
Assert.Equal("shared", (string)json["type"]);
}
}
}
@@ -0,0 +1,77 @@
using System.Reflection;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class TagMapperTests
{
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Tags.InfisicalTagMapper", true);
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
.GetType("PSInfisicalAPI.Tags.InfisicalTagResponseDto", true);
private static InfisicalTag InvokeMap(object dto, string fallbackProjectId)
{
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
return (InfisicalTag)map.Invoke(null, new object[] { dto, fallbackProjectId });
}
[Fact]
public void Map_Null_Returns_Null()
{
Assert.Null(InvokeMap(null, "proj-x"));
}
[Fact]
public void Map_Populates_Fields_With_Explicit_ProjectId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "tag-001");
DtoType.GetProperty("Slug").SetValue(dto, "critical");
DtoType.GetProperty("Name").SetValue(dto, "Critical");
DtoType.GetProperty("Color").SetValue(dto, "#FF0000");
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
Assert.Equal("tag-001", tag.Id);
Assert.Equal("critical", tag.Slug);
Assert.Equal("Critical", tag.Name);
Assert.Equal("#FF0000", tag.Color);
Assert.Equal("proj-001", tag.ProjectId);
}
[Fact]
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "tag-002");
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
Assert.Equal("wks-002", tag.ProjectId);
}
[Fact]
public void Map_Uses_Fallback_When_No_ProjectId_Or_WorkspaceId()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("Id").SetValue(dto, "tag-003");
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
Assert.Equal("fallback-proj", tag.ProjectId);
}
[Fact]
public void Map_Falls_Back_To_InternalId_For_Id()
{
object dto = System.Activator.CreateInstance(DtoType);
DtoType.GetProperty("InternalId").SetValue(dto, "internal-tag");
InfisicalTag tag = InvokeMap(dto, "p");
Assert.Equal("internal-tag", tag.Id);
}
}
}
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Authentication
{
public sealed class AzureAuthProvider : IInfisicalAuthProvider
{
private const string Component = "AzureAuthProvider";
public string Name { get { return "AzureAuth"; } }
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (request == null || string.IsNullOrEmpty(request.IdentityId))
{
throw new InfisicalAuthenticationException("IdentityId is required for Azure Auth.");
}
if (request.Jwt == null || request.Jwt.Length == 0)
{
throw new InfisicalAuthenticationException("Jwt is required for Azure Auth.");
}
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.AzureAuthLogin, Component, request, httpClient, logger, serializer =>
{
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
{
Dictionary<string, string> bodyObject = new Dictionary<string, string>
{
{ "identityId", request.IdentityId },
{ "jwt", plainJwt ?? string.Empty }
};
return serializer.Serialize(bodyObject);
});
});
}
}
}
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Authentication
{
public sealed class GcpIamAuthProvider : IInfisicalAuthProvider
{
private const string Component = "GcpIamAuthProvider";
public string Name { get { return "GcpIamAuth"; } }
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (request == null || string.IsNullOrEmpty(request.IdentityId))
{
throw new InfisicalAuthenticationException("IdentityId is required for GCP IAM Auth.");
}
if (request.Jwt == null || request.Jwt.Length == 0)
{
throw new InfisicalAuthenticationException("Jwt is required for GCP IAM Auth.");
}
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.GcpIamAuthLogin, Component, request, httpClient, logger, serializer =>
{
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
{
Dictionary<string, string> bodyObject = new Dictionary<string, string>
{
{ "identityId", request.IdentityId },
{ "jwt", plainJwt ?? string.Empty }
};
return serializer.Serialize(bodyObject);
});
});
}
}
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
using PSInfisicalAPI.Serialization;
namespace PSInfisicalAPI.Authentication
{
internal static class IdentityLoginExecutor
{
internal static InfisicalAuthenticationResult Execute(
string endpointName,
string component,
InfisicalAuthenticationRequest request,
IInfisicalHttpClient httpClient,
IInfisicalLogger logger,
Func<JsonInfisicalSerializer, string> bodyFactory)
{
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
if (bodyFactory == null) { throw new ArgumentNullException(nameof(bodyFactory)); }
IInfisicalLogger log = logger ?? NullInfisicalLogger.Instance;
log.Information(component, "Attempting to authenticate to Infisical. Please Wait...");
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName);
Uri uri = InfisicalUriBuilder.Build(request.BaseUri, definition, null, null);
JsonInfisicalSerializer serializer = new JsonInfisicalSerializer();
string body = bodyFactory(serializer);
InfisicalHttpRequest httpRequest = new InfisicalHttpRequest
{
OperationName = "Authenticate",
EndpointName = definition.Name,
Method = definition.Method,
Uri = uri,
Body = body,
ContentType = "application/json",
ContainsSecretMaterialInRequest = definition.ContainsSecretMaterialInRequest,
ContainsSecretMaterialInResponse = definition.ContainsSecretMaterialInResponse,
Headers = new Dictionary<string, string> { { "Accept", "application/json" } }
};
InfisicalHttpResponse response = httpClient.Send(httpRequest);
try
{
if (response.StatusCode < 200 || response.StatusCode >= 300)
{
log.Error(component, "Infisical authentication failed.");
throw new InfisicalAuthenticationException(string.Concat(component, " login returned status ", response.StatusCode.ToString(CultureInfo.InvariantCulture), "."));
}
IdentityLoginResponse parsed = serializer.Deserialize<IdentityLoginResponse>(response.Body);
if (parsed == null || string.IsNullOrEmpty(parsed.AccessToken))
{
throw new InfisicalAuthenticationException(string.Concat(component, " login response did not contain an access token."));
}
SecureString accessToken = SecureStringUtility.ToReadOnlySecureString(parsed.AccessToken);
DateTimeOffset? expiresAt = null;
if (parsed.ExpiresIn > 0)
{
expiresAt = DateTimeOffset.UtcNow.AddSeconds(parsed.ExpiresIn);
}
parsed.AccessToken = null;
log.Information(component, "Infisical authentication was successful.");
return new InfisicalAuthenticationResult
{
AccessToken = accessToken,
TokenType = string.IsNullOrEmpty(parsed.TokenType) ? "Bearer" : parsed.TokenType,
ExpiresAtUtc = expiresAt
};
}
finally
{
response.Clear();
}
}
private sealed class IdentityLoginResponse
{
[Newtonsoft.Json.JsonProperty("accessToken")]
public string AccessToken { get; set; }
[Newtonsoft.Json.JsonProperty("expiresIn")]
public int ExpiresIn { get; set; }
[Newtonsoft.Json.JsonProperty("tokenType")]
public string TokenType { get; set; }
}
}
}
@@ -10,5 +10,10 @@ namespace PSInfisicalAPI.Authentication
public string ClientId { get; set; } public string ClientId { get; set; }
public SecureString ClientSecret { get; set; } public SecureString ClientSecret { get; set; }
public SecureString PreSuppliedAccessToken { get; set; } public SecureString PreSuppliedAccessToken { get; set; }
public string IdentityId { get; set; }
public SecureString Jwt { get; set; }
public string Username { get; set; }
public SecureString Password { get; set; }
} }
} }
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Authentication
{
public sealed class JwtAuthProvider : IInfisicalAuthProvider
{
private const string Component = "JwtAuthProvider";
public string Name { get { return "JwtAuth"; } }
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (request == null || string.IsNullOrEmpty(request.IdentityId))
{
throw new InfisicalAuthenticationException("IdentityId is required for JWT Auth.");
}
if (request.Jwt == null || request.Jwt.Length == 0)
{
throw new InfisicalAuthenticationException("Jwt is required for JWT Auth.");
}
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.JwtAuthLogin, Component, request, httpClient, logger, serializer =>
{
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
{
Dictionary<string, string> bodyObject = new Dictionary<string, string>
{
{ "identityId", request.IdentityId },
{ "jwt", plainJwt ?? string.Empty }
};
return serializer.Serialize(bodyObject);
});
});
}
}
}
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Authentication
{
public sealed class LdapAuthProvider : IInfisicalAuthProvider
{
private const string Component = "LdapAuthProvider";
public string Name { get { return "LdapAuth"; } }
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (request == null || string.IsNullOrEmpty(request.Username))
{
throw new InfisicalAuthenticationException("Username is required for LDAP Auth.");
}
if (request.Password == null || request.Password.Length == 0)
{
throw new InfisicalAuthenticationException("Password is required for LDAP Auth.");
}
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.LdapAuthLogin, Component, request, httpClient, logger, serializer =>
{
return SecureStringUtility.UsePlainText(request.Password, plainPassword =>
{
Dictionary<string, string> bodyObject = new Dictionary<string, string>
{
{ "username", request.Username },
{ "password", plainPassword ?? string.Empty }
};
if (!string.IsNullOrEmpty(request.IdentityId))
{
bodyObject["identityId"] = request.IdentityId;
}
return serializer.Serialize(bodyObject);
});
});
}
}
}
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Authentication
{
public sealed class OidcAuthProvider : IInfisicalAuthProvider
{
private const string Component = "OidcAuthProvider";
public string Name { get { return "OidcAuth"; } }
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (request == null || string.IsNullOrEmpty(request.IdentityId))
{
throw new InfisicalAuthenticationException("IdentityId is required for OIDC Auth.");
}
if (request.Jwt == null || request.Jwt.Length == 0)
{
throw new InfisicalAuthenticationException("Jwt is required for OIDC Auth.");
}
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.OidcAuthLogin, Component, request, httpClient, logger, serializer =>
{
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
{
Dictionary<string, string> bodyObject = new Dictionary<string, string>
{
{ "identityId", request.IdentityId },
{ "jwt", plainJwt ?? string.Empty }
};
return serializer.Serialize(bodyObject);
});
});
}
}
}
@@ -0,0 +1,94 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Cmdlets
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class BulkSecretsTransformationAttribute : ArgumentTransformationAttribute
{
public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
{
if (inputData == null) { return null; }
object unwrapped = Unwrap(inputData);
if (unwrapped is IDictionary<string, string>[] strongArray) { return strongArray; }
if (unwrapped is IDictionary singleDict)
{
return new IDictionary<string, string>[] { Convert(singleDict) };
}
if (unwrapped is IEnumerable enumerable && !(unwrapped is string))
{
List<IDictionary<string, string>> result = new List<IDictionary<string, string>>();
foreach (object element in enumerable)
{
if (element == null) { continue; }
object e = Unwrap(element);
if (e is IDictionary dict)
{
result.Add(Convert(dict));
continue;
}
throw new ArgumentTransformationMetadataException(
"Each element of -Secrets must be a dictionary (Hashtable, OrderedDictionary, Dictionary<string,string>, etc.).");
}
return result.ToArray();
}
throw new ArgumentTransformationMetadataException(
"-Secrets must be a dictionary or an array of dictionaries.");
}
private static object Unwrap(object value)
{
PSObject pso = value as PSObject;
return pso != null ? pso.BaseObject : value;
}
private static IDictionary<string, string> Convert(IDictionary source)
{
Dictionary<string, string> dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in source)
{
if (entry.Key == null) { continue; }
string key = entry.Key.ToString();
dict[key] = Stringify(entry.Value);
}
return dict;
}
private static string Stringify(object value)
{
object v = Unwrap(value);
if (v == null) { return null; }
if (v is string s) { return s; }
if (v is bool b) { return b ? "true" : "false"; }
if (v is System.Security.SecureString secure)
{
return SecureStringUtility.UsePlainText(secure, plain => plain);
}
if (v is IEnumerable enumerable)
{
List<string> parts = new List<string>();
foreach (object item in enumerable)
{
if (item == null) { continue; }
parts.Add(Unwrap(item).ToString());
}
return string.Join(",", parts);
}
return v.ToString();
}
}
}
@@ -15,6 +15,11 @@ namespace PSInfisicalAPI.Cmdlets
{ {
private const string ParameterSetUniversalAuth = "UniversalAuth"; private const string ParameterSetUniversalAuth = "UniversalAuth";
private const string ParameterSetToken = "Token"; private const string ParameterSetToken = "Token";
private const string ParameterSetJwt = "JwtAuth";
private const string ParameterSetOidc = "OidcAuth";
private const string ParameterSetLdap = "LdapAuth";
private const string ParameterSetAzure = "AzureAuth";
private const string ParameterSetGcpIam = "GcpIamAuth";
private const string Component = "ConnectInfisicalCmdlet"; private const string Component = "ConnectInfisicalCmdlet";
[Parameter] [Parameter]
@@ -38,6 +43,25 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter(ParameterSetName = ParameterSetToken)] [Parameter(ParameterSetName = ParameterSetToken)]
public SecureString AccessToken { get; set; } public SecureString AccessToken { get; set; }
[Parameter(Mandatory = true, ParameterSetName = ParameterSetJwt)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetOidc)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetAzure)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetGcpIam)]
[Parameter(ParameterSetName = ParameterSetLdap)]
public string IdentityId { get; set; }
[Parameter(Mandatory = true, ParameterSetName = ParameterSetJwt)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetOidc)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetAzure)]
[Parameter(Mandatory = true, ParameterSetName = ParameterSetGcpIam)]
public SecureString Jwt { get; set; }
[Parameter(Mandatory = true, ParameterSetName = ParameterSetLdap)]
public string Username { get; set; }
[Parameter(Mandatory = true, ParameterSetName = ParameterSetLdap)]
public SecureString Password { get; set; }
[Parameter] [Parameter]
public string SecretPath { get; set; } = "/"; public string SecretPath { get; set; } = "/";
@@ -58,28 +82,91 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalAuthenticationRequest request; InfisicalAuthenticationRequest request;
InfisicalAuthType authType; InfisicalAuthType authType;
if (string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal)) switch (ParameterSetName)
{ {
provider = new TokenAuthProvider(); case ParameterSetToken:
authType = InfisicalAuthType.Token; provider = new TokenAuthProvider();
request = new InfisicalAuthenticationRequest authType = InfisicalAuthType.Token;
{ request = new InfisicalAuthenticationRequest
BaseUri = BaseUri, {
ApiVersion = ApiVersion, BaseUri = BaseUri,
PreSuppliedAccessToken = AccessToken ApiVersion = ApiVersion,
}; PreSuppliedAccessToken = AccessToken
} };
else break;
{
provider = new UniversalAuthProvider(); case ParameterSetJwt:
authType = InfisicalAuthType.UniversalAuth; provider = new JwtAuthProvider();
request = new InfisicalAuthenticationRequest authType = InfisicalAuthType.Jwt;
{ request = new InfisicalAuthenticationRequest
BaseUri = BaseUri, {
ApiVersion = ApiVersion, BaseUri = BaseUri,
ClientId = ClientId, ApiVersion = ApiVersion,
ClientSecret = ClientSecret IdentityId = IdentityId,
}; Jwt = Jwt
};
break;
case ParameterSetOidc:
provider = new OidcAuthProvider();
authType = InfisicalAuthType.Oidc;
request = new InfisicalAuthenticationRequest
{
BaseUri = BaseUri,
ApiVersion = ApiVersion,
IdentityId = IdentityId,
Jwt = Jwt
};
break;
case ParameterSetLdap:
provider = new LdapAuthProvider();
authType = InfisicalAuthType.Ldap;
request = new InfisicalAuthenticationRequest
{
BaseUri = BaseUri,
ApiVersion = ApiVersion,
IdentityId = IdentityId,
Username = Username,
Password = Password
};
break;
case ParameterSetAzure:
provider = new AzureAuthProvider();
authType = InfisicalAuthType.Azure;
request = new InfisicalAuthenticationRequest
{
BaseUri = BaseUri,
ApiVersion = ApiVersion,
IdentityId = IdentityId,
Jwt = Jwt
};
break;
case ParameterSetGcpIam:
provider = new GcpIamAuthProvider();
authType = InfisicalAuthType.GcpIam;
request = new InfisicalAuthenticationRequest
{
BaseUri = BaseUri,
ApiVersion = ApiVersion,
IdentityId = IdentityId,
Jwt = Jwt
};
break;
default:
provider = new UniversalAuthProvider();
authType = InfisicalAuthType.UniversalAuth;
request = new InfisicalAuthenticationRequest
{
BaseUri = BaseUri,
ApiVersion = ApiVersion,
ClientId = ClientId,
ClientSecret = ClientSecret
};
break;
} }
InfisicalAuthenticationResult authResult = provider.Authenticate(request, HttpClient, Logger); InfisicalAuthenticationResult authResult = provider.Authenticate(request, HttpClient, Logger);
@@ -123,6 +210,7 @@ namespace PSInfisicalAPI.Cmdlets
private void ResolveMissingParametersFromEnvironment() private void ResolveMissingParametersFromEnvironment()
{ {
bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal); bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal);
bool universalSet = string.Equals(ParameterSetName, ParameterSetUniversalAuth, StringComparison.Ordinal);
bool needsScan = bool needsScan =
BaseUri == null || BaseUri == null ||
@@ -130,8 +218,8 @@ namespace PSInfisicalAPI.Cmdlets
string.IsNullOrWhiteSpace(ProjectId) || string.IsNullOrWhiteSpace(ProjectId) ||
string.IsNullOrWhiteSpace(Environment) || string.IsNullOrWhiteSpace(Environment) ||
(tokenSet && (AccessToken == null || AccessToken.Length == 0)) || (tokenSet && (AccessToken == null || AccessToken.Length == 0)) ||
(!tokenSet && string.IsNullOrWhiteSpace(ClientId)) || (universalSet && string.IsNullOrWhiteSpace(ClientId)) ||
(!tokenSet && (ClientSecret == null || ClientSecret.Length == 0)); (universalSet && (ClientSecret == null || ClientSecret.Length == 0));
if (!needsScan) if (!needsScan)
{ {
@@ -161,7 +249,7 @@ namespace PSInfisicalAPI.Cmdlets
{ {
AccessToken = InfisicalEnvironmentResolver.ResolveSecureString("AccessToken", InfisicalEnvironmentResolver.AccessTokenPatterns, AccessToken, Logger); AccessToken = InfisicalEnvironmentResolver.ResolveSecureString("AccessToken", InfisicalEnvironmentResolver.AccessTokenPatterns, AccessToken, Logger);
} }
else else if (universalSet)
{ {
ClientId = InfisicalEnvironmentResolver.ResolveString("ClientId", InfisicalEnvironmentResolver.ClientIdPatterns, ClientId, Logger); ClientId = InfisicalEnvironmentResolver.ResolveString("ClientId", InfisicalEnvironmentResolver.ClientIdPatterns, ClientId, Logger);
ClientSecret = InfisicalEnvironmentResolver.ResolveSecureString("ClientSecret", InfisicalEnvironmentResolver.ClientSecretPatterns, ClientSecret, Logger); ClientSecret = InfisicalEnvironmentResolver.ResolveSecureString("ClientSecret", InfisicalEnvironmentResolver.ClientSecretPatterns, ClientSecret, Logger);
@@ -199,7 +287,7 @@ namespace PSInfisicalAPI.Cmdlets
{ {
if (AccessToken == null || AccessToken.Length == 0) { missing.Add("AccessToken"); } if (AccessToken == null || AccessToken.Length == 0) { missing.Add("AccessToken"); }
} }
else else if (string.Equals(ParameterSetName, ParameterSetUniversalAuth, StringComparison.Ordinal))
{ {
if (string.IsNullOrWhiteSpace(ClientId)) { missing.Add("ClientId"); } if (string.IsNullOrWhiteSpace(ClientId)) { missing.Add("ClientId"); }
if (ClientSecret == null || ClientSecret.Length == 0) { missing.Add("ClientSecret"); } if (ClientSecret == null || ClientSecret.Length == 0) { missing.Add("ClientSecret"); }
@@ -0,0 +1,75 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Secrets;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Copy, "InfisicalSecret", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalSecret))]
public sealed class CopyInfisicalSecretCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
[Alias("Id", "SecretIds")]
public string[] SecretId { get; set; }
[Parameter(Mandatory = true)]
public string DestinationEnvironment { get; set; }
[Parameter] public string DestinationSecretPath { get; set; }
[Parameter] public string SourceEnvironment { get; set; }
[Parameter] public string SourceSecretPath { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public SwitchParameter OverwriteExisting { get; set; }
[Parameter] public SwitchParameter CopySecretValue { get; set; }
[Parameter] public SwitchParameter CopySecretComment { get; set; }
[Parameter] public SwitchParameter CopyTags { get; set; }
[Parameter] public SwitchParameter CopyMetadata { get; set; }
protected override void ProcessRecord()
{
try
{
if (SecretId == null || SecretId.Length == 0) { return; }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedSourceEnv = ResolveEnvironment(connection, SourceEnvironment);
string resolvedSourcePath = ResolveSecretPath(connection, SourceSecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
string target = string.Concat(SecretId.Length, " secret(s) -> ", DestinationEnvironment);
if (!ShouldProcess(target, "Duplicate Infisical secrets")) { return; }
InfisicalDuplicateSecretsRequest request = new InfisicalDuplicateSecretsRequest
{
ProjectId = resolvedProjectId,
SourceEnvironment = resolvedSourceEnv,
DestinationEnvironment = DestinationEnvironment,
SourceSecretPath = resolvedSourcePath,
DestinationSecretPath = DestinationSecretPath,
SecretIds = SecretId,
ApiVersion = resolvedApiVersion,
OverwriteExisting = OverwriteExisting.IsPresent ? (bool?)true : null,
CopySecretValue = CopySecretValue.IsPresent ? (bool?)true : null,
CopySecretComment = CopySecretComment.IsPresent ? (bool?)true : null,
CopyTags = CopyTags.IsPresent ? (bool?)true : null,
CopyMetadata = CopyMetadata.IsPresent ? (bool?)true : null
};
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret[] duplicated = client.Duplicate(connection, request);
if (duplicated != null)
{
foreach (InfisicalSecret secret in duplicated) { WriteObject(secret); }
}
}
catch (Exception exception)
{
ThrowTerminatingForException("CopyInfisicalSecretCmdlet", "DuplicateSecrets", exception);
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironment")]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class GetInfisicalEnvironmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Slug", "Id", "Environment")]
public string EnvironmentSlugOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Retrieve(connection, resolvedProjectId, EnvironmentSlugOrId);
if (env != null)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalEnvironmentCmdlet", "RetrieveEnvironment", exception);
}
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironments")]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class GetInfisicalEnvironmentsCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment[] envs = client.List(connection, resolvedProjectId);
foreach (InfisicalEnvironment env in envs)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalEnvironmentsCmdlet", "ListEnvironments", exception);
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalFolder")]
[OutputType(typeof(InfisicalFolder))]
public sealed class GetInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Name", "Id")]
public string FolderNameOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Retrieve(connection, resolvedProjectId, resolvedEnvironment, resolvedPath, FolderNameOrId);
if (folder != null)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalFolderCmdlet", "RetrieveFolder", exception);
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalFolders")]
[OutputType(typeof(InfisicalFolder))]
public sealed class GetInfisicalFoldersCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder[] folders = client.List(connection, resolvedProjectId, resolvedEnvironment, resolvedPath);
foreach (InfisicalFolder folder in folders)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalFoldersCmdlet", "ListFolders", exception);
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalProject")]
[OutputType(typeof(InfisicalProject))]
public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Retrieve(connection, resolvedProjectId);
if (project != null)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalProjectCmdlet", "RetrieveProject", exception);
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalProjects")]
[OutputType(typeof(InfisicalProject))]
public sealed class GetInfisicalProjectsCmdlet : InfisicalCmdletBase
{
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject[] projects = client.List(connection);
foreach (InfisicalProject project in projects)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalProjectsCmdlet", "ListProjects", exception);
}
}
}
}
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery
{ {
SecretName = SecretName, SecretName = SecretName,
ProjectId = ProjectId, ProjectId = ResolveProjectId(connection, ProjectId),
Environment = Environment, Environment = ResolveEnvironment(connection, Environment),
SecretPath = SecretPath, SecretPath = ResolveSecretPath(connection, SecretPath),
ApiVersion = ApiVersion, ApiVersion = ResolveApiVersion(connection, ApiVersion),
Version = Version, Version = Version,
Type = Type.ToString(), Type = Type.ToString(),
ViewSecretValue = ViewSecretValue.IsPresent, ViewSecretValue = ViewSecretValue.IsPresent,
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalListSecretsQuery query = new InfisicalListSecretsQuery InfisicalListSecretsQuery query = new InfisicalListSecretsQuery
{ {
ProjectId = ProjectId, ProjectId = ResolveProjectId(connection, ProjectId),
Environment = Environment, Environment = ResolveEnvironment(connection, Environment),
SecretPath = SecretPath, SecretPath = ResolveSecretPath(connection, SecretPath),
ApiVersion = ApiVersion, ApiVersion = ResolveApiVersion(connection, ApiVersion),
Recursive = Recursive.IsPresent, Recursive = Recursive.IsPresent,
IncludeImports = IncludeImports.IsPresent, IncludeImports = IncludeImports.IsPresent,
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent, IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
@@ -0,0 +1,38 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalTag")]
[OutputType(typeof(InfisicalTag))]
public sealed class GetInfisicalTagCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Slug", "Id")]
public string TagSlugOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Retrieve(connection, resolvedProjectId, TagSlugOrId);
if (tag != null)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalTagCmdlet", "RetrieveTag", exception);
}
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalTags")]
[OutputType(typeof(InfisicalTag))]
public sealed class GetInfisicalTagsCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag[] tags = client.List(connection, resolvedProjectId);
foreach (InfisicalTag tag in tags)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalTagsCmdlet", "ListTags", exception);
}
}
}
}
@@ -1,5 +1,6 @@
using System; using System;
using System.Management.Automation; using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Errors; using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http; using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging; using PSInfisicalAPI.Logging;
@@ -44,5 +45,43 @@ namespace PSInfisicalAPI.Cmdlets
ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details); ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details);
ThrowTerminatingError(record); ThrowTerminatingError(record);
} }
protected string ResolveProjectId(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("ProjectId", explicitValue, connection != null ? connection.ProjectId : null, null);
}
protected string ResolveEnvironment(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("Environment", explicitValue, connection != null ? connection.Environment : null, null);
}
protected string ResolveSecretPath(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("SecretPath", explicitValue, connection != null ? connection.DefaultSecretPath : null, "/");
}
protected string ResolveApiVersion(InfisicalConnection connection, string explicitValue)
{
string fromConnection = connection != null ? (!string.IsNullOrEmpty(connection.PinnedApiVersion) ? connection.PinnedApiVersion : connection.ApiVersion) : null;
return ResolveValue("ApiVersion", explicitValue, fromConnection, null);
}
protected string ResolveOrganizationId(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("OrganizationId", explicitValue, connection != null ? connection.OrganizationId : null, null);
}
private string ResolveValue(string parameterName, string explicitValue, string inheritedValue, string defaultValue)
{
if (!string.IsNullOrEmpty(explicitValue)) { return explicitValue; }
if (!string.IsNullOrEmpty(inheritedValue))
{
Logger.Verbose(GetType().Name, string.Concat("Inherited ", parameterName, " '", inheritedValue, "' from connection."));
return inheritedValue;
}
return defaultValue;
}
} }
} }
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalEnvironment", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class NewInfisicalEnvironmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
[Parameter(Mandatory = true, Position = 1)] public string Slug { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public int? Position { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(Slug, "Create Infisical environment"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Create(connection, resolvedProjectId, Name, Slug, Position);
if (env != null)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalEnvironmentCmdlet", "CreateEnvironment", exception);
}
}
}
}
@@ -0,0 +1,44 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalFolder", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalFolder))]
public sealed class NewInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(Name, "Create Infisical folder"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Create(connection, resolvedProjectId, resolvedEnvironment, Name, resolvedPath);
if (folder != null)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalFolderCmdlet", "CreateFolder", exception);
}
}
}
}
@@ -0,0 +1,47 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalProject", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalProject))]
public sealed class NewInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0)]
[Alias("Name")]
public string ProjectName { get; set; }
[Parameter] public string Slug { get; set; }
[Parameter] public string Description { get; set; }
[Parameter] public string Type { get; set; }
[Parameter] public string OrganizationId { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(ProjectName, "Create Infisical project"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedOrgId = !string.IsNullOrEmpty(OrganizationId) ? OrganizationId : connection.OrganizationId;
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Create(connection, ProjectName, Slug, Description, Type, resolvedOrgId);
if (project != null)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalProjectCmdlet", "CreateProject", exception);
}
}
}
}
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Security;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Secrets;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")]
[OutputType(typeof(InfisicalSecret))]
public sealed class NewInfisicalSecretCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "PlainText")]
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "SecureString")]
public string SecretName { get; set; }
[Parameter(Mandatory = true, Position = 1, ParameterSetName = "PlainText")]
public string SecretValue { get; set; }
[Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")]
public SecureString SecureSecretValue { get; set; }
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
[BulkSecretsTransformation]
public IDictionary<string, string>[] Secrets { get; set; }
[Parameter] public string SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
[Parameter] public SwitchParameter SkipMultilineEncoding { get; set; }
[Parameter] public string[] TagIds { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
{
if (Secrets == null || Secrets.Length == 0) { return; }
string target = string.Concat(Secrets.Length, " secret(s)");
if (!ShouldProcess(target, "Bulk-create Infisical secrets")) { return; }
InfisicalBulkCreateSecretsRequest bulk = new InfisicalBulkCreateSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ApiVersion = resolvedApiVersion,
Secrets = InfisicalBulkSecretConverter.ToCreateItems(Secrets)
};
InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret[] created = bulkClient.CreateBatch(connection, bulk);
if (created != null)
{
foreach (InfisicalSecret secret in created) { WriteObject(secret); }
}
return;
}
if (!ShouldProcess(SecretName, "Create Infisical secret")) { return; }
string plainValue = SecureSecretValue != null
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
: SecretValue;
InfisicalCreateSecretRequest request = new InfisicalCreateSecretRequest
{
SecretName = SecretName,
SecretValue = plainValue,
SecretComment = SecretComment,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
TagIds = TagIds
};
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret single = client.Create(connection, request);
if (single != null)
{
WriteObject(single);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalSecretCmdlet", "CreateSecret", exception);
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalTag", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalTag))]
public sealed class NewInfisicalTagCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0)] public string Slug { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Color { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(Slug, "Create Infisical tag"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Create(connection, resolvedProjectId, Slug, Name, Color);
if (tag != null)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalTagCmdlet", "CreateTag", exception);
}
}
}
}
@@ -0,0 +1,43 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Remove, "InfisicalEnvironment", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalEnvironmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string EnvironmentId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(EnvironmentId, "Remove Infisical environment"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, EnvironmentId);
if (PassThru.IsPresent)
{
WriteObject(EnvironmentId);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("RemoveInfisicalEnvironmentCmdlet", "DeleteEnvironment", exception);
}
}
}
}
@@ -0,0 +1,47 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Remove, "InfisicalFolder", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string FolderId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(FolderId, "Remove Infisical folder"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, resolvedEnvironment, FolderId, resolvedPath);
if (PassThru.IsPresent)
{
WriteObject(FolderId);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("RemoveInfisicalFolderCmdlet", "DeleteFolder", exception);
}
}
}
}
@@ -0,0 +1,43 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Remove Infisical project"))
{
return;
}
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId);
if (PassThru.IsPresent)
{
WriteObject(resolvedProjectId);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("RemoveInfisicalProjectCmdlet", "DeleteProject", exception);
}
}
}
}
@@ -0,0 +1,88 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Secrets;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Remove, "InfisicalSecret", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High, DefaultParameterSetName = "Single")]
public sealed class RemoveInfisicalSecretCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "Single")]
public string SecretName { get; set; }
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
[Alias("Names", "SecretKeys")]
public string[] SecretNames { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
{
if (SecretNames == null || SecretNames.Length == 0) { return; }
string target = string.Concat(SecretNames.Length, " secret(s)");
if (!ShouldProcess(target, "Bulk-remove Infisical secrets")) { return; }
InfisicalBulkDeleteSecretsRequest bulk = new InfisicalBulkDeleteSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ApiVersion = resolvedApiVersion,
SecretNames = SecretNames
};
client.DeleteBatch(connection, bulk);
if (PassThru.IsPresent)
{
foreach (string name in SecretNames) { WriteObject(name); }
}
return;
}
if (!ShouldProcess(SecretName, "Remove Infisical secret")) { return; }
InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest
{
SecretName = SecretName,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion
};
client.Delete(connection, request);
if (PassThru.IsPresent)
{
WriteObject(SecretName);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("RemoveInfisicalSecretCmdlet", "DeleteSecret", exception);
}
}
}
}
@@ -0,0 +1,43 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Remove, "InfisicalTag", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalTagCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string TagId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(TagId, "Remove Infisical tag"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, TagId);
if (PassThru.IsPresent)
{
WriteObject(TagId);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("RemoveInfisicalTagCmdlet", "DeleteTag", exception);
}
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Update, "InfisicalEnvironment", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class UpdateInfisicalEnvironmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string EnvironmentId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Slug { get; set; }
[Parameter] public int? Position { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(EnvironmentId, "Update Infisical environment"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Update(connection, resolvedProjectId, EnvironmentId, Name, Slug, Position);
if (env != null)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("UpdateInfisicalEnvironmentCmdlet", "UpdateEnvironment", exception);
}
}
}
}
@@ -0,0 +1,48 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Update, "InfisicalFolder", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalFolder))]
public sealed class UpdateInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string FolderId { get; set; }
[Parameter(Mandatory = true, Position = 1)] public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(FolderId, "Update Infisical folder"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Update(connection, resolvedProjectId, resolvedEnvironment, FolderId, Name, resolvedPath);
if (folder != null)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("UpdateInfisicalFolderCmdlet", "UpdateFolder", exception);
}
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Update, "InfisicalProject", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalProject))]
public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Description { get; set; }
[Parameter] public bool? AutoCapitalization { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Update Infisical project"))
{
return;
}
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Update(connection, resolvedProjectId, Name, Description, AutoCapitalization);
if (project != null)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("UpdateInfisicalProjectCmdlet", "UpdateProject", exception);
}
}
}
}
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Security;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Secrets;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Update, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")]
[OutputType(typeof(InfisicalSecret))]
public sealed class UpdateInfisicalSecretCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "PlainText")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "SecureString")]
public string SecretName { get; set; }
[Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; }
[Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; }
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
[BulkSecretsTransformation]
public IDictionary<string, string>[] Secrets { get; set; }
[Parameter] public string NewSecretName { get; set; }
[Parameter] public string SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
[Parameter] public SwitchParameter SkipMultilineEncoding { get; set; }
[Parameter] public string[] TagIds { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
{
if (Secrets == null || Secrets.Length == 0) { return; }
string target = string.Concat(Secrets.Length, " secret(s)");
if (!ShouldProcess(target, "Bulk-update Infisical secrets")) { return; }
InfisicalBulkUpdateSecretsRequest bulk = new InfisicalBulkUpdateSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ApiVersion = resolvedApiVersion,
Secrets = InfisicalBulkSecretConverter.ToUpdateItems(Secrets)
};
InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret[] updated = bulkClient.UpdateBatch(connection, bulk);
if (updated != null)
{
foreach (InfisicalSecret secret in updated) { WriteObject(secret); }
}
return;
}
if (!ShouldProcess(SecretName, "Update Infisical secret")) { return; }
string plainValue = SecureSecretValue != null
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
: SecretValue;
InfisicalUpdateSecretRequest request = new InfisicalUpdateSecretRequest
{
SecretName = SecretName,
NewSecretName = NewSecretName,
SecretValue = plainValue,
SecretComment = SecretComment,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
TagIds = TagIds
};
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret single = client.Update(connection, request);
if (single != null)
{
WriteObject(single);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("UpdateInfisicalSecretCmdlet", "UpdateSecret", exception);
}
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Update, "InfisicalTag", SupportsShouldProcess = true)]
[OutputType(typeof(InfisicalTag))]
public sealed class UpdateInfisicalTagCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string TagId { get; set; }
[Parameter] public string Slug { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Color { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
if (!ShouldProcess(TagId, "Update Infisical tag"))
{
return;
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Update(connection, resolvedProjectId, TagId, Slug, Name, Color);
if (tag != null)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("UpdateInfisicalTagCmdlet", "UpdateTag", exception);
}
}
}
}
@@ -3,7 +3,45 @@ namespace PSInfisicalAPI.Endpoints
public static class InfisicalEndpointNames public static class InfisicalEndpointNames
{ {
public const string UniversalAuthLogin = "UniversalAuthLogin"; public const string UniversalAuthLogin = "UniversalAuthLogin";
public const string TokenAuthLogin = "TokenAuthLogin";
public const string JwtAuthLogin = "JwtAuthLogin";
public const string OidcAuthLogin = "OidcAuthLogin";
public const string LdapAuthLogin = "LdapAuthLogin";
public const string AzureAuthLogin = "AzureAuthLogin";
public const string GcpIamAuthLogin = "GcpIamAuthLogin";
public const string ListSecrets = "ListSecrets"; public const string ListSecrets = "ListSecrets";
public const string RetrieveSecret = "RetrieveSecret"; public const string RetrieveSecret = "RetrieveSecret";
public const string CreateSecret = "CreateSecret";
public const string UpdateSecret = "UpdateSecret";
public const string DeleteSecret = "DeleteSecret";
public const string BulkCreateSecret = "BulkCreateSecret";
public const string BulkUpdateSecret = "BulkUpdateSecret";
public const string BulkDeleteSecret = "BulkDeleteSecret";
public const string DuplicateSecret = "DuplicateSecret";
public const string ListProjects = "ListProjects";
public const string RetrieveProject = "RetrieveProject";
public const string CreateProject = "CreateProject";
public const string UpdateProject = "UpdateProject";
public const string DeleteProject = "DeleteProject";
public const string ListEnvironments = "ListEnvironments";
public const string RetrieveEnvironment = "RetrieveEnvironment";
public const string CreateEnvironment = "CreateEnvironment";
public const string UpdateEnvironment = "UpdateEnvironment";
public const string DeleteEnvironment = "DeleteEnvironment";
public const string ListFolders = "ListFolders";
public const string RetrieveFolder = "RetrieveFolder";
public const string CreateFolder = "CreateFolder";
public const string UpdateFolder = "UpdateFolder";
public const string DeleteFolder = "DeleteFolder";
public const string ListTags = "ListTags";
public const string RetrieveTag = "RetrieveTag";
public const string CreateTag = "CreateTag";
public const string UpdateTag = "UpdateTag";
public const string DeleteTag = "DeleteTag";
} }
} }
@@ -5,83 +5,495 @@ namespace PSInfisicalAPI.Endpoints
{ {
public static class InfisicalEndpointRegistry public static class InfisicalEndpointRegistry
{ {
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates = private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates;
new Dictionary<string, List<InfisicalEndpointDefinition>>
static InfisicalEndpointRegistry()
{
Candidates = new Dictionary<string, List<InfisicalEndpointDefinition>>();
RegisterAuthentication(Candidates);
RegisterSecrets(Candidates);
RegisterProjects(Candidates);
RegisterEnvironments(Candidates);
RegisterFolders(Candidates);
RegisterTags(Candidates);
}
private static void Add(Dictionary<string, List<InfisicalEndpointDefinition>> map, InfisicalEndpointDefinition definition)
{
List<InfisicalEndpointDefinition> list;
if (!map.TryGetValue(definition.Name, out list))
{ {
{ list = new List<InfisicalEndpointDefinition>();
InfisicalEndpointNames.UniversalAuthLogin, map[definition.Name] = list;
new List<InfisicalEndpointDefinition> }
{
new InfisicalEndpointDefinition list.Add(definition);
{ }
Name = InfisicalEndpointNames.UniversalAuthLogin,
Resource = "Authentication", private static void RegisterAuthentication(Dictionary<string, List<InfisicalEndpointDefinition>> map)
Version = "v1", {
Method = "POST", Add(map, new InfisicalEndpointDefinition
Template = "/api/v1/auth/universal-auth/login", {
RequiresAuthorization = false, Name = InfisicalEndpointNames.UniversalAuthLogin,
ContainsSecretMaterialInRequest = true, Resource = "Authentication",
ContainsSecretMaterialInResponse = true Version = "v1",
} Method = "POST",
} Template = "/api/v1/auth/universal-auth/login",
}, RequiresAuthorization = false,
{ ContainsSecretMaterialInRequest = true,
InfisicalEndpointNames.ListSecrets, ContainsSecretMaterialInResponse = true
new List<InfisicalEndpointDefinition> });
{
new InfisicalEndpointDefinition Add(map, new InfisicalEndpointDefinition
{ {
Name = InfisicalEndpointNames.ListSecrets, Name = InfisicalEndpointNames.TokenAuthLogin,
Resource = "Secrets", Resource = "Authentication",
Version = "v4", Version = "v1",
Method = "GET", Method = "POST",
Template = "/api/v4/secrets", Template = "/api/v1/auth/token-auth/login",
RequiresAuthorization = true, RequiresAuthorization = false,
ContainsSecretMaterialInRequest = false, ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true ContainsSecretMaterialInResponse = true
}, });
new InfisicalEndpointDefinition
{ Add(map, new InfisicalEndpointDefinition
Name = InfisicalEndpointNames.ListSecrets, {
Resource = "Secrets", Name = InfisicalEndpointNames.JwtAuthLogin,
Version = "v3", Resource = "Authentication",
Method = "GET", Version = "v1",
Template = "/api/v3/secrets/raw", Method = "POST",
RequiresAuthorization = true, Template = "/api/v1/auth/jwt-auth/login",
ContainsSecretMaterialInRequest = false, RequiresAuthorization = false,
ContainsSecretMaterialInResponse = true ContainsSecretMaterialInRequest = true,
} ContainsSecretMaterialInResponse = true
} });
},
{ Add(map, new InfisicalEndpointDefinition
InfisicalEndpointNames.RetrieveSecret, {
new List<InfisicalEndpointDefinition> Name = InfisicalEndpointNames.OidcAuthLogin,
{ Resource = "Authentication",
new InfisicalEndpointDefinition Version = "v1",
{ Method = "POST",
Name = InfisicalEndpointNames.RetrieveSecret, Template = "/api/v1/auth/oidc-auth/login",
Resource = "Secrets", RequiresAuthorization = false,
Version = "v4", ContainsSecretMaterialInRequest = true,
Method = "GET", ContainsSecretMaterialInResponse = true
Template = "/api/v4/secrets/{secretName}", });
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = false, Add(map, new InfisicalEndpointDefinition
ContainsSecretMaterialInResponse = true {
}, Name = InfisicalEndpointNames.LdapAuthLogin,
new InfisicalEndpointDefinition Resource = "Authentication",
{ Version = "v1",
Name = InfisicalEndpointNames.RetrieveSecret, Method = "POST",
Resource = "Secrets", Template = "/api/v1/auth/ldap-auth/login",
Version = "v3", RequiresAuthorization = false,
Method = "GET", ContainsSecretMaterialInRequest = true,
Template = "/api/v3/secrets/raw/{secretName}", ContainsSecretMaterialInResponse = true
RequiresAuthorization = true, });
ContainsSecretMaterialInRequest = false,
ContainsSecretMaterialInResponse = true Add(map, new InfisicalEndpointDefinition
} {
} Name = InfisicalEndpointNames.AzureAuthLogin,
} Resource = "Authentication",
}; Version = "v1",
Method = "POST",
Template = "/api/v1/auth/azure-auth/login",
RequiresAuthorization = false,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GcpIamAuthLogin,
Resource = "Authentication",
Version = "v1",
Method = "POST",
Template = "/api/v1/auth/gcp-auth/login",
RequiresAuthorization = false,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
}
private static void RegisterSecrets(Dictionary<string, List<InfisicalEndpointDefinition>> map)
{
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListSecrets,
Resource = "Secrets",
Version = "v4",
Method = "GET",
Template = "/api/v4/secrets",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListSecrets,
Resource = "Secrets",
Version = "v3",
Method = "GET",
Template = "/api/v3/secrets/raw",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveSecret,
Resource = "Secrets",
Version = "v4",
Method = "GET",
Template = "/api/v4/secrets/{secretName}",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveSecret,
Resource = "Secrets",
Version = "v3",
Method = "GET",
Template = "/api/v3/secrets/raw/{secretName}",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.CreateSecret,
Resource = "Secrets",
Version = "v3",
Method = "POST",
Template = "/api/v3/secrets/raw/{secretName}",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.UpdateSecret,
Resource = "Secrets",
Version = "v3",
Method = "PATCH",
Template = "/api/v3/secrets/raw/{secretName}",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DeleteSecret,
Resource = "Secrets",
Version = "v3",
Method = "DELETE",
Template = "/api/v3/secrets/raw/{secretName}",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkCreateSecret,
Resource = "Secrets",
Version = "v4",
Method = "POST",
Template = "/api/v4/secrets/batch",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkCreateSecret,
Resource = "Secrets",
Version = "v3",
Method = "POST",
Template = "/api/v3/secrets/batch/raw",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkUpdateSecret,
Resource = "Secrets",
Version = "v4",
Method = "PATCH",
Template = "/api/v4/secrets/batch",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkUpdateSecret,
Resource = "Secrets",
Version = "v3",
Method = "PATCH",
Template = "/api/v3/secrets/batch/raw",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkDeleteSecret,
Resource = "Secrets",
Version = "v4",
Method = "DELETE",
Template = "/api/v4/secrets/batch",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.BulkDeleteSecret,
Resource = "Secrets",
Version = "v3",
Method = "DELETE",
Template = "/api/v3/secrets/batch/raw",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DuplicateSecret,
Resource = "Secrets",
Version = "v4",
Method = "POST",
Template = "/api/v4/secrets/duplicate",
RequiresAuthorization = true,
ContainsSecretMaterialInRequest = true,
ContainsSecretMaterialInResponse = true
});
}
private static void RegisterProjects(Dictionary<string, List<InfisicalEndpointDefinition>> map)
{
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListProjects,
Resource = "Projects",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveProject,
Resource = "Projects",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace/{projectId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.CreateProject,
Resource = "Projects",
Version = "v2",
Method = "POST",
Template = "/api/v2/workspace",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.UpdateProject,
Resource = "Projects",
Version = "v1",
Method = "PATCH",
Template = "/api/v1/workspace/{projectId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DeleteProject,
Resource = "Projects",
Version = "v1",
Method = "DELETE",
Template = "/api/v1/workspace/{projectId}",
RequiresAuthorization = true
});
}
private static void RegisterEnvironments(Dictionary<string, List<InfisicalEndpointDefinition>> map)
{
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListEnvironments,
Resource = "Environments",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace/{projectId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveEnvironment,
Resource = "Environments",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.CreateEnvironment,
Resource = "Environments",
Version = "v1",
Method = "POST",
Template = "/api/v1/workspace/{projectId}/environments",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.UpdateEnvironment,
Resource = "Environments",
Version = "v1",
Method = "PATCH",
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DeleteEnvironment,
Resource = "Environments",
Version = "v1",
Method = "DELETE",
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
RequiresAuthorization = true
});
}
private static void RegisterFolders(Dictionary<string, List<InfisicalEndpointDefinition>> map)
{
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListFolders,
Resource = "Folders",
Version = "v1",
Method = "GET",
Template = "/api/v1/folders",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveFolder,
Resource = "Folders",
Version = "v1",
Method = "GET",
Template = "/api/v1/folders/{folderId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.CreateFolder,
Resource = "Folders",
Version = "v1",
Method = "POST",
Template = "/api/v1/folders",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.UpdateFolder,
Resource = "Folders",
Version = "v1",
Method = "PATCH",
Template = "/api/v1/folders/{folderId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DeleteFolder,
Resource = "Folders",
Version = "v1",
Method = "DELETE",
Template = "/api/v1/folders/{folderId}",
RequiresAuthorization = true
});
}
private static void RegisterTags(Dictionary<string, List<InfisicalEndpointDefinition>> map)
{
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListTags,
Resource = "Tags",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace/{projectId}/tags",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.RetrieveTag,
Resource = "Tags",
Version = "v1",
Method = "GET",
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.CreateTag,
Resource = "Tags",
Version = "v1",
Method = "POST",
Template = "/api/v1/workspace/{projectId}/tags",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.UpdateTag,
Resource = "Tags",
Version = "v1",
Method = "PATCH",
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.DeleteTag,
Resource = "Tags",
Version = "v1",
Method = "DELETE",
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
RequiresAuthorization = true
});
}
public static InfisicalEndpointDefinition Get(string name) public static InfisicalEndpointDefinition Get(string name)
{ {
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Serialization;
namespace PSInfisicalAPI.Environments
{
public sealed class InfisicalEnvironmentClient
{
private const string Component = "EnvironmentClient";
private readonly IInfisicalLogger _logger;
private readonly JsonInfisicalSerializer _serializer;
private readonly InfisicalApiInvoker _invoker;
public InfisicalEnvironmentClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
_logger = logger ?? NullInfisicalLogger.Instance;
_serializer = new JsonInfisicalSerializer();
_invoker = new InfisicalApiInvoker(httpClient);
}
public InfisicalEnvironment[] List(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
try
{
_logger.Information(Component, "Attempting to list Infisical environments. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListEnvironments, "ListEnvironments", pathParameters, null, null);
InfisicalEnvironmentWorkspaceWrapperDto dto = _serializer.Deserialize<InfisicalEnvironmentWorkspaceWrapperDto>(response.Body);
response.Clear();
InfisicalEnvironmentWorkspaceDto workspace = dto != null ? (dto.Workspace ?? dto.Project) : null;
List<InfisicalEnvironmentResponseDto> envs = workspace != null ? workspace.Environments : null;
InfisicalEnvironment[] mapped = InfisicalEnvironmentMapper.MapMany(envs, resolvedProjectId);
_logger.Information(Component, "Infisical environment list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical environment list retrieval failed.");
throw;
}
}
public InfisicalEnvironment Retrieve(InfisicalConnection connection, string projectId, string environmentSlugOrId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentSlugOrId)) { throw new InfisicalConfigurationException("Environment is required."); }
InfisicalEnvironment[] all = List(connection, resolvedProjectId);
foreach (InfisicalEnvironment env in all)
{
if (string.Equals(env.Id, environmentSlugOrId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(env.Slug, environmentSlugOrId, StringComparison.OrdinalIgnoreCase))
{
return env;
}
}
return null;
}
public InfisicalEnvironment Create(InfisicalConnection connection, string projectId, string name, string slug, int? position)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
InfisicalEnvironmentCreateRequestDto request = new InfisicalEnvironmentCreateRequestDto { Name = name, Slug = slug, Position = position };
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to create Infisical environment '", slug, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateEnvironment, "CreateEnvironment", pathParameters, null, body);
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
response.Clear();
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
_logger.Information(Component, "Infisical environment creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical environment creation failed.");
throw;
}
}
public InfisicalEnvironment Update(InfisicalConnection connection, string projectId, string environmentId, string name, string slug, int? position)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
InfisicalEnvironmentUpdateRequestDto request = new InfisicalEnvironmentUpdateRequestDto { Name = name, Slug = slug, Position = position };
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to update Infisical environment '", environmentId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateEnvironment, "UpdateEnvironment", pathParameters, null, body);
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
response.Clear();
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
_logger.Information(Component, "Infisical environment update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical environment update failed.");
throw;
}
}
public void Delete(InfisicalConnection connection, string projectId, string environmentId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
try
{
_logger.Information(Component, string.Concat("Attempting to delete Infisical environment '", environmentId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteEnvironment, "DeleteEnvironment", pathParameters, null, null);
response.Clear();
_logger.Information(Component, "Infisical environment deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical environment deletion failed.");
throw;
}
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
}
}
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Environments
{
internal sealed class InfisicalEnvironmentResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("_id")] public string InternalId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("position")] public int? Position { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
}
internal sealed class InfisicalEnvironmentSingleResponseDto
{
[JsonProperty("environment")] public InfisicalEnvironmentResponseDto Environment { get; set; }
}
internal sealed class InfisicalEnvironmentWorkspaceWrapperDto
{
[JsonProperty("workspace")] public InfisicalEnvironmentWorkspaceDto Workspace { get; set; }
[JsonProperty("project")] public InfisicalEnvironmentWorkspaceDto Project { get; set; }
}
internal sealed class InfisicalEnvironmentWorkspaceDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("environments")] public List<InfisicalEnvironmentResponseDto> Environments { get; set; }
}
internal sealed class InfisicalEnvironmentCreateRequestDto
{
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int? Position { get; set; }
}
internal sealed class InfisicalEnvironmentUpdateRequestDto
{
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int? Position { get; set; }
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Environments
{
internal static class InfisicalEnvironmentMapper
{
public static InfisicalEnvironment Map(InfisicalEnvironmentResponseDto dto, string fallbackProjectId)
{
if (dto == null)
{
return null;
}
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
? dto.ProjectId
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
return new InfisicalEnvironment
{
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
Name = dto.Name,
Slug = dto.Slug,
Position = dto.Position,
ProjectId = projectId
};
}
public static InfisicalEnvironment[] MapMany(IEnumerable<InfisicalEnvironmentResponseDto> items, string fallbackProjectId)
{
if (items == null)
{
return Array.Empty<InfisicalEnvironment>();
}
List<InfisicalEnvironment> results = new List<InfisicalEnvironment>();
foreach (InfisicalEnvironmentResponseDto dto in items)
{
InfisicalEnvironment mapped = Map(dto, fallbackProjectId);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
}
}
@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Serialization;
namespace PSInfisicalAPI.Folders
{
public sealed class InfisicalFolderClient
{
private const string Component = "FolderClient";
private readonly IInfisicalLogger _logger;
private readonly JsonInfisicalSerializer _serializer;
private readonly InfisicalApiInvoker _invoker;
public InfisicalFolderClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
_logger = logger ?? NullInfisicalLogger.Instance;
_serializer = new JsonInfisicalSerializer();
_invoker = new InfisicalApiInvoker(httpClient);
}
public InfisicalFolder[] List(InfisicalConnection connection, string projectId, string environment, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
new KeyValuePair<string, string>("environment", resolvedEnvironment),
new KeyValuePair<string, string>("path", resolvedPath)
};
try
{
_logger.Information(Component, "Attempting to list Infisical folders. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListFolders, "ListFolders", null, queryParameters, null);
InfisicalFolderListResponseDto dto = _serializer.Deserialize<InfisicalFolderListResponseDto>(response.Body);
response.Clear();
InfisicalFolder[] mapped = InfisicalFolderMapper.MapMany(dto != null ? dto.Folders : null, resolvedProjectId, resolvedEnvironment);
_logger.Information(Component, "Infisical folder list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical folder list retrieval failed.");
throw;
}
}
public InfisicalFolder Retrieve(InfisicalConnection connection, string projectId, string environment, string path, string folderNameOrId)
{
if (string.IsNullOrEmpty(folderNameOrId)) { throw new InfisicalConfigurationException("Folder name or id is required."); }
InfisicalFolder[] all = List(connection, projectId, environment, path);
foreach (InfisicalFolder folder in all)
{
if (string.Equals(folder.Id, folderNameOrId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(folder.Name, folderNameOrId, StringComparison.OrdinalIgnoreCase))
{
return folder;
}
}
return null;
}
public InfisicalFolder Create(InfisicalConnection connection, string projectId, string environment, string name, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
InfisicalFolderCreateRequestDto request = new InfisicalFolderCreateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
Name = name,
Path = resolvedPath
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to create Infisical folder '", name, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateFolder, "CreateFolder", null, null, body);
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
response.Clear();
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
_logger.Information(Component, "Infisical folder creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical folder creation failed.");
throw;
}
}
public InfisicalFolder Update(InfisicalConnection connection, string projectId, string environment, string folderId, string name, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
InfisicalFolderUpdateRequestDto request = new InfisicalFolderUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
Name = name,
Path = resolvedPath
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to update Infisical folder '", folderId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateFolder, "UpdateFolder", pathParameters, null, body);
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
response.Clear();
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
_logger.Information(Component, "Infisical folder update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical folder update failed.");
throw;
}
}
public void Delete(InfisicalConnection connection, string projectId, string environment, string folderId, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
new KeyValuePair<string, string>("environment", resolvedEnvironment),
new KeyValuePair<string, string>("path", resolvedPath)
};
try
{
_logger.Information(Component, string.Concat("Attempting to delete Infisical folder '", folderId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteFolder, "DeleteFolder", pathParameters, queryParameters, null);
response.Clear();
_logger.Information(Component, "Infisical folder deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical folder deletion failed.");
throw;
}
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
}
}
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Folders
{
internal sealed class InfisicalFolderResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("_id")] public string InternalId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("path")] public string Path { get; set; }
[JsonProperty("parentId")] public string ParentId { get; set; }
[JsonProperty("envId")] public string EnvId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
}
internal sealed class InfisicalFolderListResponseDto
{
[JsonProperty("folders")] public List<InfisicalFolderResponseDto> Folders { get; set; }
}
internal sealed class InfisicalFolderSingleResponseDto
{
[JsonProperty("folder")] public InfisicalFolderResponseDto Folder { get; set; }
}
internal sealed class InfisicalFolderCreateRequestDto
{
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)] public string Path { get; set; }
[JsonProperty("directory", NullValueHandling = NullValueHandling.Ignore)] public string Directory { get; set; }
}
internal sealed class InfisicalFolderUpdateRequestDto
{
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)] public string Path { get; set; }
}
}
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Folders
{
internal static class InfisicalFolderMapper
{
public static InfisicalFolder Map(InfisicalFolderResponseDto dto, string fallbackProjectId, string fallbackEnvironment)
{
if (dto == null)
{
return null;
}
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
? dto.ProjectId
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
string environment = !string.IsNullOrEmpty(dto.Environment) ? dto.Environment : fallbackEnvironment;
return new InfisicalFolder
{
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
Name = dto.Name,
Path = dto.Path,
ParentId = dto.ParentId,
Environment = environment,
ProjectId = projectId,
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
public static InfisicalFolder[] MapMany(IEnumerable<InfisicalFolderResponseDto> items, string fallbackProjectId, string fallbackEnvironment)
{
if (items == null)
{
return Array.Empty<InfisicalFolder>();
}
List<InfisicalFolder> results = new List<InfisicalFolder>();
foreach (InfisicalFolderResponseDto dto in items)
{
InfisicalFolder mapped = Map(dto, fallbackProjectId, fallbackEnvironment);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
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;
}
}
}
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Security;
namespace PSInfisicalAPI.Http
{
internal sealed class InfisicalApiInvoker
{
private readonly IInfisicalHttpClient _httpClient;
public InfisicalApiInvoker(IInfisicalHttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public InfisicalHttpResponse Invoke(
InfisicalConnection connection,
string endpointName,
string operationName,
IDictionary<string, string> pathParameters,
IEnumerable<KeyValuePair<string, string>> queryParameters,
string body)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); }
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName);
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
if (response.StatusCode >= 200 && response.StatusCode < 300)
{
return response;
}
InfisicalApiException exception = BuildApiException(response, definition);
response.Clear();
throw exception;
}
private InfisicalHttpResponse ExecuteAuthorized(
InfisicalConnection connection,
InfisicalEndpointDefinition definition,
string operationName,
Uri uri,
string body)
{
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
headers["Accept"] = "application/json";
if (!string.IsNullOrEmpty(body))
{
headers["Content-Type"] = "application/json";
}
if (definition.RequiresAuthorization)
{
if (connection.AccessToken == null)
{
throw new InfisicalAuthenticationException("Connection does not contain an access token.");
}
SecureStringUtility.UsePlainText(connection.AccessToken, plainToken =>
{
headers["Authorization"] = string.Concat("Bearer ", plainToken ?? string.Empty);
});
}
InfisicalHttpRequest request = new InfisicalHttpRequest
{
OperationName = operationName,
EndpointName = definition.Name,
Method = definition.Method,
Uri = uri,
Headers = headers,
Body = body,
ContainsSecretMaterialInRequest = definition.ContainsSecretMaterialInRequest,
ContainsSecretMaterialInResponse = definition.ContainsSecretMaterialInResponse
};
return _httpClient.Send(request);
}
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
{
InfisicalApiException exception = new InfisicalApiException(string.Concat(
"Infisical API returned ",
response.StatusCode.ToString(CultureInfo.InvariantCulture),
" (", response.ReasonPhrase ?? string.Empty, ")."));
exception.StatusCode = response.StatusCode;
exception.ReasonPhrase = response.ReasonPhrase;
exception.EndpointName = definition.Name;
exception.RequestMethod = definition.Method;
exception.SanitizedBody = response.Body;
return exception;
}
}
}
@@ -3,6 +3,11 @@ namespace PSInfisicalAPI.Models
public enum InfisicalAuthType public enum InfisicalAuthType
{ {
UniversalAuth, UniversalAuth,
Token Token,
Jwt,
Oidc,
Ldap,
Azure,
GcpIam
} }
} }
@@ -0,0 +1,16 @@
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalEnvironment
{
public string Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public int? Position { get; set; }
public string ProjectId { get; set; }
public override string ToString()
{
return string.IsNullOrEmpty(Slug) ? (Name ?? Id) : Slug;
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalFolder
{
public string Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public string ParentId { get; set; }
public string Environment { get; set; }
public string ProjectId { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
public override string ToString()
{
return string.IsNullOrEmpty(Path) ? (Name ?? Id) : Path;
}
}
}
@@ -0,0 +1,23 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalProject
{
public string Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public string Description { get; set; }
public string OrganizationId { get; set; }
public string Type { get; set; }
public bool AutoCapitalization { get; set; }
public string[] EnvironmentSlugs { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
public override string ToString()
{
return string.IsNullOrEmpty(Slug) ? (Name ?? Id) : Slug;
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalTag
{
public string Id { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public string ProjectId { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
public override string ToString()
{
return Slug ?? Name ?? Id;
}
}
}
@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Serialization;
namespace PSInfisicalAPI.Projects
{
public sealed class InfisicalProjectClient
{
private const string Component = "ProjectClient";
private readonly IInfisicalLogger _logger;
private readonly JsonInfisicalSerializer _serializer;
private readonly InfisicalApiInvoker _invoker;
public InfisicalProjectClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
_logger = logger ?? NullInfisicalLogger.Instance;
_serializer = new JsonInfisicalSerializer();
_invoker = new InfisicalApiInvoker(httpClient);
}
public InfisicalProject[] List(InfisicalConnection connection)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
try
{
_logger.Information(Component, "Attempting to list Infisical projects. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListProjects, "ListProjects", null, null, null);
InfisicalProjectListResponseDto dto = _serializer.Deserialize<InfisicalProjectListResponseDto>(response.Body);
response.Clear();
List<InfisicalProjectResponseDto> source = (dto != null && dto.Workspaces != null) ? dto.Workspaces : (dto != null ? dto.Projects : null);
InfisicalProject[] mapped = InfisicalProjectMapper.MapMany(source);
_logger.Information(Component, "Infisical project list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical project list retrieval failed.");
throw;
}
}
public InfisicalProject Retrieve(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical project '", projectId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.RetrieveProject, "RetrieveProject", pathParameters, null, null);
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
response.Clear();
InfisicalProjectResponseDto inner = dto != null ? (dto.Workspace ?? dto.Project) : null;
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
_logger.Information(Component, "Infisical project retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical project retrieval failed.");
throw;
}
}
public InfisicalProject Create(InfisicalConnection connection, string projectName, string slug, string description, string type, string organizationId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectName)) { throw new InfisicalConfigurationException("ProjectName is required."); }
InfisicalProjectCreateRequestDto request = new InfisicalProjectCreateRequestDto
{
ProjectName = projectName,
Slug = slug,
ProjectDescription = description,
Type = type,
OrganizationId = organizationId
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to create Infisical project '", projectName, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateProject, "CreateProject", null, null, body);
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
response.Clear();
InfisicalProjectResponseDto inner = dto != null ? (dto.Project ?? dto.Workspace) : null;
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
_logger.Information(Component, "Infisical project creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical project creation failed.");
throw;
}
}
public InfisicalProject Update(InfisicalConnection connection, string projectId, string name, string description, bool? autoCapitalization)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
InfisicalProjectUpdateRequestDto request = new InfisicalProjectUpdateRequestDto { Name = name, Description = description, AutoCapitalization = autoCapitalization };
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to update Infisical project '", projectId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateProject, "UpdateProject", pathParameters, null, body);
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
response.Clear();
InfisicalProjectResponseDto inner = dto != null ? (dto.Workspace ?? dto.Project) : null;
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
_logger.Information(Component, "Infisical project update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical project update failed.");
throw;
}
}
public void Delete(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
try
{
_logger.Information(Component, string.Concat("Attempting to delete Infisical project '", projectId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteProject, "DeleteProject", pathParameters, null, null);
response.Clear();
_logger.Information(Component, "Infisical project deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical project deletion failed.");
throw;
}
}
}
}
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Projects
{
internal sealed class InfisicalProjectResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("_id")] public string InternalId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("organization")] public string Organization { get; set; }
[JsonProperty("orgId")] public string OrgId { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("autoCapitalization")] public bool AutoCapitalization { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
[JsonProperty("environments")] public List<InfisicalProjectEnvironmentDto> Environments { get; set; }
}
internal sealed class InfisicalProjectEnvironmentDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
}
internal sealed class InfisicalProjectListResponseDto
{
[JsonProperty("workspaces")] public List<InfisicalProjectResponseDto> Workspaces { get; set; }
[JsonProperty("projects")] public List<InfisicalProjectResponseDto> Projects { get; set; }
}
internal sealed class InfisicalProjectSingleResponseDto
{
[JsonProperty("workspace")] public InfisicalProjectResponseDto Workspace { get; set; }
[JsonProperty("project")] public InfisicalProjectResponseDto Project { get; set; }
}
internal sealed class InfisicalProjectCreateRequestDto
{
[JsonProperty("projectName")] public string ProjectName { get; set; }
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
[JsonProperty("projectDescription", NullValueHandling = NullValueHandling.Ignore)] public string ProjectDescription { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
[JsonProperty("organizationId", NullValueHandling = NullValueHandling.Ignore)] public string OrganizationId { get; set; }
}
internal sealed class InfisicalProjectUpdateRequestDto
{
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; }
[JsonProperty("autoCapitalization", NullValueHandling = NullValueHandling.Ignore)] public bool? AutoCapitalization { get; set; }
}
}
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Projects
{
internal static class InfisicalProjectMapper
{
public static InfisicalProject Map(InfisicalProjectResponseDto dto)
{
if (dto == null)
{
return null;
}
InfisicalProject project = new InfisicalProject
{
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
Name = dto.Name,
Slug = dto.Slug,
Description = dto.Description,
OrganizationId = !string.IsNullOrEmpty(dto.Organization) ? dto.Organization : dto.OrgId,
Type = dto.Type,
AutoCapitalization = dto.AutoCapitalization,
EnvironmentSlugs = MapEnvironmentSlugs(dto.Environments),
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
return project;
}
public static InfisicalProject[] MapMany(IEnumerable<InfisicalProjectResponseDto> items)
{
if (items == null)
{
return Array.Empty<InfisicalProject>();
}
List<InfisicalProject> results = new List<InfisicalProject>();
foreach (InfisicalProjectResponseDto dto in items)
{
InfisicalProject mapped = Map(dto);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
private static string[] MapEnvironmentSlugs(List<InfisicalProjectEnvironmentDto> environments)
{
if (environments == null || environments.Count == 0)
{
return Array.Empty<string>();
}
List<string> slugs = new List<string>(environments.Count);
foreach (InfisicalProjectEnvironmentDto env in environments)
{
if (env != null && !string.IsNullOrEmpty(env.Slug))
{
slugs.Add(env.Slug);
}
}
return slugs.ToArray();
}
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;
}
}
}
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Errors;
namespace PSInfisicalAPI.Secrets
{
public static class InfisicalBulkSecretConverter
{
public static InfisicalBulkCreateSecretItem[] ToCreateItems(IEnumerable<IDictionary<string, string>> input)
{
if (input == null) { return new InfisicalBulkCreateSecretItem[0]; }
List<InfisicalBulkCreateSecretItem> list = new List<InfisicalBulkCreateSecretItem>();
foreach (IDictionary<string, string> entry in input)
{
if (entry == null) { continue; }
IDictionary<string, string> table = Normalize(entry);
InfisicalBulkCreateSecretItem item = new InfisicalBulkCreateSecretItem
{
SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"),
SecretValue = GetString(table, "SecretValue", "Value"),
SecretComment = GetString(table, "SecretComment", "Comment"),
SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"),
TagIds = GetStringArray(table, "TagIds")
};
if (string.IsNullOrEmpty(item.SecretName))
{
throw new InfisicalConfigurationException("Each bulk-create entry must include 'SecretName' (or 'Name'/'Key').");
}
list.Add(item);
}
return list.ToArray();
}
public static InfisicalBulkUpdateSecretItem[] ToUpdateItems(IEnumerable<IDictionary<string, string>> input)
{
if (input == null) { return new InfisicalBulkUpdateSecretItem[0]; }
List<InfisicalBulkUpdateSecretItem> list = new List<InfisicalBulkUpdateSecretItem>();
foreach (IDictionary<string, string> entry in input)
{
if (entry == null) { continue; }
IDictionary<string, string> table = Normalize(entry);
InfisicalBulkUpdateSecretItem item = new InfisicalBulkUpdateSecretItem
{
SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"),
NewSecretName = GetString(table, "NewSecretName", "NewName"),
SecretValue = GetString(table, "SecretValue", "Value"),
SecretComment = GetString(table, "SecretComment", "Comment"),
SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"),
TagIds = GetStringArray(table, "TagIds")
};
if (string.IsNullOrEmpty(item.SecretName))
{
throw new InfisicalConfigurationException("Each bulk-update entry must include 'SecretName' (or 'Name'/'Key').");
}
list.Add(item);
}
return list.ToArray();
}
private static IDictionary<string, string> Normalize(IDictionary<string, string> source)
{
Dictionary<string, string> normalized = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> kvp in source)
{
if (string.IsNullOrEmpty(kvp.Key)) { continue; }
normalized[kvp.Key] = kvp.Value;
}
return normalized;
}
private static string GetString(IDictionary<string, string> table, params string[] keys)
{
foreach (string key in keys)
{
string value;
if (table.TryGetValue(key, out value) && !string.IsNullOrEmpty(value))
{
return value;
}
}
return null;
}
private static bool? GetBool(IDictionary<string, string> table, string key)
{
string value;
if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; }
bool parsed;
return bool.TryParse(value, out parsed) ? parsed : (bool?)null;
}
private static string[] GetStringArray(IDictionary<string, string> table, string key)
{
string value;
if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; }
string[] parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
List<string> trimmed = new List<string>(parts.Length);
foreach (string part in parts)
{
string item = part.Trim();
if (!string.IsNullOrEmpty(item)) { trimmed.Add(item); }
}
return trimmed.Count == 0 ? null : trimmed.ToArray();
}
}
}
@@ -55,4 +55,111 @@ namespace PSInfisicalAPI.Secrets
{ {
[JsonProperty("secret")] public InfisicalSecretResponseDto Secret { get; set; } [JsonProperty("secret")] public InfisicalSecretResponseDto Secret { get; set; }
} }
internal sealed class InfisicalSecretCreateRequestDto
{
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
[JsonProperty("secretValue")] public string SecretValue { get; set; }
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
}
internal sealed class InfisicalSecretUpdateRequestDto
{
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
[JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public string SecretValue { get; set; }
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
[JsonProperty("newSecretName", NullValueHandling = NullValueHandling.Ignore)] public string NewSecretName { get; set; }
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
}
internal sealed class InfisicalSecretDeleteRequestDto
{
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
}
internal sealed class InfisicalSecretBatchCreateItemDto
{
[JsonProperty("secretKey")] public string SecretKey { get; set; }
[JsonProperty("secretValue")] public string SecretValue { get; set; }
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
[JsonProperty("secretMetadata", NullValueHandling = NullValueHandling.Ignore)] public List<InfisicalSecretMetadataDto> SecretMetadata { get; set; }
}
internal sealed class InfisicalSecretBatchUpdateItemDto
{
[JsonProperty("secretKey")] public string SecretKey { get; set; }
[JsonProperty("newSecretName", NullValueHandling = NullValueHandling.Ignore)] public string NewSecretName { get; set; }
[JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public string SecretValue { get; set; }
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
[JsonProperty("secretMetadata", NullValueHandling = NullValueHandling.Ignore)] public List<InfisicalSecretMetadataDto> SecretMetadata { get; set; }
}
internal sealed class InfisicalSecretBatchDeleteItemDto
{
[JsonProperty("secretKey")] public string SecretKey { get; set; }
}
internal sealed class InfisicalSecretBatchCreateRequestDto
{
[JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; }
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("secrets")] public List<InfisicalSecretBatchCreateItemDto> Secrets { get; set; }
}
internal sealed class InfisicalSecretBatchUpdateRequestDto
{
[JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; }
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("mode", NullValueHandling = NullValueHandling.Ignore)] public string Mode { get; set; }
[JsonProperty("secrets")] public List<InfisicalSecretBatchUpdateItemDto> Secrets { get; set; }
}
internal sealed class InfisicalSecretBatchDeleteRequestDto
{
[JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; }
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
[JsonProperty("environment")] public string Environment { get; set; }
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("secrets")] public List<InfisicalSecretBatchDeleteItemDto> Secrets { get; set; }
}
internal sealed class InfisicalSecretDuplicateAttributesDto
{
[JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public bool? SecretValue { get; set; }
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public bool? SecretComment { get; set; }
[JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)] public bool? Tags { get; set; }
[JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] public bool? Metadata { get; set; }
}
internal sealed class InfisicalSecretDuplicateRequestDto
{
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("sourceEnvironment")] public string SourceEnvironment { get; set; }
[JsonProperty("destinationEnvironment")] public string DestinationEnvironment { get; set; }
[JsonProperty("sourceSecretPath", NullValueHandling = NullValueHandling.Ignore)] public string SourceSecretPath { get; set; }
[JsonProperty("destinationSecretPath", NullValueHandling = NullValueHandling.Ignore)] public string DestinationSecretPath { get; set; }
[JsonProperty("secretIds")] public string[] SecretIds { get; set; }
[JsonProperty("overwriteExisting", NullValueHandling = NullValueHandling.Ignore)] public bool? OverwriteExisting { get; set; }
[JsonProperty("attributesToCopy", NullValueHandling = NullValueHandling.Ignore)] public InfisicalSecretDuplicateAttributesDto AttributesToCopy { get; set; }
}
} }
@@ -30,4 +30,108 @@ namespace PSInfisicalAPI.Secrets
public bool? ExpandSecretReferences { get; set; } public bool? ExpandSecretReferences { get; set; }
public bool? IncludeImports { get; set; } public bool? IncludeImports { get; set; }
} }
public sealed class InfisicalCreateSecretRequest
{
public string SecretName { get; set; }
public string SecretValue { get; set; }
public string SecretComment { get; set; }
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string Type { get; set; }
public string ApiVersion { get; set; }
public bool? SkipMultilineEncoding { get; set; }
public string[] TagIds { get; set; }
}
public sealed class InfisicalUpdateSecretRequest
{
public string SecretName { get; set; }
public string NewSecretName { get; set; }
public string SecretValue { get; set; }
public string SecretComment { get; set; }
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string Type { get; set; }
public string ApiVersion { get; set; }
public bool? SkipMultilineEncoding { get; set; }
public string[] TagIds { get; set; }
}
public sealed class InfisicalDeleteSecretRequest
{
public string SecretName { get; set; }
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string Type { get; set; }
public string ApiVersion { get; set; }
}
public sealed class InfisicalBulkCreateSecretItem
{
public string SecretName { get; set; }
public string SecretValue { get; set; }
public string SecretComment { get; set; }
public bool? SkipMultilineEncoding { get; set; }
public string[] TagIds { get; set; }
public Dictionary<string, string> SecretMetadata { get; set; }
}
public sealed class InfisicalBulkUpdateSecretItem
{
public string SecretName { get; set; }
public string NewSecretName { get; set; }
public string SecretValue { get; set; }
public string SecretComment { get; set; }
public bool? SkipMultilineEncoding { get; set; }
public string[] TagIds { get; set; }
public Dictionary<string, string> SecretMetadata { get; set; }
}
public sealed class InfisicalBulkCreateSecretsRequest
{
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string ApiVersion { get; set; }
public InfisicalBulkCreateSecretItem[] Secrets { get; set; }
}
public sealed class InfisicalBulkUpdateSecretsRequest
{
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string ApiVersion { get; set; }
public string Mode { get; set; }
public InfisicalBulkUpdateSecretItem[] Secrets { get; set; }
}
public sealed class InfisicalBulkDeleteSecretsRequest
{
public string ProjectId { get; set; }
public string Environment { get; set; }
public string SecretPath { get; set; }
public string ApiVersion { get; set; }
public string[] SecretNames { get; set; }
}
public sealed class InfisicalDuplicateSecretsRequest
{
public string ProjectId { get; set; }
public string SourceEnvironment { get; set; }
public string DestinationEnvironment { get; set; }
public string SourceSecretPath { get; set; }
public string DestinationSecretPath { get; set; }
public string[] SecretIds { get; set; }
public bool? OverwriteExisting { get; set; }
public bool? CopySecretValue { get; set; }
public bool? CopySecretComment { get; set; }
public bool? CopyTags { get; set; }
public bool? CopyMetadata { get; set; }
public string ApiVersion { get; set; }
}
} }
@@ -136,6 +136,353 @@ namespace PSInfisicalAPI.Secrets
} }
} }
public InfisicalSecret Create(InfisicalConnection connection, InfisicalCreateSecretRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
if (request.SecretValue == null) { throw new InfisicalConfigurationException("SecretValue is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretCreateRequestDto dtoRequest = new InfisicalSecretCreateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
SecretValue = request.SecretValue,
SecretComment = request.SecretComment,
SkipMultilineEncoding = request.SkipMultilineEncoding,
TagIds = request.TagIds
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to create Infisical secret '", request.SecretName, "'. Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.CreateSecret, request.ApiVersion, "CreateSecret", pathParameters, null, body);
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
response.Clear();
InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null);
_logger.Information(Component, "Infisical secret creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical secret creation failed.");
throw;
}
}
public InfisicalSecret Update(InfisicalConnection connection, InfisicalUpdateSecretRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretUpdateRequestDto dtoRequest = new InfisicalSecretUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
SecretValue = request.SecretValue,
SecretComment = request.SecretComment,
NewSecretName = request.NewSecretName,
SkipMultilineEncoding = request.SkipMultilineEncoding,
TagIds = request.TagIds
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to update Infisical secret '", request.SecretName, "'. Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.UpdateSecret, request.ApiVersion, "UpdateSecret", pathParameters, null, body);
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
response.Clear();
InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null);
_logger.Information(Component, "Infisical secret update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical secret update failed.");
throw;
}
}
public InfisicalSecret[] CreateBatch(InfisicalConnection connection, InfisicalBulkCreateSecretsRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchCreateItemDto> items = new List<InfisicalSecretBatchCreateItemDto>(request.Secrets.Length);
foreach (InfisicalBulkCreateSecretItem item in request.Secrets)
{
if (item == null) { continue; }
if (string.IsNullOrEmpty(item.SecretName)) { throw new InfisicalConfigurationException("Each bulk-create item requires SecretName."); }
items.Add(new InfisicalSecretBatchCreateItemDto
{
SecretKey = item.SecretName,
SecretValue = item.SecretValue ?? string.Empty,
SecretComment = item.SecretComment,
SkipMultilineEncoding = item.SkipMultilineEncoding,
TagIds = item.TagIds,
SecretMetadata = ToMetadataDtoList(item.SecretMetadata)
});
}
InfisicalSecretBatchCreateRequestDto dtoRequest = new InfisicalSecretBatchCreateRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Secrets = items
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to bulk-create ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkCreateSecret, request.ApiVersion, "BulkCreateSecrets", null, null, body);
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
response.Clear();
InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null);
_logger.Information(Component, "Infisical bulk secret creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical bulk secret creation failed.");
throw;
}
}
public InfisicalSecret[] UpdateBatch(InfisicalConnection connection, InfisicalBulkUpdateSecretsRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchUpdateItemDto> items = new List<InfisicalSecretBatchUpdateItemDto>(request.Secrets.Length);
foreach (InfisicalBulkUpdateSecretItem item in request.Secrets)
{
if (item == null) { continue; }
if (string.IsNullOrEmpty(item.SecretName)) { throw new InfisicalConfigurationException("Each bulk-update item requires SecretName."); }
items.Add(new InfisicalSecretBatchUpdateItemDto
{
SecretKey = item.SecretName,
NewSecretName = item.NewSecretName,
SecretValue = item.SecretValue,
SecretComment = item.SecretComment,
SkipMultilineEncoding = item.SkipMultilineEncoding,
TagIds = item.TagIds,
SecretMetadata = ToMetadataDtoList(item.SecretMetadata)
});
}
InfisicalSecretBatchUpdateRequestDto dtoRequest = new InfisicalSecretBatchUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Mode = request.Mode,
Secrets = items
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to bulk-update ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkUpdateSecret, request.ApiVersion, "BulkUpdateSecrets", null, null, body);
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
response.Clear();
InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null);
_logger.Information(Component, "Infisical bulk secret update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical bulk secret update failed.");
throw;
}
}
public void DeleteBatch(InfisicalConnection connection, InfisicalBulkDeleteSecretsRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.SecretNames == null || request.SecretNames.Length == 0) { throw new InfisicalConfigurationException("At least one secret name is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchDeleteItemDto> items = new List<InfisicalSecretBatchDeleteItemDto>(request.SecretNames.Length);
foreach (string name in request.SecretNames)
{
if (string.IsNullOrEmpty(name)) { continue; }
items.Add(new InfisicalSecretBatchDeleteItemDto { SecretKey = name });
}
InfisicalSecretBatchDeleteRequestDto dtoRequest = new InfisicalSecretBatchDeleteRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Secrets = items
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to bulk-delete ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkDeleteSecret, request.ApiVersion, "BulkDeleteSecrets", null, null, body);
response.Clear();
_logger.Information(Component, "Infisical bulk secret deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical bulk secret deletion failed.");
throw;
}
}
public InfisicalSecret[] Duplicate(InfisicalConnection connection, InfisicalDuplicateSecretsRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.SecretIds == null || request.SecretIds.Length == 0) { throw new InfisicalConfigurationException("At least one SecretId is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedSourceEnv = FirstNonEmpty(request.SourceEnvironment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedSourceEnv)) { throw new InfisicalConfigurationException("SourceEnvironment is required."); }
if (string.IsNullOrEmpty(request.DestinationEnvironment)) { throw new InfisicalConfigurationException("DestinationEnvironment is required."); }
string resolvedSourcePath = FirstNonEmpty(request.SourceSecretPath, connection.DefaultSecretPath, "/");
string resolvedDestPath = FirstNonEmpty(request.DestinationSecretPath, resolvedSourcePath);
InfisicalSecretDuplicateAttributesDto attributes = null;
if (request.CopySecretValue.HasValue || request.CopySecretComment.HasValue || request.CopyTags.HasValue || request.CopyMetadata.HasValue)
{
attributes = new InfisicalSecretDuplicateAttributesDto
{
SecretValue = request.CopySecretValue,
SecretComment = request.CopySecretComment,
Tags = request.CopyTags,
Metadata = request.CopyMetadata
};
}
InfisicalSecretDuplicateRequestDto dtoRequest = new InfisicalSecretDuplicateRequestDto
{
ProjectId = resolvedProjectId,
SourceEnvironment = resolvedSourceEnv,
DestinationEnvironment = request.DestinationEnvironment,
SourceSecretPath = resolvedSourcePath,
DestinationSecretPath = resolvedDestPath,
SecretIds = request.SecretIds,
OverwriteExisting = request.OverwriteExisting,
AttributesToCopy = attributes
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to duplicate ", request.SecretIds.Length.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.DuplicateSecret, request.ApiVersion, "DuplicateSecrets", null, null, body);
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
response.Clear();
InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null);
_logger.Information(Component, "Infisical secret duplication was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical secret duplication failed.");
throw;
}
}
private static List<InfisicalSecretMetadataDto> ToMetadataDtoList(Dictionary<string, string> metadata)
{
if (metadata == null || metadata.Count == 0) { return null; }
List<InfisicalSecretMetadataDto> list = new List<InfisicalSecretMetadataDto>(metadata.Count);
foreach (KeyValuePair<string, string> kvp in metadata)
{
list.Add(new InfisicalSecretMetadataDto { Key = kvp.Key, Value = kvp.Value });
}
return list;
}
public void Delete(InfisicalConnection connection, InfisicalDeleteSecretRequest request)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretDeleteRequestDto dtoRequest = new InfisicalSecretDeleteRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant()
};
string body = _serializer.Serialize(dtoRequest);
try
{
_logger.Information(Component, string.Concat("Attempting to delete Infisical secret '", request.SecretName, "'. Please Wait..."));
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.DeleteSecret, request.ApiVersion, "DeleteSecret", pathParameters, null, body);
response.Clear();
_logger.Information(Component, "Infisical secret deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical secret deletion failed.");
throw;
}
}
private InfisicalHttpResponse SendWithVersionFallback( private InfisicalHttpResponse SendWithVersionFallback(
InfisicalConnection connection, InfisicalConnection connection,
string endpointName, string endpointName,
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Serialization;
namespace PSInfisicalAPI.Tags
{
public sealed class InfisicalTagClient
{
private const string Component = "TagClient";
private readonly IInfisicalLogger _logger;
private readonly JsonInfisicalSerializer _serializer;
private readonly InfisicalApiInvoker _invoker;
public InfisicalTagClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
{
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
_logger = logger ?? NullInfisicalLogger.Instance;
_serializer = new JsonInfisicalSerializer();
_invoker = new InfisicalApiInvoker(httpClient);
}
public InfisicalTag[] List(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
try
{
_logger.Information(Component, "Attempting to list Infisical tags. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListTags, "ListTags", pathParameters, null, null);
InfisicalTagListResponseDto dto = _serializer.Deserialize<InfisicalTagListResponseDto>(response.Body);
response.Clear();
List<InfisicalTagResponseDto> source = dto != null ? (dto.WorkspaceTags ?? dto.Tags) : null;
InfisicalTag[] mapped = InfisicalTagMapper.MapMany(source, resolvedProjectId);
_logger.Information(Component, "Infisical tag list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical tag list retrieval failed.");
throw;
}
}
public InfisicalTag Retrieve(InfisicalConnection connection, string projectId, string tagSlugOrId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagSlugOrId)) { throw new InfisicalConfigurationException("Tag slug or id is required."); }
InfisicalTag[] all = List(connection, resolvedProjectId);
foreach (InfisicalTag tag in all)
{
if (string.Equals(tag.Id, tagSlugOrId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(tag.Slug, tagSlugOrId, StringComparison.OrdinalIgnoreCase))
{
return tag;
}
}
return null;
}
public InfisicalTag Create(InfisicalConnection connection, string projectId, string slug, string name, string color)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
InfisicalTagCreateRequestDto request = new InfisicalTagCreateRequestDto { Slug = slug, Name = name, Color = color };
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to create Infisical tag '", slug, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateTag, "CreateTag", pathParameters, null, body);
InfisicalTagSingleResponseDto dto = _serializer.Deserialize<InfisicalTagSingleResponseDto>(response.Body);
response.Clear();
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
_logger.Information(Component, "Infisical tag creation was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical tag creation failed.");
throw;
}
}
public InfisicalTag Update(InfisicalConnection connection, string projectId, string tagId, string slug, string name, string color)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
InfisicalTagUpdateRequestDto request = new InfisicalTagUpdateRequestDto { Slug = slug, Name = name, Color = color };
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to update Infisical tag '", tagId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateTag, "UpdateTag", pathParameters, null, body);
InfisicalTagSingleResponseDto dto = _serializer.Deserialize<InfisicalTagSingleResponseDto>(response.Body);
response.Clear();
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
_logger.Information(Component, "Infisical tag update was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical tag update failed.");
throw;
}
}
public void Delete(InfisicalConnection connection, string projectId, string tagId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
try
{
_logger.Information(Component, string.Concat("Attempting to delete Infisical tag '", tagId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteTag, "DeleteTag", pathParameters, null, null);
response.Clear();
_logger.Information(Component, "Infisical tag deletion was successful.");
}
catch (Exception)
{
_logger.Error(Component, "Infisical tag deletion failed.");
throw;
}
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
}
}
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Tags
{
internal sealed class InfisicalTagResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("_id")] public string InternalId { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("color")] public string Color { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
}
internal sealed class InfisicalTagListResponseDto
{
[JsonProperty("workspaceTags")] public List<InfisicalTagResponseDto> WorkspaceTags { get; set; }
[JsonProperty("tags")] public List<InfisicalTagResponseDto> Tags { get; set; }
}
internal sealed class InfisicalTagSingleResponseDto
{
[JsonProperty("workspaceTag")] public InfisicalTagResponseDto WorkspaceTag { get; set; }
[JsonProperty("tag")] public InfisicalTagResponseDto Tag { get; set; }
}
internal sealed class InfisicalTagCreateRequestDto
{
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] public string Color { get; set; }
}
internal sealed class InfisicalTagUpdateRequestDto
{
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] public string Color { get; set; }
}
}
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Tags
{
internal static class InfisicalTagMapper
{
public static InfisicalTag Map(InfisicalTagResponseDto dto, string fallbackProjectId)
{
if (dto == null)
{
return null;
}
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
? dto.ProjectId
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
return new InfisicalTag
{
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
Slug = dto.Slug,
Name = dto.Name,
Color = dto.Color,
ProjectId = projectId,
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
public static InfisicalTag[] MapMany(IEnumerable<InfisicalTagResponseDto> items, string fallbackProjectId)
{
if (items == null)
{
return Array.Empty<InfisicalTag>();
}
List<InfisicalTag> results = new List<InfisicalTag>();
foreach (InfisicalTagResponseDto dto in items)
{
InfisicalTag mapped = Map(dto, fallbackProjectId);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
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;
}
}
}