Private/22-GetAtlasDirectoryRoles.ps1
|
function Get-AtlasDirectoryRole { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasNode[]] $KnownNode ) $result = [AtlasCollectionResult]::new() $keyById = @{} $nodeById = @{} foreach ($node in $KnownNode) { if (-not $keyById.ContainsKey($node.Id) -or $node.Status -eq 'complete') { $keyById[$node.Id] = $node.Key $nodeById[$node.Id] = $node } } $definitionsEndpoint = '/v1.0/roleManagement/directory/roleDefinitions?$select=id,displayName,description,isBuiltIn,isEnabled' $assignmentsEndpoint = '/v1.0/roleManagement/directory/roleAssignments?$select=id,principalId,roleDefinitionId,directoryScopeId' $eligibilityEndpoint = '/v1.0/roleManagement/directory/roleEligibilityScheduleInstances?$select=id,principalId,roleDefinitionId,directoryScopeId,startDateTime,endDateTime,memberType' $definitionResponse = Invoke-AtlasGraphRequest -Uri $definitionsEndpoint $assignmentResponse = Invoke-AtlasGraphRequest -Uri $assignmentsEndpoint try { $eligibilityResponse = Invoke-AtlasGraphRequest -Uri $eligibilityEndpoint } catch { $result.Status = 'partial' $result.Warnings.Add("PIM eligible role assignments could not be collected: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") $eligibilityResponse = [pscustomobject] @{ Items = @() Metrics = @{ requestCount = 0; retryCount = 0 } } } $roleKeyById = @{} $unresolvedRoles = @{} $definitions = @($definitionResponse.Items) $listedIds = @{}; foreach ($definition in $definitions) { $listedIds[$definition.id] = $true } $lookupCount = 0; $lookupRequests = 0; $lookupRetries = 0 $referencedIds = @(@($assignmentResponse.Items) + @($eligibilityResponse.Items) | ForEach-Object { $_.roleDefinitionId } | Sort-Object -Unique) foreach ($roleId in $referencedIds) { if ($listedIds.ContainsKey($roleId)) { continue } try { $lookupCount++ $lookupRequests++ $endpoint = '/v1.0/roleManagement/directory/roleDefinitions/' + [uri]::EscapeDataString($roleId) $lookup = Invoke-AtlasGraphRequest -Uri $endpoint $lookupRequests += $lookup.Metrics.requestCount - 1 $lookupRetries += $lookup.Metrics.retryCount $items = @($lookup.Items) if ($items.Count -ne 1 -or $items[0].id -ne $roleId -or -not $items[0].displayName) { throw 'Direct role lookup returned an unexpected identifier or incomplete definition.' } $definitions += $items[0] } catch [System.Management.Automation.PipelineStoppedException] { throw } catch [System.OperationCanceledException] { throw } catch { $result.Status = 'partial' $unresolvedRoles[$roleId] = $true $result.Warnings.Add("Role definition '$roleId' could not be resolved. Assignment evidence retained: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") $placeholder = New-AtlasNode -TenantId $TenantId -Id $roleId -Kind roleDefinition -DisplayName "Unresolved role $roleId" -Status unresolved -Source @{ collector = 'directoryRoles'; provider = 'microsoftGraph' } $result.Nodes.Add($placeholder) $roleKeyById[$roleId] = $placeholder.Key } } foreach ($definition in $definitions) { $roleNode = New-AtlasNode -TenantId $TenantId -Id $definition.id -Kind 'roleDefinition' -DisplayName $definition.displayName -Properties @{ description = Get-AtlasResponseProperty -InputObject $definition -Name 'description' isBuiltIn = Get-AtlasResponseProperty -InputObject $definition -Name 'isBuiltIn' isEnabled = Get-AtlasResponseProperty -InputObject $definition -Name 'isEnabled' } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.unifiedRoleDefinition' resourcePath = "/roleManagement/directory/roleDefinitions/$($definition.id)" collector = 'directoryRoles' } $result.Nodes.Add($roleNode) $roleKeyById[$definition.id] = $roleNode.Key } foreach ($assignment in $assignmentResponse.Items) { $appScopeId = Get-AtlasResponseProperty -InputObject $assignment -Name 'appScopeId' if ($keyById.ContainsKey($assignment.principalId)) { $principalKey = $keyById[$assignment.principalId] } else { $principalNode = New-AtlasNode -TenantId $TenantId -Id $assignment.principalId -Kind 'directoryObject' -DisplayName $assignment.principalId -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' resourcePath = "/directoryObjects/$($assignment.principalId)" collector = 'directoryRoles' } $result.Nodes.Add($principalNode) $principalKey = $principalNode.Key } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'directoryRoles' -Endpoint $assignmentsEndpoint -SourceObjectId $assignment.id -Fields @{ principalId = $assignment.principalId roleDefinitionId = $assignment.roleDefinitionId directoryScopeId = $assignment.directoryScopeId appScopeId = $appScopeId activation = 'active' } if ($unresolvedRoles.ContainsKey($assignment.roleDefinitionId)) { $evidence.Completeness = 'partial' } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $roleKeyById[$assignment.roleDefinitionId] -Relationship 'assignedRole' -State @{ assignmentId = $assignment.id activation = 'active' assignment = if ($nodeById.ContainsKey($assignment.principalId) -and $nodeById[$assignment.principalId].Kind -eq 'group') { 'group' } else { 'direct' } directoryScopeId = $assignment.directoryScopeId appScopeId = $appScopeId } -EvidenceIds @($evidence.Key) -Source @{ collector = 'directoryRoles' }) ) } foreach ($eligibility in $eligibilityResponse.Items) { $eligibilityStartDateTime = Get-AtlasResponseProperty -InputObject $eligibility -Name 'startDateTime' $eligibilityEndDateTime = Get-AtlasResponseProperty -InputObject $eligibility -Name 'endDateTime' $eligibilityMemberType = Get-AtlasResponseProperty -InputObject $eligibility -Name 'memberType' if ($keyById.ContainsKey($eligibility.principalId)) { $principalKey = $keyById[$eligibility.principalId] } else { $principalNode = New-AtlasNode -TenantId $TenantId -Id $eligibility.principalId -Kind 'directoryObject' -DisplayName $eligibility.principalId -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' resourcePath = "/directoryObjects/$($eligibility.principalId)" collector = 'directoryRoleEligibility' } $result.Nodes.Add($principalNode) $principalKey = $principalNode.Key } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'directoryRoleEligibility' -Endpoint $eligibilityEndpoint -SourceObjectId $eligibility.id -Fields @{ principalId = $eligibility.principalId roleDefinitionId = $eligibility.roleDefinitionId directoryScopeId = $eligibility.directoryScopeId activation = 'eligible' startDateTime = $eligibilityStartDateTime endDateTime = $eligibilityEndDateTime memberType = $eligibilityMemberType } if ($unresolvedRoles.ContainsKey($eligibility.roleDefinitionId)) { $evidence.Completeness = 'partial' } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $roleKeyById[$eligibility.roleDefinitionId] -Relationship 'eligibleRole' -State @{ assignmentId = $eligibility.id activation = 'eligible' assignment = if ($nodeById.ContainsKey($eligibility.principalId) -and $nodeById[$eligibility.principalId].Kind -eq 'group') { 'group' } else { 'direct' } directoryScopeId = $eligibility.directoryScopeId startDateTime = $eligibilityStartDateTime endDateTime = $eligibilityEndDateTime memberType = $eligibilityMemberType } -EvidenceIds @($evidence.Key) -Source @{ collector = 'directoryRoleEligibility' }) ) } $result.Metrics = @{ roleDefinitionCount = $definitions.Count directRoleLookupCount = $lookupCount unresolvedRoleDefinitionCount = $unresolvedRoles.Count roleAssignmentCount = $assignmentResponse.Items.Count roleEligibilityCount = $eligibilityResponse.Items.Count requestCount = $definitionResponse.Metrics.requestCount + $assignmentResponse.Metrics.requestCount + $eligibilityResponse.Metrics.requestCount + $lookupRequests retryCount = $definitionResponse.Metrics.retryCount + $assignmentResponse.Metrics.retryCount + $eligibilityResponse.Metrics.retryCount + $lookupRetries } return $result } |