Private/29-GetAtlasEntitlementManagement.ps1

function Get-AtlasEntitlementManagement {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $TenantId,

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [AtlasNode[]] $KnownNode
    )

    $result = [AtlasCollectionResult]::new()
    $keyById = @{}
    $keyByAppId = @{}
    $addedNode = @{}
    foreach ($node in $KnownNode) {
        $keyById[$node.Id] = $node.Key
        if ($node.Properties.ContainsKey('appId') -and $node.Properties.appId) {
            $keyByAppId[$node.Properties.appId] = $node.Key
        }
    }

    $catalogsEndpoint = '/v1.0/identityGovernance/entitlementManagement/catalogs?$select=id,displayName,description,catalogType,state,isExternallyVisible,createdDateTime,modifiedDateTime'
    $packagesEndpoint = '/v1.0/identityGovernance/entitlementManagement/accessPackages?$expand=catalog'
    $policiesEndpoint = '/v1.0/identityGovernance/entitlementManagement/assignmentPolicies?$expand=accessPackage'
    $assignmentsEndpoint = '/v1.0/identityGovernance/entitlementManagement/assignments?$expand=target,accessPackage'

    $catalogResponse = Invoke-AtlasGraphRequest -Uri $catalogsEndpoint
    $packageResponse = Invoke-AtlasGraphRequest -Uri $packagesEndpoint
    $policyResponse = Invoke-AtlasGraphRequest -Uri $policiesEndpoint
    $assignmentResponse = Invoke-AtlasGraphRequest -Uri $assignmentsEndpoint
    $requestCount = $catalogResponse.Metrics.requestCount + $packageResponse.Metrics.requestCount + $policyResponse.Metrics.requestCount + $assignmentResponse.Metrics.requestCount
    $retryCount = $catalogResponse.Metrics.retryCount + $packageResponse.Metrics.retryCount + $policyResponse.Metrics.retryCount + $assignmentResponse.Metrics.retryCount

    $catalogKeyById = @{}
    foreach ($catalog in $catalogResponse.Items) {
        $catalogNode = New-AtlasNode -TenantId $TenantId -Id "accessPackageCatalog:$($catalog.id)" -Kind 'accessPackageCatalog' -DisplayName $catalog.displayName -Properties @{
            catalogId = $catalog.id
            description = Get-AtlasResponseProperty -InputObject $catalog -Name 'description'
            catalogType = Get-AtlasResponseProperty -InputObject $catalog -Name 'catalogType'
            state = Get-AtlasResponseProperty -InputObject $catalog -Name 'state'
            isExternallyVisible = Get-AtlasResponseProperty -InputObject $catalog -Name 'isExternallyVisible'
            createdDateTime = Get-AtlasResponseProperty -InputObject $catalog -Name 'createdDateTime'
            modifiedDateTime = Get-AtlasResponseProperty -InputObject $catalog -Name 'modifiedDateTime'
        } -Source @{
            provider = 'microsoftGraph'
            apiVersion = 'v1.0'
            odataType = '#microsoft.graph.accessPackageCatalog'
            resourcePath = "/identityGovernance/entitlementManagement/catalogs/$($catalog.id)"
            collector = 'entitlementManagement'
        } -Tags @('governance', 'entitlementManagement')
        $result.Nodes.Add($catalogNode)
        $catalogKeyById[$catalog.id] = $catalogNode.Key
    }

    $packageKeyById = @{}
    $resourceRoleScopeCount = 0
    foreach ($package in $packageResponse.Items) {
        $catalog = Get-AtlasResponseProperty -InputObject $package -Name 'catalog'
        $catalogId = Get-AtlasResponseProperty -InputObject $catalog -Name 'id'
        $packageNode = New-AtlasNode -TenantId $TenantId -Id "accessPackage:$($package.id)" -Kind 'accessPackage' -DisplayName $package.displayName -Properties @{
            accessPackageId = $package.id
            description = Get-AtlasResponseProperty -InputObject $package -Name 'description'
            catalogId = $catalogId
            createdDateTime = Get-AtlasResponseProperty -InputObject $package -Name 'createdDateTime'
            modifiedDateTime = Get-AtlasResponseProperty -InputObject $package -Name 'modifiedDateTime'
            isHidden = Get-AtlasResponseProperty -InputObject $package -Name 'isHidden'
        } -Source @{
            provider = 'microsoftGraph'
            apiVersion = 'v1.0'
            odataType = '#microsoft.graph.accessPackage'
            resourcePath = "/identityGovernance/entitlementManagement/accessPackages/$($package.id)"
            collector = 'entitlementManagement'
        } -Tags @('governance', 'entitlementManagement')
        $result.Nodes.Add($packageNode)
        $packageKeyById[$package.id] = $packageNode.Key

        if ($catalogId -and $catalogKeyById.ContainsKey($catalogId)) {
            $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'entitlementManagement' -Endpoint $packagesEndpoint -SourceObjectId "$catalogId|$($package.id)" -Fields @{
                catalogId = $catalogId
                accessPackageId = $package.id
            }
            $result.Evidence.Add($evidence)
            $result.Edges.Add(
                (New-AtlasEdge -TenantId $TenantId -From $catalogKeyById[$catalogId] -To $packageNode.Key -Relationship 'containsAccessPackage' -EvidenceIds @($evidence.Key) -Source @{ collector = 'entitlementManagement' })
            )
        }

        $resourceEndpoint = "/v1.0/identityGovernance/entitlementManagement/accessPackages/$($package.id)?`$expand=resourceRoleScopes(`$expand=role,scope)"
        try {
            $resourceResponse = Invoke-AtlasGraphRequest -Uri $resourceEndpoint
            $requestCount += $resourceResponse.Metrics.requestCount
            $retryCount += $resourceResponse.Metrics.retryCount
        }
        catch {
            $result.Status = 'partial'
            $result.Warnings.Add("Resource roles could not be collected for access package '$($package.displayName)': $(Get-AtlasSafeErrorDetail -ErrorRecord $_)")
            continue
        }

        $expandedPackage = @($resourceResponse.Items)[0]
        $resourceRoleScopes = @(Get-AtlasResponseProperty -InputObject $expandedPackage -Name 'resourceRoleScopes')
        foreach ($resourceRoleScope in $resourceRoleScopes) {
            $role = Get-AtlasResponseProperty -InputObject $resourceRoleScope -Name 'role'
            $scope = Get-AtlasResponseProperty -InputObject $resourceRoleScope -Name 'scope'
            $originId = Get-AtlasResponseProperty -InputObject $scope -Name 'originId'
            if (-not $originId) {
                $originId = Get-AtlasResponseProperty -InputObject $role -Name 'originId'
            }
            $originSystem = Get-AtlasResponseProperty -InputObject $scope -Name 'originSystem'
            if (-not $originSystem) {
                $originSystem = Get-AtlasResponseProperty -InputObject $role -Name 'originSystem'
            }
            if (-not $originId) { continue }

            if ($keyById.ContainsKey($originId)) {
                $resourceKey = $keyById[$originId]
            }
            elseif ($keyByAppId.ContainsKey($originId)) {
                $resourceKey = $keyByAppId[$originId]
            }
            else {
                $resourceId = "entitlementResource:$originSystem`:$originId"
                $resourceKey = "tenant:$TenantId`:graph:$resourceId"
                if (-not $addedNode.ContainsKey($resourceKey)) {
                    $resourceName = Get-AtlasResponseProperty -InputObject $scope -Name 'displayName'
                    if (-not $resourceName) { $resourceName = $originId }
                    $resourceNode = New-AtlasNode -TenantId $TenantId -Id $resourceId -Kind 'entitlementResource' -DisplayName $resourceName -Properties @{
                        originId = $originId
                        originSystem = $originSystem
                    } -Source @{
                        provider = 'microsoftGraph'
                        apiVersion = 'v1.0'
                        odataType = '#microsoft.graph.accessPackageResourceScope'
                        resourcePath = "/identityGovernance/entitlementManagement/accessPackages/$($package.id)"
                        collector = 'entitlementManagement'
                    } -Tags @('governance', 'entitlementResource')
                    $result.Nodes.Add($resourceNode)
                    $addedNode[$resourceKey] = $true
                }
            }

            $resourceRoleScopeId = Get-AtlasResponseProperty -InputObject $resourceRoleScope -Name 'id'
            if (-not $resourceRoleScopeId) {
                $resourceRoleScopeId = "$($package.id)|$originId|$(Get-AtlasResponseProperty -InputObject $role -Name 'originId')"
            }
            $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'entitlementManagement' -Endpoint $resourceEndpoint -SourceObjectId $resourceRoleScopeId -Fields @{
                accessPackageId = $package.id
                resourceOriginId = $originId
                resourceOriginSystem = $originSystem
                roleOriginId = Get-AtlasResponseProperty -InputObject $role -Name 'originId'
                roleDisplayName = Get-AtlasResponseProperty -InputObject $role -Name 'displayName'
                scopeDisplayName = Get-AtlasResponseProperty -InputObject $scope -Name 'displayName'
            }
            $result.Evidence.Add($evidence)
            $result.Edges.Add(
                (New-AtlasEdge -TenantId $TenantId -From $packageNode.Key -To $resourceKey -Relationship 'grantsEntitlementResourceRole' -State @{
                    roleDisplayName = Get-AtlasResponseProperty -InputObject $role -Name 'displayName'
                    roleOriginId = Get-AtlasResponseProperty -InputObject $role -Name 'originId'
                    originSystem = $originSystem
                } -EvidenceIds @($evidence.Key) -Source @{ collector = 'entitlementManagement' })
            )
            $resourceRoleScopeCount++
        }
    }

    $policyKeyById = @{}
    foreach ($policy in $policyResponse.Items) {
        $accessPackage = Get-AtlasResponseProperty -InputObject $policy -Name 'accessPackage'
        $accessPackageId = Get-AtlasResponseProperty -InputObject $accessPackage -Name 'id'
        if (-not $accessPackageId) {
            $accessPackageId = Get-AtlasResponseProperty -InputObject $policy -Name 'accessPackageId'
        }
        $policyNode = New-AtlasNode -TenantId $TenantId -Id "accessPackageAssignmentPolicy:$($policy.id)" -Kind 'accessPackageAssignmentPolicy' -DisplayName $policy.displayName -Properties @{
            assignmentPolicyId = $policy.id
            description = Get-AtlasResponseProperty -InputObject $policy -Name 'description'
            accessPackageId = $accessPackageId
            allowedTargetScope = Get-AtlasResponseProperty -InputObject $policy -Name 'allowedTargetScope'
            canExtend = Get-AtlasResponseProperty -InputObject $policy -Name 'canExtend'
            expiration = Get-AtlasResponseProperty -InputObject $policy -Name 'expiration'
            requestApprovalSettings = Get-AtlasResponseProperty -InputObject $policy -Name 'requestApprovalSettings'
            reviewSettings = Get-AtlasResponseProperty -InputObject $policy -Name 'reviewSettings'
            createdDateTime = Get-AtlasResponseProperty -InputObject $policy -Name 'createdDateTime'
            modifiedDateTime = Get-AtlasResponseProperty -InputObject $policy -Name 'modifiedDateTime'
        } -Source @{
            provider = 'microsoftGraph'
            apiVersion = 'v1.0'
            odataType = '#microsoft.graph.accessPackageAssignmentPolicy'
            resourcePath = "/identityGovernance/entitlementManagement/assignmentPolicies/$($policy.id)"
            collector = 'entitlementManagement'
        } -Tags @('governance', 'entitlementManagement')
        $result.Nodes.Add($policyNode)
        $policyKeyById[$policy.id] = $policyNode.Key

        if ($accessPackageId -and $packageKeyById.ContainsKey($accessPackageId)) {
            $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'entitlementManagement' -Endpoint $policiesEndpoint -SourceObjectId "$accessPackageId|$($policy.id)" -Fields @{
                accessPackageId = $accessPackageId
                assignmentPolicyId = $policy.id
            }
            $result.Evidence.Add($evidence)
            $result.Edges.Add(
                (New-AtlasEdge -TenantId $TenantId -From $packageKeyById[$accessPackageId] -To $policyNode.Key -Relationship 'governedByAccessPackagePolicy' -EvidenceIds @($evidence.Key) -Source @{ collector = 'entitlementManagement' })
            )
        }
    }

    $assignmentCount = 0
    foreach ($assignment in $assignmentResponse.Items) {
        $target = Get-AtlasResponseProperty -InputObject $assignment -Name 'target'
        $accessPackage = Get-AtlasResponseProperty -InputObject $assignment -Name 'accessPackage'
        $targetId = Get-AtlasResponseProperty -InputObject $target -Name 'objectId'
        if (-not $targetId) { $targetId = Get-AtlasResponseProperty -InputObject $assignment -Name 'targetId' }
        $accessPackageId = Get-AtlasResponseProperty -InputObject $accessPackage -Name 'id'
        if (-not $accessPackageId) { $accessPackageId = Get-AtlasResponseProperty -InputObject $assignment -Name 'accessPackageId' }
        if (-not $targetId -or -not $packageKeyById.ContainsKey($accessPackageId)) {
            $result.Status = 'partial'
            $result.Warnings.Add("Entitlement assignment '$($assignment.id)' could not be connected to both a subject and access package.")
            continue
        }

        if ($keyById.ContainsKey($targetId)) {
            $targetKey = $keyById[$targetId]
        }
        else {
            $targetKey = "tenant:$TenantId`:graph:$targetId"
            if (-not $addedNode.ContainsKey($targetKey)) {
                $targetName = Get-AtlasResponseProperty -InputObject $target -Name 'displayName'
                if (-not $targetName) { $targetName = $targetId }
                $targetNode = New-AtlasNode -TenantId $TenantId -Id $targetId -Kind 'entitlementSubject' -DisplayName $targetName -Status 'partial' -Properties @{
                    email = Get-AtlasResponseProperty -InputObject $target -Name 'email'
                    subjectType = Get-AtlasResponseProperty -InputObject $target -Name 'subjectType'
                } -Source @{
                    provider = 'microsoftGraph'
                    apiVersion = 'v1.0'
                    odataType = '#microsoft.graph.accessPackageSubject'
                    resourcePath = "/identityGovernance/entitlementManagement/assignments/$($assignment.id)"
                    collector = 'entitlementManagement'
                }
                $result.Nodes.Add($targetNode)
                $addedNode[$targetKey] = $true
            }
        }

        $schedule = Get-AtlasResponseProperty -InputObject $assignment -Name 'schedule'
        $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'entitlementManagement' -Endpoint $assignmentsEndpoint -SourceObjectId $assignment.id -Fields @{
            assignmentId = $assignment.id
            targetId = $targetId
            accessPackageId = $accessPackageId
            assignmentPolicyId = Get-AtlasResponseProperty -InputObject $assignment -Name 'assignmentPolicyId'
            state = Get-AtlasResponseProperty -InputObject $assignment -Name 'state'
            status = Get-AtlasResponseProperty -InputObject $assignment -Name 'status'
            schedule = $schedule
            expiredDateTime = Get-AtlasResponseProperty -InputObject $assignment -Name 'expiredDateTime'
        }
        $result.Evidence.Add($evidence)
        $result.Edges.Add(
            (New-AtlasEdge -TenantId $TenantId -From $targetKey -To $packageKeyById[$accessPackageId] -Relationship 'assignedAccessPackage' -State @{
                assignmentId = $assignment.id
                assignmentPolicyId = Get-AtlasResponseProperty -InputObject $assignment -Name 'assignmentPolicyId'
                state = Get-AtlasResponseProperty -InputObject $assignment -Name 'state'
                status = Get-AtlasResponseProperty -InputObject $assignment -Name 'status'
                schedule = $schedule
                expiredDateTime = Get-AtlasResponseProperty -InputObject $assignment -Name 'expiredDateTime'
            } -EvidenceIds @($evidence.Key) -Source @{ collector = 'entitlementManagement' })
        )
        $assignmentCount++
    }

    $result.Metrics = @{
        catalogCount = $catalogResponse.Items.Count
        accessPackageCount = $packageResponse.Items.Count
        assignmentPolicyCount = $policyResponse.Items.Count
        assignmentCount = $assignmentCount
        resourceRoleScopeCount = $resourceRoleScopeCount
        requestCount = $requestCount
        retryCount = $retryCount
    }
    return $result
}