Private/Test-EGILeafExpression.ps1
|
function Test-EGILeafExpression { <# .SYNOPSIS Evaluates one leaf expression (e.g. 'user.department -eq "Sales"') against a user object. .DESCRIPTION Supports the common single-property comparison operators (-eq, -ne, -startsWith, -notStartsWith, -endsWith, -notEndsWith, -contains, -notContains, -match, -notMatch, -in, -notIn) plus a best-effort handling of '-any (_ -op value)' over collection properties such as otherMails / proxyAddresses. NOT SUPPORTED in this v0.1 prototype (throws a descriptive error so callers can skip/flag the rule instead of silently returning a wrong result): - 'Direct Reports for "<objectId>"' rules - the memberOf (preview) operator - employeeHireDate date-math expressions (system.now -plus/-minus) .PARAMETER Expression The raw leaf text, e.g. 'user.department -eq "Sales"'. .PARAMETER User A [pscustomobject]/hashtable representing the user, with properties named after the Graph user attributes (department, jobTitle, otherMails, ...). #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory)] [string] $Expression, [Parameter(Mandatory)] $User ) if ($Expression -match '(?i)^Direct Reports for') { throw "Leaf '$Expression' uses the direct-reports rule, which this prototype does not simulate." } if ($Expression -match '(?i)\bmemberOf\b') { throw "Leaf '$Expression' uses the (preview) memberOf operator, which this prototype does not simulate." } if ($Expression -match '(?i)system\.now') { throw "Leaf '$Expression' uses date-math against system.now, which this prototype does not simulate." } function Get-Prop { param($Obj, [string]$Name) if ($Obj -is [System.Collections.IDictionary]) { foreach ($k in $Obj.Keys) { if ($k -ieq $Name) { return $Obj[$k] } } return $null } $p = $Obj.PSObject.Properties | Where-Object { $_.Name -ieq $Name } | Select-Object -First 1 if ($p) { return $p.Value } else { return $null } } function ConvertFrom-EGILiteral { param([string] $Text) $t = $Text.Trim() if ($t -ieq 'null') { return $null } if ($t -ieq 'true') { return $true } if ($t -ieq 'false') { return $false } if ($t -match '^\[(.*)\]$') { $inner = $Matches[1] return ($inner -split ',') | ForEach-Object { $v = $_.Trim().Trim('"', "'") $v -replace '``"', '"' } } if ($t -match '^"(.*)"$' -or $t -match "^'(.*)'$") { return ($Matches[1] -replace '``"', '"') } if ($t -match '^-?\d+(\.\d+)?$') { return [double]$t } return $t } function Invoke-EGIOperator { param($Left, [string] $Op, $Right) $op = $Op.TrimStart('-').ToLowerInvariant() switch ($op) { 'eq' { return $Left -eq $Right } 'ne' { return $Left -ne $Right } 'startswith' { return ($null -ne $Left) -and ("$Left" -like "$Right*") } 'notstartswith' { return -not (($null -ne $Left) -and ("$Left" -like "$Right*")) } 'endswith' { return ($null -ne $Left) -and ("$Left" -like "*$Right") } 'notendswith' { return -not (($null -ne $Left) -and ("$Left" -like "*$Right")) } 'contains' { return ($null -ne $Left) -and ("$Left" -like "*$Right*") } 'notcontains' { return -not (($null -ne $Left) -and ("$Left" -like "*$Right*")) } 'match' { return ($null -ne $Left) -and ("$Left" -match "$Right") } 'notmatch' { return -not (($null -ne $Left) -and ("$Left" -match "$Right")) } 'in' { return $Right -contains $Left } 'notin' { return -not ($Right -contains $Left) } default { throw "Unsupported operator '$Op' in leaf '$Expression'." } } } # '-any (_ -op value)' over a collection property. if ($Expression -match '(?i)^user\.([A-Za-z0-9]+)\s+-any\s*\(\s*_\s+(-\S+)\s+(.+)\)\s*$') { $propName = $Matches[1] $op = $Matches[2] $value = ConvertFrom-EGILiteral -Text $Matches[3] $collection = @(Get-Prop -Obj $User -Name $propName) foreach ($item in $collection) { if (Invoke-EGIOperator -Left $item -Op $op -Right $value) { return $true } } return $false } # Simple single-property comparison: user.<prop> <op> <value> if ($Expression -match '(?i)^user\.([A-Za-z0-9]+)\s+(-\S+)\s+(.+)$') { $propName = $Matches[1] $op = $Matches[2] $value = ConvertFrom-EGILiteral -Text $Matches[3] $left = Get-Prop -Obj $User -Name $propName return (Invoke-EGIOperator -Left $left -Op $op -Right $value) } throw "Could not parse leaf expression: '$Expression'." } |