Private/RoleManagement/Get-PIMRolePolicy.ps1
function Get-PIMRolePolicy { <# .SYNOPSIS Retrieves policy information for a PIM role. .DESCRIPTION Gets the policy requirements for activating a specific PIM role including maximum duration, MFA requirements, justification requirements, approval requirements, and authentication context. Supports Entra ID roles, PIM for Groups, and Azure Resource roles. Uses intelligent caching to reduce repeated API calls and improve performance. .PARAMETER Role The role object to get policy information for. Must contain Type, Id, Name properties. For Group roles, must also contain ResourceId property. .EXAMPLE Get-PIMRolePolicy -Role $role Returns policy information for the specified role. .OUTPUTS PSCustomObject Returns an object with the following properties: - MaxDuration: Maximum activation duration in hours - RequiresMfa: Whether MFA is required for activation - RequiresJustification: Whether justification text is required - RequiresTicket: Whether ticket/tracking number is required - RequiresApproval: Whether approval workflow is required - RequiresAuthenticationContext: Whether specific authentication context is required - AuthenticationContextId: The ID of the required authentication context - AuthenticationContextDisplayName: Display name of the authentication context - AuthenticationContextDescription: Description of the authentication context - AuthenticationContextDetails: Full authentication context object .NOTES Uses module-level caching for both policies and authentication contexts to improve performance. Gracefully handles API failures by returning sensible defaults. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject]$Role ) # Initialize module-level cache for policies if (-not (Test-Path Variable:script:PolicyCache) -or -not $script:PolicyCache) { $script:PolicyCache = @{} Write-Verbose "Initialized policy cache" } # Create cache key based on role type and ID $cacheKey = "$($Role.Type)_$($Role.Id)" if ($Role.Type -eq 'Group') { $cacheKey = "Group_$($Role.ResourceId)" } # Return cached result if available if ($script:PolicyCache.ContainsKey($cacheKey)) { Write-Verbose "Retrieved cached policy for role: $($Role.Name)" return $script:PolicyCache[$cacheKey] } Write-Verbose "Retrieving policy for role: $($Role.Name) [Type: $($Role.Type)]" # Initialize policy object with default values $policyInfo = [PSCustomObject]@{ MaxDuration = 8 RequiresMfa = $false RequiresJustification = $false RequiresTicket = $false RequiresApproval = $false RequiresAuthenticationContext = $false AuthenticationContextId = $null AuthenticationContextDisplayName = $null AuthenticationContextDescription = $null AuthenticationContextDetails = $null } # Initialize authentication context cache if needed if (-not $script:AuthenticationContextCache) { $script:AuthenticationContextCache = @{} Write-Verbose "Caching available authentication contexts..." try { $availableContexts = Get-AuthenticationContexts if ($availableContexts) { foreach ($context in $availableContexts) { $script:AuthenticationContextCache[$context.Id] = $context } Write-Verbose "Cached $($availableContexts.Count) authentication contexts" } } catch { Write-Verbose "Authentication contexts not available: $($_.Exception.Message)" } } try { switch ($Role.Type) { 'Entra' { Write-Verbose "Processing Entra ID role policy [RoleId: $($Role.Id)]" try { # Get policy assignments for this role $policyAssignments = Get-MgPolicyRoleManagementPolicyAssignment -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole' and roleDefinitionId eq '$($Role.Id)'" -ErrorAction Stop $policyAssignmentsArray = @($policyAssignments) if ($policyAssignmentsArray.Count -eq 0) { Write-Verbose "No policy assignments found for Entra role" break } $assignment = $policyAssignmentsArray[0] Write-Verbose "Found policy assignment [PolicyId: $($assignment.PolicyId)]" # Get the policy with expanded rules $policy = Get-MgPolicyRoleManagementPolicy -UnifiedRoleManagementPolicyId $assignment.PolicyId -ExpandProperty "rules" -ErrorAction Stop if ($policy -and $policy.Rules) { $rulesArray = @($policy.Rules) Write-Verbose "Processing $($rulesArray.Count) policy rules" foreach ($rule in $rulesArray) { if ($rule.AdditionalProperties) { $ruleType = $rule.AdditionalProperties['@odata.type'] switch ($ruleType) { '#microsoft.graph.unifiedRoleManagementPolicyExpirationRule' { if ($rule.AdditionalProperties.maximumDuration) { try { $duration = [System.Xml.XmlConvert]::ToTimeSpan($rule.AdditionalProperties.maximumDuration) $policyInfo.MaxDuration = [int]$duration.TotalHours } catch { Write-Verbose "Could not parse duration: $($rule.AdditionalProperties.maximumDuration)" } } } '#microsoft.graph.unifiedRoleManagementPolicyEnablementRule' { if ($rule.AdditionalProperties.enabledRules) { $enabledRulesArray = @($rule.AdditionalProperties.enabledRules) $policyInfo.RequiresJustification = 'Justification' -in $enabledRulesArray $policyInfo.RequiresTicket = 'Ticketing' -in $enabledRulesArray $policyInfo.RequiresMfa = 'MultiFactorAuthentication' -in $enabledRulesArray $policyInfo.RequiresAuthenticationContext = 'AuthenticationContext' -in $enabledRulesArray } } '#microsoft.graph.unifiedRoleManagementPolicyApprovalRule' { if ($rule.AdditionalProperties.setting -and $rule.AdditionalProperties.setting.isApprovalRequired) { $policyInfo.RequiresApproval = $true } } '#microsoft.graph.unifiedRoleManagementPolicyAuthenticationContextRule' { if ($rule.AdditionalProperties.isEnabled -and $rule.AdditionalProperties.claimValue) { $policyInfo.RequiresAuthenticationContext = $true $contextId = $rule.AdditionalProperties.claimValue $policyInfo.AuthenticationContextId = $contextId # Enhance with cached context information if ($script:AuthenticationContextCache.ContainsKey($contextId)) { $contextInfo = $script:AuthenticationContextCache[$contextId] $policyInfo.AuthenticationContextDisplayName = $contextInfo.DisplayName $policyInfo.AuthenticationContextDescription = $contextInfo.Description $policyInfo.AuthenticationContextDetails = $contextInfo } } } } } } } } catch { Write-Warning "Failed to retrieve Entra role policy for $($Role.Name): $($_.Exception.Message)" } } 'Group' { Write-Verbose "Processing PIM for Groups policy [GroupId: $($Role.ResourceId)]" if ($Role.ResourceId) { try { # Get policy assignments for the group $uri = "https://graph.microsoft.com/v1.0/policies/roleManagementPolicyAssignments?`$filter=scopeId eq '$($Role.ResourceId)' and scopeType eq 'Group'" $response = Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop if ($response.value -and @($response.value).Count -gt 0) { $assignmentsArray = @($response.value) # Look for member role assignment first, then owner $assignment = $assignmentsArray | Where-Object { $_.roleDefinitionId -eq 'member' } | Select-Object -First 1 if (-not $assignment) { $assignment = $assignmentsArray | Where-Object { $_.roleDefinitionId -eq 'owner' } | Select-Object -First 1 } if ($assignment) { Write-Verbose "Found group policy assignment [Role: $($assignment.roleDefinitionId), PolicyId: $($assignment.policyId)]" # Get the policy with expanded rules $policyUri = "https://graph.microsoft.com/v1.0/policies/roleManagementPolicies/$($assignment.policyId)?`$expand=rules" $policyResponse = Invoke-MgGraphRequest -Uri $policyUri -Method GET -ErrorAction Stop if ($policyResponse.rules) { $rulesArray = @($policyResponse.rules) Write-Verbose "Processing $($rulesArray.Count) group policy rules" foreach ($rule in $rulesArray) { if ($rule.id -like "*Expiration_EndUser_Assignment" -and $rule.maximumDuration) { try { $duration = [System.Xml.XmlConvert]::ToTimeSpan($rule.maximumDuration) $policyInfo.MaxDuration = [int]$duration.TotalHours } catch { Write-Verbose "Could not parse group duration: $($rule.maximumDuration)" } } elseif ($rule.id -like "*Enablement_EndUser_Assignment" -and $rule.enabledRules) { $enabledRulesArray = @($rule.enabledRules) $policyInfo.RequiresJustification = 'Justification' -in $enabledRulesArray $policyInfo.RequiresTicket = 'Ticketing' -in $enabledRulesArray $policyInfo.RequiresMfa = 'MultiFactorAuthentication' -in $enabledRulesArray $policyInfo.RequiresAuthenticationContext = 'AuthenticationContext' -in $enabledRulesArray } elseif ($rule.id -like "*Approval_EndUser_Assignment" -and $rule.setting.isApprovalRequired) { $policyInfo.RequiresApproval = $true } elseif ($rule.id -like "*AuthenticationContext_EndUser_Assignment" -and $rule.isEnabled -and $rule.claimValue) { $policyInfo.RequiresAuthenticationContext = $true $contextId = $rule.claimValue $policyInfo.AuthenticationContextId = $contextId # Enhance with cached context information if ($script:AuthenticationContextCache.ContainsKey($contextId)) { $contextInfo = $script:AuthenticationContextCache[$contextId] $policyInfo.AuthenticationContextDisplayName = $contextInfo.DisplayName $policyInfo.AuthenticationContextDescription = $contextInfo.Description $policyInfo.AuthenticationContextDetails = $contextInfo } } } } } else { Write-Verbose "No suitable role assignment found for group" } } else { Write-Verbose "No policy assignments found for group" } } catch { Write-Warning "Failed to retrieve group policy for $($Role.Name): $($_.Exception.Message)" # Set sensible defaults for groups $policyInfo.RequiresJustification = $true } } } 'AzureResource' { Write-Verbose "Using default policy for Azure Resource role" $policyInfo.RequiresJustification = $true } } # Cache the result $script:PolicyCache[$cacheKey] = $policyInfo # Create summary for verbose output $requirements = @() if ($policyInfo.RequiresMfa) { $requirements += "MFA" } if ($policyInfo.RequiresJustification) { $requirements += "Justification" } if ($policyInfo.RequiresTicket) { $requirements += "Ticket" } if ($policyInfo.RequiresApproval) { $requirements += "Approval" } if ($policyInfo.RequiresAuthenticationContext) { if ($policyInfo.AuthenticationContextDisplayName) { $requirements += "AuthContext ($($policyInfo.AuthenticationContextDisplayName))" } else { $requirements += "AuthContext ($($policyInfo.AuthenticationContextId))" } } $requirementsSummary = if ($requirements.Count -gt 0) { $requirements -join ", " } else { "None" } Write-Verbose "Policy cached for $($Role.Name): Duration=$($policyInfo.MaxDuration)h, Requirements=[$requirementsSummary]" } catch { Write-Warning "Failed to retrieve policy for role $($Role.Name): $($_.Exception.Message)" # Cache the default to avoid repeated failures $script:PolicyCache[$cacheKey] = $policyInfo } return $policyInfo } |