Skip to content

Commit f6819cd

Browse files
🌟 [Major]: Context as PSCustomObject (#48)
## Description Think we are finally there: - Context are now returned as `[PSCustomObjects]`. - Fixes #45 - SecureStrings are converted to plain text and prefixed with `[SECURE]` and converted back when put in the customobject before being returned. - Context are stored as JSON compressed strings. - Fixes #47 - Contexts now have an ID, that is decoupled from the contents of the context, allowing to set them independently from the context values. - Fixes #46 - Removed initialization parameters for context vault, making the initialization call simpler. - Change parameter names for `Contexts`: - The parameter identifying the context is now called `ID` and `ContextID`. - Change parameter names for `ContextSettings`: - The parameter identifying the context is now called `ID` and `ContextID`. - Updated tests ## Type of change <!-- Use the check-boxes [x] on the options that are relevant. --> - [ ] 📖 [Docs] - [ ] 🪲 [Fix] - [ ] 🩹 [Patch] - [ ] ⚠️ [Security fix] - [ ] 🚀 [Feature] - [x] 🌟 [Breaking change] ## Checklist <!-- Use the check-boxes [x] on the options that are relevant. --> - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas
1 parent 2bb6321 commit f6819cd

17 files changed

+570
-244
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ $script:Config = @{
100100

101101
`src\loader.ps1`
102102
```pwsh
103-
Write-Verbose "Initialized secret vault [$($script:Config.Context.VaultName)] of type [$($script:Config.Context.VaultType)]"
103+
Write-Verbose "Initialized secret vault [$($script:Config.VaultName)] of type [$($script:Config.VaultType)]"
104104
### This is the context config for this module
105105
$contextParams = @{
106106
Name = $script:Config.Name

src/functions/private/Get-ContextVault.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ function Get-ContextVault {
1919
[CmdletBinding()]
2020
param()
2121

22-
if (-not $script:Config.Context.VaultName) {
22+
if (-not $script:Config.VaultName) {
2323
throw 'Context vault name not set'
2424
}
2525

26-
Write-Verbose "Connecting to context vault [$($script:Config.Context.VaultName)]"
27-
$secretVault = Get-SecretVault | Where-Object { $_.Name -eq $script:Config.Context.VaultName }
26+
Write-Verbose "Connecting to context vault [$($script:Config.VaultName)]"
27+
$secretVault = Get-SecretVault | Where-Object { $_.Name -eq $script:Config.VaultName }
2828
if (-not $secretVault) {
2929
Write-Error $_
30-
throw "Context vault [$($script:Config.Context.VaultName)] not found"
30+
throw "Context vault [$($script:Config.VaultName)] not found"
3131
}
3232

3333
return $secretVault

src/functions/private/Initialize-ContextVault.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ function Initialize-ContextVault {
2525
param (
2626
# The name of the secret vault.
2727
[Parameter()]
28-
[string] $Name = $script:Config.Context.VaultName,
28+
[string] $Name = $script:Config.VaultName,
2929

3030
# The type of the secret vault.
3131
[Parameter()]
32-
[string] $Type = $script:Config.Context.VaultType
32+
[string] $Type = $script:Config.VaultType
3333
)
3434
$vault = Get-SecretVault | Where-Object { $_.ModuleName -eq $Type }
3535
if (-not $vault) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
function Convert-ContextHashtableToObjectRecursive {
2+
<#
3+
.SYNOPSIS
4+
Converts a hashtable to a context object.
5+
6+
.DESCRIPTION
7+
This function is used to convert a hashtable to a context object.
8+
String values that are prefixed with '[SECURESTRING]', are converted back to SecureString objects.
9+
Other values are converted to their original types, like ints, booleans, string, arrays, and nested objects.
10+
11+
.EXAMPLE
12+
Convert-ContextHashtableToObjectRecursive -Hashtable @{
13+
Name = 'Test'
14+
Token = '[SECURESTRING]TestToken'
15+
Nested = @{
16+
Name = 'Nested'
17+
Token = '[SECURESTRING]NestedToken'
18+
}
19+
}
20+
21+
This example converts a hashtable to a context object, where the 'Token' and 'Nested.Token' values are SecureString objects.
22+
#>
23+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
24+
'PSAvoidUsingConvertToSecureStringWithPlainText', '',
25+
Justification = 'The securestring is read from the object this function reads.'
26+
)]
27+
[OutputType([pscustomobject])]
28+
[CmdletBinding()]
29+
param (
30+
# Hashtable to convert to context object
31+
[object] $Hashtable
32+
)
33+
$result = [pscustomobject]@{}
34+
35+
foreach ($key in $Hashtable.Keys) {
36+
$value = $Hashtable[$key]
37+
Write-Debug "Processing [$key]"
38+
Write-Debug "Value type: $($value.GetType().FullName)"
39+
Write-Debug "Value: $value"
40+
if ($value -is [string] -and $value -like '`[SECURESTRING`]*') {
41+
Write-Debug "Converting [$key] as [SecureString]"
42+
$secureValue = $value -replace '^\[SECURESTRING\]', ''
43+
$result | Add-Member -NotePropertyName $key -NotePropertyValue ($secureValue | ConvertTo-SecureString -AsPlainText -Force)
44+
} elseif ($value -is [System.Collections.IEnumerable] -and ($value -isnot [string])) {
45+
Write-Debug "Converting [$key] as [IEnumerable], including arrays and hashtables"
46+
$result | Add-Member -NotePropertyName $key -NotePropertyValue @(
47+
$value | ForEach-Object {
48+
if ($_ -is [hashtable]) {
49+
Convert-ContextHashtableToObjectRecursive $_
50+
} else {
51+
$_
52+
}
53+
}
54+
)
55+
} elseif ($value -is [hashtable]) {
56+
Write-Debug "Converting [$key] as [hashtable]"
57+
$result | Add-Member -NotePropertyName $key -NotePropertyValue (Convert-ContextHashtableToObjectRecursive $value)
58+
} else {
59+
Write-Debug "Converting [$key] as regular value"
60+
$result | Add-Member -NotePropertyName $key -NotePropertyValue $value
61+
}
62+
}
63+
return $result
64+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function ConvertFrom-ContextJson {
2+
<#
3+
.SYNOPSIS
4+
Converts a JSON string to a context object.
5+
6+
.DESCRIPTION
7+
Converts a JSON string to a context object.
8+
[SECURESTRING] prefixed text is converted to SecureString objects.
9+
Other values are converted to their original types, like ints, booleans, string, arrays, and nested objects.
10+
11+
.EXAMPLE
12+
ConvertFrom-ContextJson -JsonString '{
13+
"Name": "Test",
14+
"Token": "[SECURESTRING]TestToken",
15+
"Nested": {
16+
"Name": "Nested",
17+
"Token": "[SECURESTRING]NestedToken"
18+
}
19+
}'
20+
21+
This example converts a JSON string to a context object, where the 'Token' and 'Nested.Token' values are SecureString objects.
22+
#>
23+
[OutputType([pscustomobject])]
24+
[CmdletBinding()]
25+
param (
26+
# JSON string to convert to context object
27+
[Parameter(Mandatory)]
28+
[string] $JsonString
29+
)
30+
31+
$hashtableObject = $JsonString | ConvertFrom-Json -Depth 100 -AsHashtable
32+
return Convert-ContextHashtableToObjectRecursive $hashtableObject
33+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
function Convert-ContextObjectToHashtableRecursive {
2+
<#
3+
.SYNOPSIS
4+
Converts a context object to a hashtable.
5+
6+
.DESCRIPTION
7+
This function converts a context object to a hashtable.
8+
Secure strings are converted to a string representation, prefixed with '[SECURESTRING]'.
9+
Datetime objects are converted to a string representation using the 'o' format specifier.
10+
Nested context objects are recursively converted to hashtables.
11+
12+
.EXAMPLE
13+
Convert-ContextObjectToHashtableRecursive -Object ([PSCustomObject]@{
14+
Name = 'MySecret'
15+
AccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force
16+
Nested = @{
17+
Name = 'MyNestedSecret'
18+
NestedAccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force
19+
}
20+
})
21+
22+
Converts the context object to a hashtable. Converts the AccessToken and NestedAccessToken secure strings to a string representation.
23+
#>
24+
[OutputType([hashtable])]
25+
[CmdletBinding()]
26+
param (
27+
# The object to convert.
28+
[object] $Object
29+
)
30+
$result = @{}
31+
32+
if ($Object -is [hashtable]) {
33+
Write-Debug 'Converting [hashtable] to [PSCustomObject]'
34+
$Object = [PSCustomObject]$Object
35+
} elseif ($Object -is [string] -or $Object -is [int] -or $Object -is [bool]) {
36+
Write-Debug 'returning as string'
37+
return $Object
38+
}
39+
40+
foreach ($property in $Object.PSObject.Properties) {
41+
$value = $property.Value
42+
Write-Debug "Processing [$($property.Name)]"
43+
Write-Debug "Value type: $($value.GetType().FullName)"
44+
if ($value -is [datetime]) {
45+
Write-Debug '- as DateTime'
46+
$result[$property.Name] = $value.ToString('o')
47+
} elseif ($value -is [string] -or $Object -is [int] -or $Object -is [bool]) {
48+
Write-Debug '- as string, int, bool'
49+
$result[$property.Name] = $value
50+
} elseif ($value -is [System.Security.SecureString]) {
51+
Write-Debug '- as SecureString'
52+
$value = $value | ConvertFrom-SecureString -AsPlainText
53+
$result[$property.Name] = "[SECURESTRING]$value"
54+
} elseif ($value -is [System.Collections.IEnumerable]) {
55+
Write-Debug '- as IEnumerable, including arrays and hashtables'
56+
$result[$property.Name] = @(
57+
$value | ForEach-Object {
58+
Convert-ContextObjectToHashtableRecursive $_
59+
}
60+
)
61+
} elseif ($value -is [psobject] -or $value -is [PSCustomObject]) {
62+
Write-Debug '- as PSObject, PSCustomObject'
63+
$result[$property.Name] = Convert-ContextObjectToHashtableRecursive $value
64+
} else {
65+
Write-Debug '- as regular value'
66+
$result[$property.Name] = $value
67+
}
68+
}
69+
return $result
70+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function ConvertTo-ContextJson {
2+
<#
3+
.SYNOPSIS
4+
Takes an object and converts it to a JSON string.
5+
6+
.DESCRIPTION
7+
Takes objects or hashtables and converts them to a JSON string.
8+
SecureStrings are converted to plain text strings and prefixed with [SECURESTRING]. The conversion is recursive for any nested objects.
9+
Use ConvertFrom-ContextJson to convert back to an object.
10+
11+
.EXAMPLE
12+
ConvertTo-ContextJson -Context ([pscustomobject]@{
13+
Name = 'MySecret'
14+
AccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force
15+
})
16+
17+
Returns a JSON string representation of the object.
18+
19+
```json
20+
{
21+
"Name": "MySecret",
22+
"AccessToken ": "[SECURESTRING]123123123"
23+
}
24+
```
25+
#>
26+
[OutputType([string])]
27+
[CmdletBinding()]
28+
param (
29+
# The object to convert to a Context JSON string.
30+
[Parameter(Mandatory)]
31+
[object] $Context
32+
)
33+
34+
$processedObject = Convert-ContextObjectToHashtableRecursive $Context
35+
return ($processedObject | ConvertTo-Json -Depth 100 -Compress)
36+
}

src/functions/public/Context/Get-Context.ps1

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,47 @@ filter Get-Context {
66
Retrieves a context from the context vault.
77
88
.DESCRIPTION
9-
Retrieves contexts from a specified context vault. You can specify the name of the context to retrieve or use a wildcard pattern to retrieve
10-
multiple contexts. If no name is specified, all contexts from the context vault will be retrieved.
11-
Optionally, you can choose to retrieve the contexts as plain text by providing the -AsPlainText switch.
9+
Retrieves a context from the context vault.
10+
If no name is specified, all contexts from the context vault will be retrieved.
1211
1312
.EXAMPLE
1413
Get-Context
1514
1615
Get all contexts from the context vault.
1716
1817
.EXAMPLE
19-
Get-Context -Name 'MySecret'
18+
Get-Context -ID 'MySecret'
2019
2120
Get the context called 'MySecret' from the vault.
22-
23-
.EXAMPLE
24-
Get-Context -Name 'My*'
25-
26-
Get all contexts that match the pattern 'My*' from the vault.
27-
28-
.EXAMPLE
29-
'My*' | Get-Context
30-
31-
Get all contexts that match the pattern 'My*' from the vault.
3221
#>
33-
[OutputType([hashtable])]
22+
[OutputType([pscustomobject])]
3423
[CmdletBinding()]
3524
param(
36-
# The name of the context to retrieve from the vault. Supports wildcard patterns.
37-
[Parameter(
38-
ValueFromPipeline,
39-
ValueFromPipelineByPropertyName
40-
)]
41-
[SupportsWildcards()]
42-
[Alias('Context', 'ContextName')]
43-
[string] $Name = '*',
44-
45-
# Switch to retrieve all the contexts secrets as plain text.
25+
# The name of the context to retrieve from the vault.
4626
[Parameter()]
47-
[switch] $AsPlainText
27+
[SupportsWildcards()]
28+
[Alias('ContextID')]
29+
[string] $ID
4830
)
4931

5032
$contextVault = Get-ContextVault
5133

52-
Write-Verbose "Retrieving contexts from vault [$($contextVault.Name)] using pattern [$Name]"
53-
$contexts = Get-SecretInfo -Vault $contextVault.Name | Where-Object { $_.Name -like "$($script:Config.Name)$Name" }
34+
if (-not $PSBoundParameters.ContainsKey('ID')) {
35+
Write-Verbose "Retrieving all contexts from [$($contextVault.Name)]"
36+
$contexts = Get-SecretInfo -Vault $contextVault.Name | Select-Object -ExpandProperty Name
37+
} elseif ([string]::IsNullOrEmpty($ID)) {
38+
Write-Verbose "Return 0 contexts from [$($contextVault.Name)]"
39+
return
40+
} else {
41+
$ID = "$($script:Config.SecretPrefix)$ID"
42+
Write-Verbose "Retrieving context [$ID] from [$($contextVault.Name)]"
43+
$contexts = Get-SecretInfo -Vault $contextVault.Name -Name $ID | Select-Object -ExpandProperty Name
44+
}
5445

55-
Write-Verbose "Found [$($contexts.Count)] contexts in context vault [$($contextVault.Name)]"
46+
Write-Verbose "Found [$($contexts.Count)] contexts in [$($contextVault.Name)]"
5647
$contexts | ForEach-Object {
57-
Write-Verbose " - $($_.Name)"
58-
Get-Secret -Name $_.Name -Vault $contextVault.Name -AsPlainText:$AsPlainText
48+
Write-Verbose " - $_"
49+
$contextJson = Get-Secret -Name $_ -Vault $contextVault.Name -AsPlainText
50+
ConvertFrom-ContextJson -JsonString $contextJson
5951
}
6052
}
61-
62-
Register-ArgumentCompleter -CommandName Get-Context -ParameterName Name -ScriptBlock {
63-
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
64-
$null = $commandName, $parameterName, $commandAst, $fakeBoundParameter
65-
66-
Get-SecretInfo -Vault $script:Config.Context.VaultName -Name "$($script:Config.Name)$wordToComplete*" -Verbose:$false |
67-
ForEach-Object {
68-
$contextName = $_.Name.Replace($script:Config.Name, '')
69-
[System.Management.Automation.CompletionResult]::new($contextName, $contextName, 'ParameterValue', $contextName)
70-
}
71-
}

0 commit comments

Comments
 (0)