Files
M365FoundationsCISReport/source/Public/Grant-M365SecurityAuditConsent.ps1
2024-07-07 16:42:55 -05:00

183 lines
12 KiB
PowerShell

<#
.SYNOPSIS
Grants Microsoft Graph permissions for an auditor.
.DESCRIPTION
This function grants the specified Microsoft Graph permissions to a user, allowing the user to perform audits. It connects to Microsoft Graph, checks if a service principal exists for the client application, creates it if it does not exist, and then grants the specified permissions. Finally, it assigns the app to the user.
.PARAMETER UserPrincipalNameForConsent
The UPN or ID of the user to grant consent for.
.PARAMETER SkipGraphConnection
If specified, skips connecting to Microsoft Graph.
.PARAMETER DoNotDisconnect
If specified, does not disconnect from Microsoft Graph after granting consent.
.PARAMETER SkipModuleCheck
If specified, skips the check for the Microsoft.Graph module.
.PARAMETER SuppressRevertOutput
If specified, suppresses the output of the revert commands.
.EXAMPLE
Grant-M365SecurityAuditConsent -UserPrincipalNameForConsent user@example.com
Grants Microsoft Graph permissions to user@example.com for the client application with the specified Application ID.
.EXAMPLE
Grant-M365SecurityAuditConsent -UserPrincipalNameForConsent user@example.com -SkipGraphConnection
Grants Microsoft Graph permissions to user@example.com, skipping the connection to Microsoft Graph.
.NOTES
This function requires the Microsoft.Graph module version 2.4.0 or higher.
.LINK
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Grant-M365SecurityAuditConsent
#>
function Grant-M365SecurityAuditConsent {
[CmdletBinding(
SupportsShouldProcess = $true,
ConfirmImpact = 'High'
)]
[OutputType([void])]
param (
[Parameter(
Mandatory = $true,
Position = 0,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Specify the UPN of the user to grant consent for.'
)]
[ValidatePattern('^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')]
[String]$UserPrincipalNameForConsent,
[Parameter(
Mandatory = $false,
HelpMessage = 'Skip connecting to Microsoft Graph.'
)]
[switch]$SkipGraphConnection,
[Parameter(
Mandatory = $false,
HelpMessage = 'Skip the check for the Microsoft.Graph module.'
)]
[switch]$SkipModuleCheck,
[Parameter(
Mandatory = $false,
HelpMessage = 'Suppress the output of the revert commands.'
)]
[switch]$SuppressRevertOutput,
[Parameter(
Mandatory = $false,
HelpMessage = 'Do not disconnect from Microsoft Graph after granting consent.'
)]
[switch]$DoNotDisconnect
)
begin {
if (!($SkipModuleCheck)) {
Assert-ModuleAvailability -ModuleName Microsoft.Graph -RequiredVersion "2.4.0"
}
# Adjusted from: https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-consent-single-user?pivots=msgraph-powershell
# Needed: A user account with a Privileged Role Administrator, Application Administrator, or Cloud Application Administrator
# The app for which consent is being granted.
$clientAppId = "14d82eec-204b-4c2f-b7e8-296a70dab67e" # Microsoft Graph PowerShell
# The API to which access will be granted. Microsoft Graph PowerShell makes API
# requests to the Microsoft Graph API, so we'll use that here.
$resourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph API
# The permissions to grant.
$permissions = @("Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All")
# The user on behalf of whom access will be granted. The app will be able to access
# the API on behalf of this user.
$userUpnOrId = $UserPrincipalNameForConsent
}
process {
try {
if (-not $SkipGraphConnection -and $PSCmdlet.ShouldProcess("Scopes: User.ReadBasic.All, Application.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All, AppRoleAssignment.ReadWrite.All", "Connect-MgGraph")) {
# Step 0. Connect to Microsoft Graph PowerShell. We need User.ReadBasic.All to get
# users' IDs, Application.ReadWrite.All to list and create service principals,
# DelegatedPermissionGrant.ReadWrite.All to create delegated permission grants,
# and AppRoleAssignment.ReadWrite.All to assign an app role.
# WARNING: These are high-privilege permissions!
Write-Host "Connecting to Microsoft Graph with scopes: User.ReadBasic.All, Application.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All, AppRoleAssignment.ReadWrite.All" -ForegroundColor Yellow
Connect-MgGraph -Scopes ("User.ReadBasic.All Application.ReadWrite.All " + "DelegatedPermissionGrant.ReadWrite.All " + "AppRoleAssignment.ReadWrite.All") -NoWelcome
$context = Get-MgContext
Write-Host "Connected to Microsoft Graph with user: $(($context.Account)) with the authtype `"$($context.AuthType)`" for the `"$($context.Environment)`" environment." -ForegroundColor Green
}
}
catch {
throw "Connection execution aborted: $_"
break
}
try {
if ($PSCmdlet.ShouldProcess("Create Microsoft Graph API service princial if not found", "New-MgServicePrincipal")) {
# Step 1. Check if a service principal exists for the client application.
# If one doesn't exist, create it.
$clientSp = Get-MgServicePrincipal -Filter "appId eq '$($clientAppId)'" -ErrorAction SilentlyContinue
if (-not $clientSp) {
Write-Host "Client service principal not found. Creating one." -ForegroundColor Yellow
$clientSp = New-MgServicePrincipal -AppId $clientAppId
}
$user = Get-MgUser -UserId $userUpnOrId
if (!($user)) {
throw "User with UPN or ID `"$userUpnOrId`" not found."
}
Write-Verbose "User: $($user.UserPrincipalName) Found!"
$resourceSp = Get-MgServicePrincipal -Filter "appId eq '$($resourceAppId)'"
$scopeToGrant = $permissions -join " "
$existingGrant = Get-MgOauth2PermissionGrant -Filter "clientId eq '$($clientSp.Id)' and principalId eq '$($user.Id)' and resourceId eq '$($resourceSp.Id)'"
}
if (-not $existingGrant -and $PSCmdlet.ShouldProcess("User: $userUpnOrId for Microsoft Graph PowerShell Scopes: $($permissions -join ', ')", "New-MgOauth2PermissionGrant: Granting Consent")) {
# Step 2. Create a delegated permission that grants the client app access to the
# API, on behalf of the user.
$grant = New-MgOauth2PermissionGrant -ResourceId $resourceSp.Id -Scope $scopeToGrant -ClientId $clientSp.Id -ConsentType "Principal" -PrincipalId $user.Id
Write-Host "Consent granted to user $($user.UserPrincipalName) for Microsoft Graph API with scopes: $((($grant.Scope) -split ' ') -join ', ')" -ForegroundColor Green
}
if ($existingGrant -and $PSCmdlet.ShouldProcess("Update existing Microsoft Graph permissions for user $userUpnOrId", "Update-MgOauth2PermissionGrant")) {
# Step 2. Update the existing permission grant with the new scopes.
Write-Host "Updating existing permission grant for user $($user.UserPrincipalName)." -ForegroundColor Yellow
$updatedGrant = Update-MgOauth2PermissionGrant -PermissionGrantId $existingGrant.Id -Scope $scopeToGrant -Confirm:$false
Write-Host "Updated permission grant with ID $($updatedGrant.Id) for scopes: $scopeToGrant" -ForegroundColor Green
}
if ($PSCmdlet.ShouldProcess("Assigning app to user $userUpnOrId", "New-MgServicePrincipalAppRoleAssignedTo")) {
# Step 3. Assign the app to the user. This ensures that the user can sign in if assignment
# is required, and ensures that the app shows up under the user's My Apps portal.
if ($clientSp.AppRoles | Where-Object { $_.AllowedMemberTypes -contains "User" }) {
Write-Warning "A default app role assignment cannot be created because the client application exposes user-assignable app roles. You must assign the user a specific app role for the app to be listed in the user's My Apps access panel."
}
else {
# The app role ID 00000000-0000-0000-0000-000000000000 is the default app role
# indicating that the app is assigned to the user, but not for any specific
# app role.
$assignment = New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $clientSp.Id -ResourceId $clientSp.Id -PrincipalId $user.Id -AppRoleId "00000000-0000-0000-0000-000000000000"
# $assignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $assignment.ResourceId -All -WhatIf
}
}
}
catch {
throw "An error occurred while granting consent:`n$_"
}
finally {
if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft Graph", "Disconnect")) {
# Clean up sessions
Write-Host "Disconnecting from Microsoft Graph." -ForegroundColor Yellow
Disconnect-MgGraph | Out-Null
}
}
}
end {
if (-not $SuppressRevertOutput -and $PSCmdlet.ShouldProcess("Instructions to undo this change", "Generate Revert Commands")) {
<#
# Instructions to revert the changes made by this script
$resourceAppId = "00000003-0000-0000-c000-000000000000"
$clientAppId = "14d82eec-204b-4c2f-b7e8-296a70dab67e"
# Get the user object
#$user = Get-MgUser -UserId "user@example.com"
$resourceSp = Get-MgServicePrincipal -Filter "appId eq '$($resourceAppId)'"
# Get the service principal using $clientAppId
$clientSp = Get-MgServicePrincipal -Filter "appId eq '$($clientAppId)'"
$existingGrant = Get-MgOauth2PermissionGrant -Filter "clientId eq '$($clientSp.Id)' and principalId eq '$($user.Id)' and resourceId eq '$($resourceSp.Id)'"
# Get all app role assignments for the service principal
$appRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $clientSp.Id -All
# At index of desired user assignment
Remove-MgServicePrincipalAppRoleAssignedTo -AppRoleAssignmentId $appRoleAssignments[1].Id -ServicePrincipalId $clientSp.Id
Remove-MgOAuth2PermissionGrant -OAuth2PermissionGrantId $existingGrant.Id
#>
Write-Host "App assigned to user $($assignment.PrincipalDisplayName) for $($assignment.ResourceDisplayName) at $($assignment.CreatedDateTime)." -ForegroundColor Green
Write-Host "If you made a mistake and would like to remove the assignement for `"$($user.UserPrincipalName)`", you can run the following:`n" -ForegroundColor Yellow
Write-Host "Connect-MgGraph -Scopes (`"User.ReadBasic.All Application.ReadWrite.All `" + `"DelegatedPermissionGrant.ReadWrite.All `" + `"AppRoleAssignment.ReadWrite.All`")" -ForegroundColor Cyan
Write-Host "Remove-MgServicePrincipalAppRoleAssignedTo -AppRoleAssignmentId `"$($assignment.Id)`" -ServicePrincipalId `"$($assignment.ResourceId)`"" -ForegroundColor Cyan
Write-Host "Remove-MgOAuth2PermissionGrant -OAuth2PermissionGrantId `"$($grant.Id)`"" -ForegroundColor Cyan
}
}
}