modules/Devolutions.CIEM.Checks/Private/GetCIEMIAMNeeds.ps1

function GetCIEMIAMNeeds {
    <#
    .SYNOPSIS
        Loads IAM (Azure RBAC) discovery data into the scan service-data hashtable
        for each requested 'iam:*' need key.

    .DESCRIPTION
        Called by GetCIEMAzureScanServiceCache when at least one selected check
        declares an iam:* data need. Populates $ServiceData['IAM'] as a
        per-subscription hashtable containing RoleAssignments, RoleDefinitions,
        and CustomRoles arrays.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [string[]]$NeedKeys,

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [string[]]$SubscriptionIds,

        [Parameter(Mandatory)]
        [hashtable]$ServiceData,

        [Parameter(Mandatory)]
        [hashtable]$ServiceErrors,

        [Parameter(Mandatory)]
        [hashtable]$ServiceStarted
    )

    $ErrorActionPreference = 'Stop'

    $iamNeeds = @($NeedKeys | Where-Object { $_ -like 'iam:*' })
    if ($iamNeeds.Count -eq 0) {
        return
    }

    if (-not $ServiceData.ContainsKey('IAM')) {
        $ServiceData['IAM'] = @{}
        $ServiceErrors['IAM'] = @()
        $ServiceStarted['IAM'] = [Diagnostics.Stopwatch]::StartNew()
    }

    $ensureBucket = {
        param([string]$SubId)
        if (-not $ServiceData['IAM'].ContainsKey($SubId)) {
            $ServiceData['IAM'][$SubId] = @{
                RoleAssignments = @()
                RoleDefinitions = @()
                CustomRoles = @()
            }
        }
    }

    # Always pre-create the known subscription buckets so downstream checks don't
    # have to handle the missing-key case.
    foreach ($subscriptionId in $SubscriptionIds) {
        & $ensureBucket $subscriptionId
    }

    foreach ($needKey in ($iamNeeds | Select-Object -Unique)) {
        if ($needKey -cne $needKey.ToLowerInvariant()) {
            throw "Data need '$needKey' must use lowercase canonical form."
        }

        switch ($needKey) {
            'iam:roleassignments' {
                foreach ($resource in @(Get-CIEMAzureArmResource -Type 'microsoft.authorization/roleassignments')) {
                    $subscriptionId = $resource.SubscriptionId
                    if (-not $subscriptionId -and $resource.Id -match '^/subscriptions/([^/]+)') {
                        $subscriptionId = $Matches[1]
                    }
                    if (-not $subscriptionId) {
                        continue
                    }
                    & $ensureBucket $subscriptionId
                    $ServiceData['IAM'][$subscriptionId].RoleAssignments += ConvertFromCIEMStoredResource -Resource $resource
                }
            }
            'iam:roledefinitions' {
                foreach ($resource in @(Get-CIEMAzureArmResource -Type 'microsoft.authorization/roledefinitions')) {
                    $definition = ConvertFromCIEMStoredResource -Resource $resource
                    $targetSubscriptions = @()
                    foreach ($scope in @($definition.assignableScopes)) {
                        if ($scope -match '^/subscriptions/([^/]+)') {
                            $targetSubscriptions += $Matches[1]
                        }
                    }
                    if ($targetSubscriptions.Count -eq 0) {
                        $targetSubscriptions = @($SubscriptionIds)
                    }
                    foreach ($subscriptionId in ($targetSubscriptions | Select-Object -Unique)) {
                        if (-not $subscriptionId) {
                            continue
                        }
                        & $ensureBucket $subscriptionId
                        $ServiceData['IAM'][$subscriptionId].RoleDefinitions += $definition
                        if ($definition.PSObject.Properties.Name -contains 'type' -and $definition.type -eq 'CustomRole') {
                            $ServiceData['IAM'][$subscriptionId].CustomRoles += $definition
                        }
                    }
                }
            }
            default {
                throw "Unknown data need '$needKey'."
            }
        }
    }
}