Providers/PingOne/Public/Test-PingOneEnvironment.ps1
|
function Test-PingOneEnvironment { <# .SYNOPSIS Verifies that the seeded PingOne environment matches the seed data .DESCRIPTION Reads the populations, users, groups, applications, resources and attributes the module owns in the connected environment, the same way teardown finds them, and compares them with the seed files: every seeded name should be present, nothing the module owns should be there that the data does not describe, every user's given and family name should match the data by codepoint, and every group a user row lists should hold that user. Names are compared ordinally, not with -eq, because a decomposed and a precomposed name are equal to -eq and different on the wire; a name that came back mangled is the fault this exists to catch, and it is the fault the PingOne provider once shipped. Usernames are compared case-insensitively because PingOne folds them. Memberships are read one user at a time, for the users the data puts in a group, and are judged on what is missing only: a dynamic group's filter and a nested group add members the data never lists, and that is not a fault. .PARAMETER SkipMembership Do not read group memberships, which costs one call per seeded user that has any .PARAMETER Quiet Return the result without writing to the console .OUTPUTS PSCustomObject typed TestEnvironmentVerification. Passed is $true when every check passed. .EXAMPLE PS> Test-PingOneEnvironment Prints one line per check and returns the result. .EXAMPLE PS> Test-PingOneEnvironment -SkipMembership -Quiet | Select-Object -ExpandProperty Checks The object checks alone, as objects. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [switch]$SkipMembership, [Parameter()] [switch]$Quiet ) $connection = Get-PingOneConnection $dataPath = Get-PingOneDataPath $checks = New-Object System.Collections.Generic.List[object] $read = { param($file) @(Import-Csv -LiteralPath (Join-Path -Path $dataPath -ChildPath $file) -Encoding UTF8) } $displayOf = { param($key) Resolve-PingOneSeedName -Key $key -Kind DisplayName -Connection $connection } $usernameOf = { param($key) Resolve-PingOneSeedName -Key $key -Kind Username -Connection $connection } # --- Populations ----------------------------------------------------------------------- $populationRows = & $read 'PingOnePopulations.csv' $populations = @(Get-PingOneSeededObject -Type Populations -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Populations' ` -Expected @($populationRows | ForEach-Object { & $displayOf $_.Name }) ` -Found @($populations | ForEach-Object { [string]$_.name }))) # --- Users ----------------------------------------------------------------------------- $userRows = & $read 'PingOneUsers.csv' $users = @(Get-PingOneSeededObject -Type Users -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Users' ` -Expected @($userRows | ForEach-Object { & $usernameOf $_.Key }) ` -Found @($users | ForEach-Object { [string]$_.username }) -IgnoreCase)) $userByName = @{} foreach ($user in $users) { if ($user.username) { $userByName[([string]$user.username).ToLowerInvariant()] = $user } } $compared = 0 $mismatch = foreach ($row in $userRows) { $user = $userByName[(& $usernameOf $row.Key).ToLowerInvariant()] if (-not $user) { continue } $compared++ $given = [string]$user.name.given $family = [string]$user.name.family if ($row.GivenName -and -not [string]::Equals($given, [string]$row.GivenName, [StringComparison]::Ordinal)) { "{0}: given name '{1}' should be '{2}'" -f $row.Key, $given, $row.GivenName } if ($row.FamilyName -and -not [string]::Equals($family, [string]$row.FamilyName, [StringComparison]::Ordinal)) { "{0}: family name '{1}' should be '{2}'" -f $row.Key, $family, $row.FamilyName } } $checks.Add((New-TestEnvironmentCheck -Name 'User names' -Compared $compared -Mismatch @($mismatch))) # --- Groups, applications, resources, attributes --------------------------------------- $groupRows = & $read 'PingOneGroups.csv' $groups = @(Get-PingOneSeededObject -Type Groups -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Groups' ` -Expected @($groupRows | ForEach-Object { & $displayOf $_.Name }) ` -Found @($groups | ForEach-Object { [string]$_.name }))) $applicationRows = & $read 'PingOneApplications.csv' $applications = @(Get-PingOneSeededObject -Type Applications -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Applications' ` -Expected @($applicationRows | ForEach-Object { & $displayOf $_.Name }) ` -Found @($applications | ForEach-Object { [string]$_.name }))) $resourceRows = & $read 'PingOneResources.csv' $resources = @(Get-PingOneSeededObject -Type Resources -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Resources' ` -Expected @($resourceRows | ForEach-Object { & $displayOf $_.Name }) ` -Found @($resources | ForEach-Object { [string]$_.name }))) $attributeRows = & $read 'PingOneProfileAttributes.csv' $attributes = @(Get-PingOneSeededObject -Type Attributes -Connection $connection) $checks.Add((New-TestEnvironmentCheck -Name 'Attributes' -FoundCount $attributes.Count -ExpectedCount $attributeRows.Count)) # --- Memberships ----------------------------------------------------------------------- if (-not $SkipMembership) { # A user row names its groups by their row key; the environment names them by display name. $groupNameByKey = @{} foreach ($row in $groupRows) { $groupNameByKey[$row.Key] = & $displayOf $row.Name } $groupNameById = @{} foreach ($group in $groups) { $groupNameById[[string]$group.id] = [string]$group.name } $expectedPairs = New-Object System.Collections.Generic.List[string] $foundPairs = New-Object System.Collections.Generic.List[string] foreach ($row in $userRows) { $keys = @($row.Groups -split ';' | Where-Object { $_ }) if ($keys.Count -eq 0) { continue } $username = & $usernameOf $row.Key foreach ($key in $keys) { $groupName = if ($groupNameByKey.ContainsKey($key)) { $groupNameByKey[$key] } else { & $displayOf $key } $expectedPairs.Add(('{0} <- {1}' -f $groupName, $username)) } $user = $userByName[$username.ToLowerInvariant()] if (-not $user) { continue } try { foreach ($membership in @(Invoke-PingOneRequest -Method GET -Path "users/$($user.id)/memberOfGroups" -Paginate -Connection $connection)) { if ($null -eq $membership) { continue } $groupName = if ($membership.name) { [string]$membership.name } else { $groupNameById[[string]$membership.id] } if ($groupName) { $foundPairs.Add(('{0} <- {1}' -f $groupName, [string]$user.username)) } } } catch { Write-Warning "Could not read the groups of '$username': $($_.Exception.Message). They are counted as missing." } } $checks.Add((New-TestEnvironmentCheck -Name 'Group memberships' -Expected $expectedPairs -Found $foundPairs -IgnoreCase -MissingOnly)) } return New-TestEnvironmentVerification -Provider 'PingOne' -Target $connection.EnvironmentId -Check $checks.ToArray() -Quiet:$Quiet } |