Implement a function to "find" a CredentialStoreItem in CredentialStores

This commit is contained in:
pinguinfuss 2023-05-22 21:44:13 +02:00
parent ddb85d907f
commit db0fa1cf4a
2 changed files with 302 additions and 0 deletions

View File

@ -0,0 +1,150 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSAvoidUsingConvertToSecureStringWithPlainText',
'',
Justification = 'just used in pester tests.'
)]
param ()
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
# Backup existing CredentialStores
$Paths = @(('{0}\AppData\Roaming' -f $env:USERPROFILE), ('{0}\ProgramData\PSCredentialStore\' -f $env:SystemDrive))
$Files = @('CredentialStore.json', 'PSCredentialStore.pfx')
foreach ($Filepath in $Paths) {
foreach ($File in $Files) {
$OrgPath = Join-Path -Path $FilePath -ChildPath $File
$NewPath = $OrgPath + '.orig'
if (Test-Path $OrgPath) {
try {
$null = Remove-Item -Path $NewPath -Force -Confirm:$false
$null = Rename-Item -Path $OrgPath -NewName $NewPath -Confirm:$false
}
catch {
$_.Exception.Message | Write-Warning
Write-Error -Message ('Unable to revert {0} to {1}' -f $OrgPath, $NewPath)
}
}
}
}
# Construct the necessary CredentialStores for the Unit tests.
New-CredentialStore -Force
New-CredentialStore -Shared -Force
# Construct the necessary CredentialStoreItems for the Unit tests.
$CredentialUserName = 'MyUser'
$CredentialPassword = 'FooBar' | ConvertTo-SecureString -AsPlainText -Force
$Credential = [PSCredential]::new($CredentialUserName, $CredentialPassword)
# Create the CredentialStoreItems
New-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' -Credential $Credential
New-CredentialStoreItem -Shared -RemoteHost 'test-case-a.domain.my' -Credential $Credential
New-CredentialStoreItem -Shared -RemoteHost 'test-case-b.domain.my' -Credential $Credential
New-CredentialStoreItem -RemoteHost 'test-case-c.domain.my' -Credential $Credential
New-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' -Credential $Credential -Identifier 'Foo'
New-CredentialStoreItem -Shared -RemoteHost 'test-case-a.domain.my' -Credential $Credential -Identifier 'Foo'
New-CredentialStoreItem -Shared -RemoteHost 'test-case-b.domain.my' -Credential $Credential -Identifier 'Foo'
New-CredentialStoreItem -RemoteHost 'test-case-c.domain.my' -Credential $Credential -Identifier 'Foo'
}
AfterAll {
# Check if the private CredentialStore exists
$Paths = @(('{0}\AppData\Roaming' -f $env:USERPROFILE), ('{0}\ProgramData\PSCredentialStore\' -f $env:SystemDrive))
$Files = @('CredentialStore.json.orig', 'PSCredentialStore.pfx.orig')
foreach ($Filepath in $Paths) {
foreach ($File in $Files) {
$OrgPath = Join-Path -Path $FilePath -ChildPath $File
$NewPath = $OrgPath.Replace('.orig', '')
if (Test-Path $OrgPath) {
try {
$null = Remove-Item -Path $NewPath -Force -Confirm:$false -ErrorAction SilentlyContinue
$null = Rename-Item -Path $OrgPath -NewName $NewPath -Confirm:$false
}
catch {
$_.Exception.Message | Write-Warning
Write-Error -Message ('Unable to revert {0} to {1}' -f $OrgPath, $NewPath)
}
}
}
}
}
Describe 'Find-CredentialStoreItem' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Find-CredentialStoreItem' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Find-CredentialStoreItem' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Find-CredentialStoreItem'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Coding' {
It 'Calling Find-CredentialStoreItem with wrong Type' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' -Type 'Foo' } | Should -Throw
}
It 'Calling Find-CredentialStoreItem present in both CredentialStores w/o Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-a.domain.my'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
It 'Calling Find-CredentialStoreItem present only in shared CredentialStore w/o Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-b.domain.my' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-b.domain.my'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
It 'Calling Find-CredentialStoreItem present only in private CredentialStore w/o Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-c.domain.my' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-c.domain.my'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
It 'Calling Find-CredentialStoreItem present in both CredentialStores w Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' -Identifier 'Foo' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-a.domain.my' -Identifier 'Foo'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
It 'Calling Find-CredentialStoreItem present only in shared CredentialStore w/o Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-b.domain.my' -Identifier 'Foo' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-b.domain.my' -Identifier 'Foo'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
It 'Calling Find-CredentialStoreItem present only in private CredentialStore w/o Identifier' {
{ Find-CredentialStoreItem -RemoteHost 'test-case-c.domain.my' -Identifier 'Foo' } | Should -Not -Throw
$foo = Find-CredentialStoreItem -RemoteHost 'test-case-c.domain.my' -Identifier 'Foo'
$foo.UserName | Should -Be 'MyUser'
$foo.GetNetworkCredential().Password | Should -Be 'FooBar'
}
}
}

View File

@ -0,0 +1,152 @@
function Find-CredentialStoreItem {
<#
.SYNOPSIS
Locates a CredentialStoreItem in any CredentialStore from a given remote host item.
.DESCRIPTION
Find the credential object and return it as PSCredential object.
.PARAMETER RemoteHost
Specify the host, for which you would like to find the credentials.
.PARAMETER Identifier
Provide a custom identifier to the given remote host key. This enables you to store multiple credentials
for a single remote host entry. For example ad/sys1, ftp/sys1, sql/sys1
.PARAMETER Type
Influence in which types of CredentialStore this function will look for a object. List of possible types:
- All (include private and shared CredentialStore) - this is also the default.
- Private (only look in a private CredentialStore)
- Shared (only look in the shared CredentialStore)
.INPUTS
[None]
.OUTPUTS
[System.Management.Automation.PSCredential]
.EXAMPLE
$Credential = Find-CredentialStoreItem -RemoteHost 'support.komm-one.net' -Type 'All'
.EXAMPLE
$params = @{
RemoteHost = 'support.komm-one.net'
Type = 'Private'
Identifier = 'PersonId'
}
$Credential = Find-CredentialStoreItem @params
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $RemoteHost,
[Parameter(Mandatory = $false)]
[string] $Identifier,
[Parameter(Mandatory = $false)]
[ValidateSet('All', 'Private', 'Shared')]
[string] $Type = 'All'
)
begin {
# Define some defaults for the PreferenceVariables.
$ErrorActionPreference = 'Stop'
$InformationPreference = 'Continue'
$WarningPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue'
# Construct the CredentialStore list, based on what $Type says.
switch ($Type) {
'All' {
$CredentialStoreList = @('Private', 'Shared')
break
}
'Private' {
$CredentialStoreList = @('Private')
}
'Shared' {
$CredentialStoreList = @('Shared')
}
}
}
process {
# Now go and look for the CredentialStoreItem.
foreach ($Store in $CredentialStoreList) {
# First make sure, that the CredentialStore exists. Sadly I don't have a way to solve this any better
# programmatically, as PowerShell behaves oddly, if you try and pass an empty splatting to a function.
Write-Verbose -Message ('Checking if CredentialStore of type {0} exists' -f $Store)
if ($Store -eq 'Private') {
if (-not (Test-CredentialStore)) {
Write-Warning -Message ('CredentialStore of type {0} not found, skipping ahead' -f $Store)
continue
}
}
elseif ($Store -eq 'Shared') {
if (-not (Test-CredentialStore -Shared)) {
Write-Warning -Message ('CredentialStore of type {0} not found, skipping ahead' -f $Store)
continue
}
}
else {
Write-Error -Message ('Invalid CredentialStore type {0} supplied' -f $Store)
continue
}
# Now that we're here, means we have tested the CredentialStore for existence. We can check, if it
# contains a CredentialStoreItem that we are looking for.
$params = @{
RemoteHost = $RemoteHost
}
# Check if the user passed -Identifier, then we add it to the splatting.
if (-not [string]::IsNullOrWhiteSpace($Identifier)) {
$params.Add('Identifier', $Identifier)
}
# Check the CredentialStore type we're currently looking at.
if ($Store -eq 'Shared') {
$params.Add('Shared', $true)
}
# Now check if the CredentialStoreItem exists
$message = 'Checking if CredentialStoreItem {0}/{1} exists in CredentialStore {2}'
$argumentlist = @($RemoteHost, $Identifier, $Store)
Write-Verbose -Message ($message -f $argumentlist)
if (Test-CredentialStoreItem @params) {
$message = 'Looking up CredentialStoreItem {0}/{1} from CredentialStore {2}'
$argumentlist = @($RemoteHost, $Identifier, $Store)
Write-Verbose -Message ($message -f $argumentlist)
try {
Write-Information -MessageData ($message -f $argumentlist)
# Read the CredentialStoreItem from the CredentialStore and store it in $CredentialObject
$CredentialObject = Get-CredentialStoreItem @params
# Now finish the loop, as we've found what we're looking for.
break
}
catch {
$_.Exception.Message | Write-Warning
$message = 'Unable to read CredentialStoreItem {0}/{1} from CredentialStore {2}'
$argumentlist = @($RemoteHost, $Identifier, $Store)
Write-Warning -Message ($message -f $argumentlist)
}
}
}
}
end {
# Only if we've found a CredentialStoreItem above, return it back to the caller.
if ($null -ne $CredentialObject) {
$CredentialObject
}
}
}