From 87b601ef7da047852119eaf1d5fcaf76ffd13566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 29 Oct 2023 19:07:00 +0100 Subject: [PATCH 01/20] fix 404 links to the old docs site --- ExecAccessChecks/run.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ExecAccessChecks/run.ps1 b/ExecAccessChecks/run.ps1 index ae45d23e6230..9e569ce565d6 100644 --- a/ExecAccessChecks/run.ps1 +++ b/ExecAccessChecks/run.ps1 @@ -46,7 +46,7 @@ if ($Request.query.Permissions -eq 'true') { $Messages.Add('Your refresh token does not match key vault, clear your cache or wait 30 minutes.') | Out-Null $Links.Add([PSCustomObject]@{ Text = 'Clear Token Cache' - Href = 'https://cipp.app/docs/general/troubleshooting/#clear-token-cache' + Href = 'https://docs.cipp.app/setup/installation/cleartokencache' } ) | Out-Null } else { @@ -79,7 +79,7 @@ if ($Request.query.Permissions -eq 'true') { $Success = $false $Links.Add([PSCustomObject]@{ Text = 'MFA Troubleshooting' - Href = 'https://cipp.app/docs/general/troubleshooting/#multi-factor-authentication-troubleshooting' + Href = 'https://docs.cipp.app/troubleshooting/troubleshooting#multi-factor-authentication-troubleshooting' } ) | Out-Null } @@ -93,7 +93,7 @@ if ($Request.query.Permissions -eq 'true') { $Success = $false $Links.Add([PSCustomObject]@{ Text = 'Permissions' - Href = 'https://cipp.app/docs/user/gettingstarted/postinstall/permissions/' + Href = 'https://docs.cipp.app/setup/installation/permissions' } ) | Out-Null } else { From 856752b7cc58733526afba752491b0e40bc9565b Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Mon, 30 Oct 2023 09:43:14 +0000 Subject: [PATCH 02/20] Fix OOO in Offboarding Wizard --- ExecOffboardUser/run.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExecOffboardUser/run.ps1 b/ExecOffboardUser/run.ps1 index 6195792e60ec..5e701c996166 100644 --- a/ExecOffboardUser/run.ps1 +++ b/ExecOffboardUser/run.ps1 @@ -45,7 +45,7 @@ try { } { $_."OOO" -ne "" } { - Set-CIPPOutOfOffice -tenantFilter $tenantFilter -userid $username -OOO $request.body.OOO -ExecutingUser $request.headers.'x-ms-client-principal' -APIName "ExecOffboardUser" + Set-CIPPOutOfOffice -tenantFilter $tenantFilter -userid $username -InternalMessage $request.body.OOO -ExternalMessage $request.body.OOO -ExecutingUser $request.headers.'x-ms-client-principal' -APIName "ExecOffboardUser" } { $_."forward" -ne "" } { Set-CIPPForwarding -userid $userid -username $username -tenantFilter $Tenantfilter -Forward $request.body.forward -KeepCopy [bool]$request.body.keepCopy -ExecutingUser $request.headers.'x-ms-client-principal' -APIName "ExecOffboardUser" From decca178662677ce3b0dba79b4450ca6eb234f26 Mon Sep 17 00:00:00 2001 From: Alexander McLennan Date: Mon, 30 Oct 2023 20:28:50 +0800 Subject: [PATCH 03/20] BugFix: Get-CIPPMFAState outputting ints --- Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index c51b4ee90fae..213f70459b57 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -41,12 +41,12 @@ function Get-CIPPMFAState { if ($Policy.conditions.applications.includeApplications -ne 'All') { Write-Host $Policy.conditions.applications.includeApplications $CAState.Add("$($policy.displayName) - Specific Applications - $($policy.state)") | Out-Null - $Policy.conditions.users.excludeUsers.foreach({ $ExcludeSpecific.Add($_) }) + $Policy.conditions.users.excludeUsers.foreach({ $ExcludeSpecific.Add($_) | Out-Null }) continue } if ($Policy.conditions.users.includeUsers -eq 'All') { $CAState.Add("$($policy.displayName) - All Users - $($policy.state)") | Out-Null - $Policy.conditions.users.excludeUsers.foreach({ $ExcludeAllUsers.Add($_) }) + $Policy.conditions.users.excludeUsers.foreach({ $ExcludeAllUsers.Add($_) | Out-Null }) continue } } From 9b475900f82d3b80d6034ca01cfa3fbcbc5f5b38 Mon Sep 17 00:00:00 2001 From: Alexander McLennan Date: Tue, 31 Oct 2023 09:28:13 +0800 Subject: [PATCH 04/20] Unbork Get-CIPPMFAState.ps1 --- Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index 213f70459b57..73a2295b3be1 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -25,7 +25,7 @@ function Get-CIPPMFAState { $MFARegistration = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails' -tenantid $TenantFilter) } catch { - $CAState.Add('Not Licensed for Conditional Access') + $CAState.Add('Not Licensed for Conditional Access') | Out-Null $MFARegistration = $null } From 2cd3bec16e819f95a5cbfdbb020537fe3dd952be Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 1 Nov 2023 10:18:35 +0100 Subject: [PATCH 05/20] little more sleep --- Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index 2c0a3eaf0047..81c0bdd6761a 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -13,7 +13,7 @@ function Add-CIPPApplicationPermission { $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId if(!$ourSVCPrincipal) { #Our Service Principal isn't available yet. We do a sleep and reexecute after 3 seconds. - Start-Sleep -Seconds 3 + Start-Sleep -Seconds 5 $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId } From 64ee7bfd3868d9259d88501defc82630ce8af0d1 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Wed, 1 Nov 2023 21:42:40 +0100 Subject: [PATCH 06/20] Add Get-CIPPPartnerAzSubscriptions.ps1 --- .../Public/Get-CIPPPartnerAzSubscriptions.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 diff --git a/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 b/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 new file mode 100644 index 000000000000..ea67ec1a3f9e --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 @@ -0,0 +1,64 @@ +function Get-CIPPPartnerAzSubscriptions { + param ( + $TenantFilter, + $APIName = "Get-CIPPPartnerAzSubscriptions" + ) + + try { + if ($variable -notmatch '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}') { + $TenantFilter = (Invoke-RestMethod -Method GET "https://login.windows.net/$TenantFilter/.well-known/openid-configuration").token_endpoint.Split('/')[3] + } + } catch { + throw "Tenant $($TenantFilter) could not be found" + } + + $subsCache = [system.collections.generic.list[hashtable]]::new() + try { + try { + $usageRecords = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/usagerecords" -scope "https://api.partnercenter.microsoft.com/user_impersonation").items + } catch { + throw "Unable to retrieve usagerecord(s): $($_.Exception.Message)" + } + + foreach ($usageRecord in $usageRecords) { + # if condition probably needs more refining + if ($usageRecord.offerId -notlike "DZH318Z0BPS6*") { + # Legacy subscriptions are directly accessible + $subDetails = @{ + tenantId = $tenantFilter + subscriptionId = ($usageRecord.id).ToLower() + isLegacy = $true + POR = "Legacy subscription" + status = $usageRecord.status + } + + $subsCache.Add($subDetails) + } else { + # For modern subscriptions we need to dig a little deeper + try { + $subid = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/$($usageRecord.id)/azureEntitlements" -scope "https://api.partnercenter.microsoft.com/user_impersonation").items #| Where-Object { $_.status -eq "active" } + + foreach ($id in $subid) { + $subDetails = @{ + tenantId = $tenantFilter + subscriptionId = ($id.id) + isLegacy = $false + POR = $id.partnerOnRecord + status = $id.status + } + + $subsCache.Add($subDetails) + } + } catch { + # what do we do here error wise? + #Write-LogMessage -message "Unable to retrieve subscriptions(s) from usagerecord $($usageRecord.id): $($_.Exception.Message)" -Sev 'ERROR' -API $APINAME + #Write-Error "Unable to retrieve sub(s) from usagerecord $($usageRecord.id) for tenant $($tenantFilter): $($_.Exception.Message)" + } + } + } + + return $subsCache + } catch { + Write-LogMessage -message "Unable to retrieve CSP Azure subscriptions for $($TenantFilter): $($_.Exception.Message)" -Sev 'ERROR' -API $APINAME + } +} From 0b942ac3011d69fc6bbb7b95b9d0835efe23c9de Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Thu, 2 Nov 2023 14:24:10 +0000 Subject: [PATCH 07/20] Fixed Typo :) --- Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 index 5c6ffb25dc38..1602e9861665 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 @@ -24,8 +24,8 @@ function Remove-CIPPMailboxPermissions { } "FullAccess" { $permissions = New-ExoRequest -tenantid $TenantFilter -cmdlet "Remove-MailboxPermission" -cmdParams @{Identity = $userid; user = $AccessUser; accessRights = @("FullAccess") } -Anchor $userid - Write-LogMessage -user $ExecutingUser -API $APIName -message "Removed FullAcess permissions for $($AccessUser) from $($userid)'s mailbox." -Sev "Info" -tenant $TenantFilter - "Removed FullAcess permissions for $($AccessUser) from $($userid)'s mailbox." + Write-LogMessage -user $ExecutingUser -API $APIName -message "Removed FullAccess permissions for $($AccessUser) from $($userid)'s mailbox." -Sev "Info" -tenant $TenantFilter + "Removed FullAccess permissions for $($AccessUser) from $($userid)'s mailbox." } } } From fb98600087211f6b4a2d404a1f2fd58c95ff8ab3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 2 Nov 2023 16:25:52 +0100 Subject: [PATCH 08/20] fix for blank tenants --- GraphHelper.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GraphHelper.psm1 b/GraphHelper.psm1 index bcbcdf24eb0f..1848c74ecb09 100644 --- a/GraphHelper.psm1 +++ b/GraphHelper.psm1 @@ -505,7 +505,7 @@ function Get-Tenants { }) | Out-Null } foreach ($Tenant in $TenantList) { - if ($Tenant.defaultDomainName -eq 'Invalid') { continue } + if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { continue } $IncludedTenantsCache.Add(@{ RowKey = [string]$Tenant.customerId PartitionKey = 'Tenants' @@ -528,7 +528,7 @@ function Get-Tenants { Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache } } - return ($IncludedTenantsCache | Sort-Object -Property displayName) + return ($IncludedTenantsCache | Where-Object -Property defaultDomainName -ne $null | Sort-Object -Property displayName) } From 3f5cd04b7d4d54c41a661a5c7d2ca151955019fe Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:38:17 +0100 Subject: [PATCH 09/20] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cippb2p4g.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/dev_cippb2p4g.yml diff --git a/.github/workflows/dev_cippb2p4g.yml b/.github/workflows/dev_cippb2p4g.yml new file mode 100644 index 000000000000..e82ec8c1ba86 --- /dev/null +++ b/.github/workflows/dev_cippb2p4g.yml @@ -0,0 +1,29 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cippb2p4g + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + build-and-deploy: + runs-on: windows-latest + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cippb2p4g' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_C106B398356B4EFCB81F779ED5806A0D }} From 6e52317fc6eac3b45fb309b690118d00194eccdc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 2 Nov 2023 17:25:41 -0400 Subject: [PATCH 10/20] GDAP Invite Tweaks - Add autoExtendDuration - Remove partner property - Add separate property for invite urls to return --- ExecGDAPInvite/run.ps1 | 21 ++++++++++++--------- ExecGDAPInviteQueue/run.ps1 | 6 +++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ExecGDAPInvite/run.ps1 b/ExecGDAPInvite/run.ps1 index ff966b6e8a75..2e043db68286 100644 --- a/ExecGDAPInvite/run.ps1 +++ b/ExecGDAPInvite/run.ps1 @@ -7,19 +7,18 @@ $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $RoleMappings = $Request.body.gdapRoles -$Results = [System.Collections.ArrayList]@() +$Results = [System.Collections.Generic.List[string]]::new() +$InviteUrls = [System.Collections.Generic.List[string]]::new() $Table = Get-CIPPTable -TableName 'GDAPInvites' try { $JSONBody = @{ - 'displayName' = "$((New-Guid).GUID)" - 'partner' = @{ - 'tenantId' = "$env:tenantid" - } - 'accessDetails' = @{ + 'displayName' = "$((New-Guid).GUID)" + 'accessDetails' = @{ 'unifiedRoles' = @($RoleMappings | Select-Object roleDefinitionId) } - 'duration' = 'P730D' + 'autoExtendDuration' = 'P180D' + 'duration' = 'P730D' } | ConvertTo-Json -Depth 5 -Compress $NewRelationship = New-GraphPostRequest -NoAuthCheck $True -uri 'https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships' -type POST -body $JSONBody -verbose -tenantid $env:TenantID @@ -40,6 +39,7 @@ try { if ($NewRelationshipRequest.action -eq 'lockForApproval') { $InviteUrl = "https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/$($NewRelationship.id)" + $InviteUrls.Add($InviteUrl) $InviteEntity = [PSCustomObject]@{ 'PartitionKey' = 'invite' @@ -58,9 +58,12 @@ try { $Results.add('Error creating GDAP relationship') } -Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Debug' +Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' -$body = @{Results = @($Results) } +$body = @{ + Results = @($Results) + InviteUrls = @($InviteUrls) +} Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $body diff --git a/ExecGDAPInviteQueue/run.ps1 b/ExecGDAPInviteQueue/run.ps1 index 3d9fd22c68b6..78e43c118449 100644 --- a/ExecGDAPInviteQueue/run.ps1 +++ b/ExecGDAPInviteQueue/run.ps1 @@ -7,7 +7,7 @@ Write-Host "PowerShell queue trigger function processed work item: $QueueItem" $Table = Get-CIPPTable -TableName 'GDAPInvites' $Invite = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$QueueItem'" - +$APINAME = 'GDAPInvites' $RoleMappings = $Invite.RoleMappings | ConvertFrom-Json Write-Host ($Invite | ConvertTo-Json -Compress) @@ -27,9 +27,9 @@ foreach ($role in $RoleMappings) { New-GraphPostRequest -NoAuthCheck $True -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$($QueueItem)/accessAssignments" -tenantid $env:TenantID -type POST -body $MappingBody -verbose Start-Sleep -Milliseconds 100 } catch { - Write-LogMessage -API $APINAME -message "GDAP Group mapping failed - $($role.GroupId): $($_.Exception.Message)" -Sev 'Debug' + Write-LogMessage -API $APINAME -message "GDAP Group mapping failed - $($role.GroupId): $($_.Exception.Message)" -Sev Error exit 1 } - Write-LogMessage -API $APINAME -message "Groups mapped for GDAP Relationship: $($GdapInvite.RowKey)" + Write-LogMessage -API $APINAME -message "Groups mapped for GDAP Relationship: $($GdapInvite.RowKey)" -Sev Info } Remove-AzDataTableEntity @Table -Entity $Invite From 146f3ace97d5d5127e7e619b93223bcf7514cdc3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 11:34:34 +0100 Subject: [PATCH 11/20] first version gdap check --- ExecAccessChecks/run.ps1 | 116 ++++-------------- .../Public/Test-CIPPGDAPRelationships.ps1 | 82 +++++++++++++ 2 files changed, 108 insertions(+), 90 deletions(-) create mode 100644 Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 diff --git a/ExecAccessChecks/run.ps1 b/ExecAccessChecks/run.ps1 index 9e569ce565d6..d9dd4990af89 100644 --- a/ExecAccessChecks/run.ps1 +++ b/ExecAccessChecks/run.ps1 @@ -49,17 +49,20 @@ if ($Request.query.Permissions -eq 'true') { Href = 'https://docs.cipp.app/setup/installation/cleartokencache' } ) | Out-Null - } else { + } + else { $Messages.Add('Your refresh token matches key vault.') | Out-Null } - } catch { + } + catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' } } try { $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue - } catch { + } + catch { $AccessTokenDetails = [PSCustomObject]@{ Name = '' AuthMethods = @() @@ -71,10 +74,12 @@ if ($Request.query.Permissions -eq 'true') { if ($AccessTokenDetails.Name -eq '') { $Messages.Add('Your refresh token is invalid, check for line breaks or missing characters.') | Out-Null $Success = $false - } else { + } + else { if ($AccessTokenDetails.AuthMethods -contains 'mfa') { $Messages.Add('Your access token contains the MFA claim.') | Out-Null - } else { + } + else { $Messages.Add('Your access token does not contain the MFA claim, Refresh your SAM tokens.') | Out-Null $Success = $false $Links.Add([PSCustomObject]@{ @@ -96,7 +101,8 @@ if ($Request.query.Permissions -eq 'true') { Href = 'https://docs.cipp.app/setup/installation/permissions' } ) | Out-Null - } else { + } + else { $Messages.Add('Your Secure Application Model has all required permissions') | Out-Null } $CIPPGroupCount = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/`$count?`$filter=startsWith(displayName,'M365 GDAP')" -NoAuthCheck $true -ComplexFilter @@ -135,10 +141,12 @@ if ($Request.query.Permissions -eq 'true') { } if (($MissingGroups | Measure-Object).Count -eq 0) { $Messages.Add('The SAM user has all the required groups') - } else { + } + else { $Success = $false } - } catch { + } + catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' $Messages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null $Success = $false @@ -177,93 +185,21 @@ if ($Request.query.Tenants -eq 'true') { $TenantIds = foreach ($Tenant in $Tenants) { ($TenantList | Where-Object { $_.defaultDomainName -eq $Tenant }).customerId } - $MyRoles = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/myRoles?`$filter=tenantId in ('$($TenantIds -join "','")')" + try { + $MyRoles = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/myRoles?`$filter=tenantId in ('$($TenantIds -join "','")')" + } + catch { + $MyRoles = $null + } $results = foreach ($tenant in $Tenants) { - $AddedText = '' - try { - $TenantId = ($TenantList | Where-Object { $_.defaultDomainName -eq $tenant }).customerId - $Assignments = ($MyRoles | Where-Object { $_.tenantId -eq $TenantId }).assignments - $SAMUserRoles = ($Assignments | Where-Object { $_.assignmentType -eq 'granularDelegatedAdminPrivileges' }).roles - - $BulkRequests = $ExpectedRoles | ForEach-Object { @( - @{ - id = "roleManagement_$($_.id)" - method = 'GET' - url = "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($_.id)'&`$expand=principal" - } - ) - } - $GDAPRolesGraph = New-GraphBulkRequest -tenantid $tenant -Requests $BulkRequests - $GDAPRoles = [System.Collections.Generic.List[object]]::new() - $MissingRoles = [System.Collections.Generic.List[object]]::new() - foreach ($RoleId in $ExpectedRoles) { - $GraphRole = $GDAPRolesGraph.body.value | Where-Object -Property roleDefinitionId -EQ $RoleId.Id - $Role = $GraphRole.principal | Where-Object -Property organizationId -EQ $ENV:tenantid - $SAMRole = $SAMUserRoles | Where-Object -Property templateId -EQ $RoleId.Id - if (!$Role) { - $MissingRoles.Add( - [PSCustomObject]@{ - Name = $RoleId.Name - Type = 'Tenant' - } - ) - $AddedText = 'but missing GDAP roles' - } else { - $GDAPRoles.Add([PSCustomObject]$RoleId) - } - if (!$SAMRole) { - $MissingRoles.Add( - [PSCustomObject]@{ - Name = $RoleId.Name - Type = 'SAM User' - } - ) - $AddedText = 'but missing GDAP roles' - } - } - if (!($MissingRoles | Measure-Object).Count -gt 0) { - $MissingRoles = $true - } - @{ - TenantName = "$($Tenant)" - Status = "Successfully connected $($AddedText)" - GDAPRoles = $GDAPRoles - MissingRoles = $MissingRoles - SAMUserRoles = $SAMUserRoles - } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message 'Tenant access check executed successfully' -Sev 'Info' - - } catch { - @{ - TenantName = "$($tenant)" - Status = "Failed to connect: $(Get-NormalizedError -message $_.Exception.Message)" - GDAP = '' - } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check failed: $(Get-NormalizedError -message $_) " -Sev 'Error' - - } - - try { - $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop - @{ - TenantName = "$($Tenant)" - Status = 'Successfully connected to Exchange' - } - - } catch { - $ReportedError = ($_.ErrorDetails | ConvertFrom-Json -ErrorAction SilentlyContinue) - $Message = if ($ReportedError.error.details.message) { $ReportedError.error.details.message } else { $ReportedError.error.innererror.internalException.message } - if ($null -eq $Message) { $Message = $($_.Exception.Message) } - @{ - TenantName = "$($Tenant)" - Status = "Failed to connect to Exchange: $(Get-NormalizedError -message $Message)" - } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check for Exchange failed: $(Get-NormalizedError -message $Message) " -Sev 'Error' - } + Test-CIPPTenantAccess -TenantFilter $Tenant -Myroles $Myroles -ExpectedRoles $ExpectedRoles } if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } } +if ($Request.query.GDAP -eq 'true') { + $Results = Test-CIPPGDAPConnection +} $body = [pscustomobject]@{'Results' = $Results } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 new file mode 100644 index 000000000000..ac202ac4a517 --- /dev/null +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -0,0 +1,82 @@ +function Test-CIPPTenantAccess { + [CmdletBinding()] + param ( + $TenantFilter, + $APIName = "Access Check", + $ExecutingUser + ) + + $GDAPissues = [System.Collections.ArrayList]@() + try { + #Get graph request to list all relationships. + $Relationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active'" -tenantid $ENV:TenantID -NoAuthCheck $true + #Group relationships by tenant. The tenant information is in $relationships.customer.TenantId. + $RelationshipsByTenant = $Relationships | Group-Object -Property { $_.customer.TenantId } + foreach ($Tenant in $RelationshipsByTenant) { + if ($Tenant.Group.displayName.count -le 1 -and $Tenant.Group.displayName -like 'MLT_*') { + $GDAPissues.add([PSCustomObject]@{ + Type = "Error" + Issue = "This tenant only has a MLT(Microsoft Led Transition) relationship. This is a read-only relationship. You must migrate this tenant to GDAP." + Tenant = $Tenant.Group.customer.displayName + Relationship = $Tenant.Group.displayName + }) | Out-Null + } + foreach ($Group in $Tenant.Group) { + if ("62e90394-69f5-4237-9190-012177145e10" -in $Group.accessDetails) { + $GDAPissues.add([PSCustomObject]@{ + Type = "Warning" + Issue = "The relationship for $($Tenant.Group.customer.displayName) has global administrator access. This relationship is not available for auto-extend." + Tenant = $Tenant.Group.customer.displayName + Relationship = $group.displayName + }) | Out-Null + } + } + + } + $CIPPGroupCount = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/`$count?`$filter=startsWith(displayName,'M365 GDAP')" -NoAuthCheck $true -ComplexFilter + $SAMUserMemberships = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/me/memberOf?$select=id,displayName,isAssignableToRole' -NoAuthCheck $true + $ExpectedGroups = @( + 'AdminAgents', + 'M365 GDAP Application Administrator', + 'M365 GDAP User Administrator', + 'M365 GDAP Intune Administrator', + 'M365 GDAP Exchange Administrator', + 'M365 GDAP Security Administrator', + 'M365 GDAP Cloud App Security Administrator', + 'M365 GDAP Cloud Device Administrator', + 'M365 GDAP Teams Administrator', + 'M365 GDAP Sharepoint Administrator', + 'M365 GDAP Authentication Policy Administrator', + 'M365 GDAP Privileged Role Administrator', + 'M365 GDAP Privileged Authentication Administrator' + ) + $RoleAssignableGroups = $SAMUserMemberships | Where-Object { $_.isAssignableToRole } + $NestedGroups = foreach ($Group in $RoleAssignableGroups) { + New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($Group.id)/memberOf?`$select=id,displayName" -NoAuthCheck $true + } + foreach ($Group in $ExpectedGroups) { + $GroupFound = $false + foreach ($Membership in ($SAMUserMemberships + $NestedGroups)) { + if ($Membership.displayName -match $Group -and (($CIPPGroupCount -gt 0 -and $Group -match 'M365 GDAP') -or $Group -notmatch 'M365 GDAP')) { + $GroupFound = $true + } + } + if (-not $GroupFound) { + $GDAPissues.add([PSCustomObject]@{ + Type = "Warning" + Issue = "$($Group) cannot be found in your tenant. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." + Tenant = "*Partner Tenant" + Relationship = "None" + }) | Out-Null + } + } + + } + catch { + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Failed to run GDAP check for $($TenantFilter): $($_.Exception.Message)" -Sev "Error" + } + + return [PSCustomObject]@{ + GDAPIssues = @($GDAPissues) + } +} From e6ef8c734a8f74f3197fdc1d0a549e4b0ee24e47 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 15:53:25 +0100 Subject: [PATCH 12/20] improved access checks --- ExecAccessChecks/run.ps1 | 188 +----------------- .../Public/PermissionsTranslator.json | 9 + .../Public/Test-CIPPAccessPermissions.ps1 | 131 ++++++++++++ .../CIPPCore/Public/Test-CIPPAccessTenant.ps1 | 118 +++++++++++ .../Public/Test-CIPPGDAPRelationships.ps1 | 7 +- 5 files changed, 267 insertions(+), 186 deletions(-) create mode 100644 Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 create mode 100644 Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 diff --git a/ExecAccessChecks/run.ps1 b/ExecAccessChecks/run.ps1 index d9dd4990af89..0cc71645208e 100644 --- a/ExecAccessChecks/run.ps1 +++ b/ExecAccessChecks/run.ps1 @@ -10,196 +10,16 @@ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -m # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' if ($Request.query.Permissions -eq 'true') { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Started permissions check' -Sev 'Debug' - $Messages = [System.Collections.Generic.List[string]]::new() - $MissingPermissions = [System.Collections.Generic.List[string]]::new() - $Links = [System.Collections.Generic.List[object]]::new() - $AccessTokenDetails = [PSCustomObject]@{ - AppId = '' - AppName = '' - Audience = '' - AuthMethods = '' - IPAddress = '' - Name = '' - Scope = '' - TenantId = '' - UserPrincipalName = '' - } - $Success = $true - try { - Set-Location (Get-Item $PSScriptRoot).Parent.FullName - $ExpectedPermissions = Get-Content '.\Cache_SAMSetup\SAMManifest.json' | ConvertFrom-Json - - $GraphToken = Get-GraphToken -returnRefresh $true - if ($GraphToken) { - $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true - } - if ($env:MSI_SECRET) { - try { - Disable-AzContextAutosave -Scope Process | Out-Null - $AzSession = Connect-AzAccount -Identity - - $KV = $ENV:WEBSITE_DEPLOYMENT_ID - $KeyVaultRefresh = Get-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -AsPlainText - if ($ENV:RefreshToken -ne $KeyVaultRefresh) { - $Success = $false - $Messages.Add('Your refresh token does not match key vault, clear your cache or wait 30 minutes.') | Out-Null - $Links.Add([PSCustomObject]@{ - Text = 'Clear Token Cache' - Href = 'https://docs.cipp.app/setup/installation/cleartokencache' - } - ) | Out-Null - } - else { - $Messages.Add('Your refresh token matches key vault.') | Out-Null - } - } - catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' - } - } - - try { - $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue - } - catch { - $AccessTokenDetails = [PSCustomObject]@{ - Name = '' - AuthMethods = @() - } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Token exception: $($_) " -Sev 'Error' - $Success = $false - } - - if ($AccessTokenDetails.Name -eq '') { - $Messages.Add('Your refresh token is invalid, check for line breaks or missing characters.') | Out-Null - $Success = $false - } - else { - if ($AccessTokenDetails.AuthMethods -contains 'mfa') { - $Messages.Add('Your access token contains the MFA claim.') | Out-Null - } - else { - $Messages.Add('Your access token does not contain the MFA claim, Refresh your SAM tokens.') | Out-Null - $Success = $false - $Links.Add([PSCustomObject]@{ - Text = 'MFA Troubleshooting' - Href = 'https://docs.cipp.app/troubleshooting/troubleshooting#multi-factor-authentication-troubleshooting' - } - ) | Out-Null - } - } - - $MissingPermissions = $ExpectedPermissions.requiredResourceAccess.ResourceAccess.id | Where-Object { $_ -notin $GraphPermissions.requiredResourceAccess.ResourceAccess.id } - if ($MissingPermissions) { - $Translator = Get-Content '.\Cache_SAMSetup\PermissionsTranslator.json' | ConvertFrom-Json - $TranslatedPermissions = $Translator | Where-Object id -In $MissingPermissions | ForEach-Object { "$($_.value) - $($_.Origin)" } - $MissingPermissions = @($TranslatedPermissions) - $Success = $false - $Links.Add([PSCustomObject]@{ - Text = 'Permissions' - Href = 'https://docs.cipp.app/setup/installation/permissions' - } - ) | Out-Null - } - else { - $Messages.Add('Your Secure Application Model has all required permissions') | Out-Null - } - $CIPPGroupCount = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/`$count?`$filter=startsWith(displayName,'M365 GDAP')" -NoAuthCheck $true -ComplexFilter - $SAMUserMemberships = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/me/memberOf?$select=id,displayName,isAssignableToRole' -NoAuthCheck $true - $ExpectedGroups = @( - 'AdminAgents', - 'M365 GDAP Application Administrator', - 'M365 GDAP User Administrator', - 'M365 GDAP Intune Administrator', - 'M365 GDAP Exchange Administrator', - 'M365 GDAP Security Administrator', - 'M365 GDAP Cloud App Security Administrator', - 'M365 GDAP Cloud Device Administrator', - 'M365 GDAP Teams Administrator', - 'M365 GDAP Sharepoint Administrator', - 'M365 GDAP Authentication Policy Administrator', - 'M365 GDAP Privileged Role Administrator', - 'M365 GDAP Privileged Authentication Administrator' - ) - $RoleAssignableGroups = $SAMUserMemberships | Where-Object { $_.isAssignableToRole } - $NestedGroups = foreach ($Group in $RoleAssignableGroups) { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($Group.id)/memberOf?`$select=id,displayName" -NoAuthCheck $true - } - - $MissingGroups = [System.Collections.Generic.List[string]]::new() - foreach ($Group in $ExpectedGroups) { - $GroupFound = $false - foreach ($Membership in ($SAMUserMemberships + $NestedGroups)) { - if ($Membership.displayName -match $Group -and (($CIPPGroupCount -gt 0 -and $Group -match 'M365 GDAP') -or $Group -notmatch 'M365 GDAP')) { - $GroupFound = $true - } - } - if (-not $GroupFound) { - $MissingGroups.Add($Group) - } - } - if (($MissingGroups | Measure-Object).Count -eq 0) { - $Messages.Add('The SAM user has all the required groups') - } - else { - $Success = $false - } - } - catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' - $Messages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null - $Success = $false - } - - $Results = [PSCustomObject]@{ - AccessTokenDetails = $AccessTokenDetails - Messages = @($Messages) - MissingPermissions = @($MissingPermissions) - MissingGroups = @($MissingGroups) - Memberships = @($SAMUserMemberships) - CIPPGroupCount = $CIPPGroupCount - Links = @($Links) - Success = $Success - } + $Results = Test-CIPPAccessPermissions -tenantfilter $ENV:tenantid -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' } if ($Request.query.Tenants -eq 'true') { - $ExpectedRoles = @( - @{ Name = 'Application Administrator'; Id = '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3' }, - @{ Name = 'User Administrator'; Id = 'fe930be7-5e62-47db-91af-98c3a49a38b1' }, - @{ Name = 'Intune Administrator'; Id = '3a2c62db-5318-420d-8d74-23affee5d9d5' }, - @{ Name = 'Exchange Administrator'; Id = '29232cdf-9323-42fd-ade2-1d097af3e4de' }, - @{ Name = 'Security Administrator'; Id = '194ae4cb-b126-40b2-bd5b-6091b380977d' }, - @{ Name = 'Cloud App Security Administrator'; Id = '892c5842-a9a6-463a-8041-72aa08ca3cf6' }, - @{ Name = 'Cloud Device Administrator'; Id = '7698a772-787b-4ac8-901f-60d6b08affd2' }, - @{ Name = 'Teams Administrator'; Id = '69091246-20e8-4a56-aa4d-066075b2a7a8' }, - @{ Name = 'Sharepoint Administrator'; Id = 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' }, - @{ Name = 'Authentication Policy Administrator'; Id = '0526716b-113d-4c15-b2c8-68e3c22b9f80' }, - @{ Name = 'Privileged Role Administrator'; Id = 'e8611ab8-c189-46e8-94e1-60213ab1f814' }, - @{ Name = 'Privileged Authentication Administrator'; Id = '7be44c8a-adaf-4e2a-84d6-ab2649e08a13' } - ) - $Tenants = ($Request.body.tenantid).split(',') - if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } - $TenantList = Get-Tenants - $TenantIds = foreach ($Tenant in $Tenants) { - ($TenantList | Where-Object { $_.defaultDomainName -eq $Tenant }).customerId - } - try { - $MyRoles = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/myRoles?`$filter=tenantId in ('$($TenantIds -join "','")')" - } - catch { - $MyRoles = $null - } - $results = foreach ($tenant in $Tenants) { - Test-CIPPTenantAccess -TenantFilter $Tenant -Myroles $Myroles -ExpectedRoles $ExpectedRoles - } - if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } + $Results = Test-CIPPAccessTenant -Tenantcsv $Request.body.TenantId } - if ($Request.query.GDAP -eq 'true') { - $Results = Test-CIPPGDAPConnection + $Results = Test-CIPPGDAPRelationships } + $body = [pscustomobject]@{'Results' = $Results } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json index a38d0786d5f1..a0ba05d3dc02 100644 --- a/Modules/CIPPCore/Public/PermissionsTranslator.json +++ b/Modules/CIPPCore/Public/PermissionsTranslator.json @@ -5312,5 +5312,14 @@ "userConsentDescription": "Access Microsoft Teams and Skype for Business data as the signed in user", "userConsentDisplayName": "Access Microsoft Teams and Skype for Business data based on the user's role membership", "value": "user_impersonation" + }, + { + "description": "Read and write all on-premises directory synchronization information", + "displayName": "Read and write all on-premises directory synchronization information", + "id": "c2d95988-7604-4ba1-aaed-38a5f82a51c7", + "Origin": "Delegated", + "userConsentDescription": "Access Microsoft Teams and Skype for Business data as the signed in user", + "userConsentDisplayName": "Access Microsoft Teams and Skype for Business data based on the user's role membership", + "value": "OnPremDirectorySynchronization.ReadWrite.All" } ] diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 new file mode 100644 index 000000000000..971753f68d40 --- /dev/null +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -0,0 +1,131 @@ +function Test-CIPPAccessPermissions { + [CmdletBinding()] + param ( + $TenantFilter, + $APIName = "Access Check", + $ExecutingUser + ) + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Started permissions check' -Sev 'Debug' + $Messages = [System.Collections.Generic.List[string]]::new() + $MissingPermissions = [System.Collections.Generic.List[string]]::new() + $Links = [System.Collections.Generic.List[object]]::new() + $AccessTokenDetails = [PSCustomObject]@{ + AppId = '' + AppName = '' + Audience = '' + AuthMethods = '' + IPAddress = '' + Name = '' + Scope = '' + TenantId = '' + UserPrincipalName = '' + } + Write-Host "Setting success to true by default." + $Success = $true + try { + Set-Location (Get-Item $PSScriptRoot).FullName + $ExpectedPermissions = Get-Content '.\SAMManifest.json' | ConvertFrom-Json + + $GraphToken = Get-GraphToken -returnRefresh $true + if ($GraphToken) { + $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true + } + if ($env:MSI_SECRET) { + try { + Disable-AzContextAutosave -Scope Process | Out-Null + $AzSession = Connect-AzAccount -Identity + + $KV = $ENV:WEBSITE_DEPLOYMENT_ID + $KeyVaultRefresh = Get-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -AsPlainText + if ($ENV:RefreshToken -ne $KeyVaultRefresh) { + Write-Host "Setting success to false due to nonmaching token." + + $Success = $false + $Messages.Add('Your refresh token does not match key vault, clear your cache or wait 30 minutes.') | Out-Null + $Links.Add([PSCustomObject]@{ + Text = 'Clear Token Cache' + Href = 'https://docs.cipp.app/setup/installation/cleartokencache' + } + ) | Out-Null + } + else { + $Messages.Add('Your refresh token matches key vault.') | Out-Null + } + } + catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' + } + } + + try { + $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue + } + catch { + $AccessTokenDetails = [PSCustomObject]@{ + Name = '' + AuthMethods = @() + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Token exception: $($_) " -Sev 'Error' + $Success = $false + Write-Host "Setting success to false due to not able to decode token." + + } + + if ($AccessTokenDetails.Name -eq '') { + $Messages.Add('Your refresh token is invalid, check for line breaks or missing characters.') | Out-Null + Write-Host "Setting success to false invalid token." + + $Success = $false + } + else { + if ($AccessTokenDetails.AuthMethods -contains 'mfa') { + $Messages.Add('Your access token contains the MFA claim.') | Out-Null + } + else { + $Messages.Add('Your access token does not contain the MFA claim, Refresh your SAM tokens.') | Out-Null + Write-Host "Setting success to False due to invalid list of claims." + + $Success = $false + $Links.Add([PSCustomObject]@{ + Text = 'MFA Troubleshooting' + Href = 'https://docs.cipp.app/troubleshooting/troubleshooting#multi-factor-authentication-troubleshooting' + } + ) | Out-Null + } + } + + $MissingPermissions = $ExpectedPermissions.requiredResourceAccess.ResourceAccess.id | Where-Object { $_ -notin $GraphPermissions.requiredResourceAccess.ResourceAccess.id } + if ($MissingPermissions) { + Write-Host "Setting success to False due to permissions issues: $($MissingPermissions | ConvertTo-Json)" + + $Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json + $TranslatedPermissions = $Translator | Where-Object id -In $MissingPermissions | ForEach-Object { "$($_.value) - $($_.Origin)" } + $MissingPermissions = @($TranslatedPermissions) + $Success = $false + $Links.Add([PSCustomObject]@{ + Text = 'Permissions' + Href = 'https://docs.cipp.app/setup/installation/permissions' + } + ) | Out-Null + } + else { + $Messages.Add('Your Secure Application Model has all required permissions') | Out-Null + } + + } + catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' + $Messages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null + Write-Host "Setting success to False due to not being able to connect." + + $Success = $false + } + + return [PSCustomObject]@{ + AccessTokenDetails = $AccessTokenDetails + Messages = @($Messages) + MissingPermissions = @($MissingPermissions) + Links = @($Links) + Success = $Success + } +} diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 new file mode 100644 index 000000000000..677b642e24c0 --- /dev/null +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -0,0 +1,118 @@ +function Test-CIPPAccessTenant { + [CmdletBinding()] + param ( + $TenantCSV, + $APIName = "Access Check", + $ExecutingUser + ) + $ExpectedRoles = @( + @{ Name = 'Application Administrator'; Id = '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3' }, + @{ Name = 'User Administrator'; Id = 'fe930be7-5e62-47db-91af-98c3a49a38b1' }, + @{ Name = 'Intune Administrator'; Id = '3a2c62db-5318-420d-8d74-23affee5d9d5' }, + @{ Name = 'Exchange Administrator'; Id = '29232cdf-9323-42fd-ade2-1d097af3e4de' }, + @{ Name = 'Security Administrator'; Id = '194ae4cb-b126-40b2-bd5b-6091b380977d' }, + @{ Name = 'Cloud App Security Administrator'; Id = '892c5842-a9a6-463a-8041-72aa08ca3cf6' }, + @{ Name = 'Cloud Device Administrator'; Id = '7698a772-787b-4ac8-901f-60d6b08affd2' }, + @{ Name = 'Teams Administrator'; Id = '69091246-20e8-4a56-aa4d-066075b2a7a8' }, + @{ Name = 'Sharepoint Administrator'; Id = 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' }, + @{ Name = 'Authentication Policy Administrator'; Id = '0526716b-113d-4c15-b2c8-68e3c22b9f80' }, + @{ Name = 'Privileged Role Administrator'; Id = 'e8611ab8-c189-46e8-94e1-60213ab1f814' }, + @{ Name = 'Privileged Authentication Administrator'; Id = '7be44c8a-adaf-4e2a-84d6-ab2649e08a13' } + ) + $Tenants = ($TenantCSV).split(',') + if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } + $TenantList = Get-Tenants + $TenantIds = foreach ($Tenant in $Tenants) { + ($TenantList | Where-Object { $_.defaultDomainName -eq $Tenant }).customerId + } + $MyRoles = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/myRoles?`$filter=tenantId in ('$($TenantIds -join "','")')" + $results = foreach ($tenant in $Tenants) { + $AddedText = '' + try { + $TenantId = ($TenantList | Where-Object { $_.defaultDomainName -eq $tenant }).customerId + $Assignments = ($MyRoles | Where-Object { $_.tenantId -eq $TenantId }).assignments + $SAMUserRoles = ($Assignments | Where-Object { $_.assignmentType -eq 'granularDelegatedAdminPrivileges' }).roles + + $BulkRequests = $ExpectedRoles | ForEach-Object { @( + @{ + id = "roleManagement_$($_.id)" + method = 'GET' + url = "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($_.id)'&`$expand=principal" + } + ) + } + $GDAPRolesGraph = New-GraphBulkRequest -tenantid $tenant -Requests $BulkRequests + $GDAPRoles = [System.Collections.Generic.List[object]]::new() + $MissingRoles = [System.Collections.Generic.List[object]]::new() + foreach ($RoleId in $ExpectedRoles) { + $GraphRole = $GDAPRolesGraph.body.value | Where-Object -Property roleDefinitionId -EQ $RoleId.Id + $Role = $GraphRole.principal | Where-Object -Property organizationId -EQ $ENV:tenantid + $SAMRole = $SAMUserRoles | Where-Object -Property templateId -EQ $RoleId.Id + if (!$Role) { + $MissingRoles.Add( + [PSCustomObject]@{ + Name = $RoleId.Name + Type = 'Tenant' + } + ) + $AddedText = 'but missing GDAP roles' + } + else { + $GDAPRoles.Add([PSCustomObject]$RoleId) + } + if (!$SAMRole) { + $MissingRoles.Add( + [PSCustomObject]@{ + Name = $RoleId.Name + Type = 'SAM User' + } + ) + $AddedText = 'but missing GDAP roles' + } + } + if (!($MissingRoles | Measure-Object).Count -gt 0) { + $MissingRoles = $true + } + @{ + TenantName = "$($Tenant)" + Status = "Successfully connected $($AddedText)" + GDAPRoles = $GDAPRoles + MissingRoles = $MissingRoles + SAMUserRoles = $SAMUserRoles + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message 'Tenant access check executed successfully' -Sev 'Info' + + } + catch { + @{ + TenantName = "$($tenant)" + Status = "Failed to connect: $(Get-NormalizedError -message $_.Exception.Message)" + GDAP = '' + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check failed: $(Get-NormalizedError -message $_) " -Sev 'Error' + + } + + try { + $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop + @{ + TenantName = "$($Tenant)" + Status = 'Successfully connected to Exchange' + } + + } + catch { + $ReportedError = ($_.ErrorDetails | ConvertFrom-Json -ErrorAction SilentlyContinue) + $Message = if ($ReportedError.error.details.message) { $ReportedError.error.details.message } else { $ReportedError.error.innererror.internalException.message } + if ($null -eq $Message) { $Message = $($_.Exception.Message) } + @{ + TenantName = "$($Tenant)" + Status = "Failed to connect to Exchange: $(Get-NormalizedError -message $Message)" + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check for Exchange failed: $(Get-NormalizedError -message $Message) " -Sev 'Error' + } + } + if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } + + return $results +} diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index ac202ac4a517..253cf0fd79a2 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -1,4 +1,4 @@ -function Test-CIPPTenantAccess { +function Test-CIPPGDAPRelationships { [CmdletBinding()] param ( $TenantFilter, @@ -77,6 +77,9 @@ function Test-CIPPTenantAccess { } return [PSCustomObject]@{ - GDAPIssues = @($GDAPissues) + GDAPIssues = @($GDAPissues) + MissingGroups = @($MissingGroups) + Memberships = @($SAMUserMemberships) + CIPPGroupCount = $CIPPGroupCount } } From e25eb591e66f3624488f94332c4bfbafb6e6555a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 17:53:11 +0100 Subject: [PATCH 13/20] adding links --- Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 253cf0fd79a2..55b1e2d8df1e 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -19,6 +19,7 @@ function Test-CIPPGDAPRelationships { Issue = "This tenant only has a MLT(Microsoft Led Transition) relationship. This is a read-only relationship. You must migrate this tenant to GDAP." Tenant = $Tenant.Group.customer.displayName Relationship = $Tenant.Group.displayName + Link = "https://docs.cipp.app/setup/gdap/index" }) | Out-Null } foreach ($Group in $Tenant.Group) { @@ -28,6 +29,8 @@ function Test-CIPPGDAPRelationships { Issue = "The relationship for $($Tenant.Group.customer.displayName) has global administrator access. This relationship is not available for auto-extend." Tenant = $Tenant.Group.customer.displayName Relationship = $group.displayName + Link = "https://docs.cipp.app/setup/gdap/troubleshooting#autoextend" + }) | Out-Null } } @@ -67,6 +70,8 @@ function Test-CIPPGDAPRelationships { Issue = "$($Group) cannot be found in your tenant. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." Tenant = "*Partner Tenant" Relationship = "None" + Link = "https://docs.cipp.app/setup/gdap/troubleshooting#groups" + }) | Out-Null } } From d5c7e98e8ede7ecafb7d77dd13ef425c753d0a2e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 18:17:20 +0100 Subject: [PATCH 14/20] added <12 group report --- .../CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 55b1e2d8df1e..6d4e3af7dac1 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -36,6 +36,7 @@ function Test-CIPPGDAPRelationships { } } + $me = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/me?$select=UserPrincipalName' -NoAuthCheck $true).UserPrincipalName $CIPPGroupCount = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/`$count?`$filter=startsWith(displayName,'M365 GDAP')" -NoAuthCheck $true -ComplexFilter $SAMUserMemberships = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/me/memberOf?$select=id,displayName,isAssignableToRole' -NoAuthCheck $true $ExpectedGroups = @( @@ -67,7 +68,17 @@ function Test-CIPPGDAPRelationships { if (-not $GroupFound) { $GDAPissues.add([PSCustomObject]@{ Type = "Warning" - Issue = "$($Group) cannot be found in your tenant. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." + Issue = "$($Group) is not assigned to the SAM user $me. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." + Tenant = "*Partner Tenant" + Relationship = "None" + Link = "https://docs.cipp.app/setup/gdap/troubleshooting#groups" + + }) | Out-Null + } + if ($CIPPGroupCount -lt 12) { + $GDAPissues.add([PSCustomObject]@{ + Type = "Warning" + Issue = "We could not find all 12 recommended CIPP groups. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." Tenant = "*Partner Tenant" Relationship = "None" Link = "https://docs.cipp.app/setup/gdap/troubleshooting#groups" From a3df5735d0d49253e7a8c1cb4d291ab3aefb2b45 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 18:18:06 +0100 Subject: [PATCH 15/20] added count --- Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 6d4e3af7dac1..d1e2da39827e 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -78,7 +78,7 @@ function Test-CIPPGDAPRelationships { if ($CIPPGroupCount -lt 12) { $GDAPissues.add([PSCustomObject]@{ Type = "Warning" - Issue = "We could not find all 12 recommended CIPP groups. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." + Issue = "We only found $($CIPPGroupCount) of the 12 required groups. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." Tenant = "*Partner Tenant" Relationship = "None" Link = "https://docs.cipp.app/setup/gdap/troubleshooting#groups" From 30eebcc562ee2c6d86bdbfedf2964247ab7526b4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 18:33:37 +0100 Subject: [PATCH 16/20] fixed bug --- Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index d1e2da39827e..3501b7dae8ab 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -23,7 +23,7 @@ function Test-CIPPGDAPRelationships { }) | Out-Null } foreach ($Group in $Tenant.Group) { - if ("62e90394-69f5-4237-9190-012177145e10" -in $Group.accessDetails) { + if ("62e90394-69f5-4237-9190-012177145e10" -in $Group.accessDetails.unifiedRoles.roleDefinitionId) { $GDAPissues.add([PSCustomObject]@{ Type = "Warning" Issue = "The relationship for $($Tenant.Group.customer.displayName) has global administrator access. This relationship is not available for auto-extend." From 2d55e5cd6fe02405748ccf81c299f86c8ab9a452 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 18:39:57 +0100 Subject: [PATCH 17/20] design changes --- Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 3501b7dae8ab..65ae1519c540 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -26,9 +26,9 @@ function Test-CIPPGDAPRelationships { if ("62e90394-69f5-4237-9190-012177145e10" -in $Group.accessDetails.unifiedRoles.roleDefinitionId) { $GDAPissues.add([PSCustomObject]@{ Type = "Warning" - Issue = "The relationship for $($Tenant.Group.customer.displayName) has global administrator access. This relationship is not available for auto-extend." - Tenant = $Tenant.Group.customer.displayName - Relationship = $group.displayName + Issue = "The relationship has global administrator access. This relationship is not available for Auto-Extend." + Tenant = $Tenant.Group.customer.displayName | Out-String + Relationship = $group.displayName | Out-String Link = "https://docs.cipp.app/setup/gdap/troubleshooting#autoextend" }) | Out-Null From 7458433c7d56d0e299fcb433422bd23025927cad Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 18:44:49 +0100 Subject: [PATCH 18/20] minor text change --- Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 65ae1519c540..cf2bacb49e68 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -26,7 +26,7 @@ function Test-CIPPGDAPRelationships { if ("62e90394-69f5-4237-9190-012177145e10" -in $Group.accessDetails.unifiedRoles.roleDefinitionId) { $GDAPissues.add([PSCustomObject]@{ Type = "Warning" - Issue = "The relationship has global administrator access. This relationship is not available for Auto-Extend." + Issue = "The relationship has global administrator access. Auto-Extend is not available." Tenant = $Tenant.Group.customer.displayName | Out-String Relationship = $group.displayName | Out-String Link = "https://docs.cipp.app/setup/gdap/troubleshooting#autoextend" From 4bbd61837d3db9a2216c6214795739290f8717b3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 3 Nov 2023 13:49:30 -0400 Subject: [PATCH 19/20] Mailbox Restores --- ExecMailboxRestore/function.json | 18 +++++++ ListMailboxRestores/function.json | 18 +++++++ ListMailboxes/run.ps1 | 29 ++++++---- .../Entrypoints/Invoke-ExecMailboxRestore.ps1 | 54 +++++++++++++++++++ .../Invoke-ListMailboxRestores.ps1 | 43 +++++++++++++++ 5 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 ExecMailboxRestore/function.json create mode 100644 ListMailboxRestores/function.json create mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailboxRestore.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRestores.ps1 diff --git a/ExecMailboxRestore/function.json b/ExecMailboxRestore/function.json new file mode 100644 index 000000000000..bf6c3ef0c49a --- /dev/null +++ b/ExecMailboxRestore/function.json @@ -0,0 +1,18 @@ +{ + "scriptFile": "../Modules/CippEntryPoints/CippEntryPoints.psm1", + "entryPoint": "Receive-CippHttpTrigger", + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": ["get", "post"] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} diff --git a/ListMailboxRestores/function.json b/ListMailboxRestores/function.json new file mode 100644 index 000000000000..bf6c3ef0c49a --- /dev/null +++ b/ListMailboxRestores/function.json @@ -0,0 +1,18 @@ +{ + "scriptFile": "../Modules/CippEntryPoints/CippEntryPoints.psm1", + "entryPoint": "Receive-CippHttpTrigger", + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": ["get", "post"] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} diff --git a/ListMailboxes/run.ps1 b/ListMailboxes/run.ps1 index c492cf59fcec..8f617d95006b 100644 --- a/ListMailboxes/run.ps1 +++ b/ListMailboxes/run.ps1 @@ -4,33 +4,44 @@ using namespace System.Net param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName -Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" +Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' # Write to the Azure Functions log stream. -Write-Host "PowerShell HTTP trigger function processed a request." +Write-Host 'PowerShell HTTP trigger function processed a request.' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter try { $users = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/?`$top=999&`$select=id,userPrincipalName,assignedLicenses" -Tenantid $tenantfilter - $GraphRequest = (New-ExoRequest -tenantid $TenantFilter -cmdlet "Get-mailbox") | Select-Object id, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, + + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'Get-Mailbox' + } + + if ([bool]$Request.Query.SoftDeletedMailbox -eq $true) { + $ExoRequest.cmdParams = @{ SoftDeletedMailbox = $true } + } + + Write-Host ($ExoRequest | ConvertTo-Json) + + $GraphRequest = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, - @{ Name = 'SharedMailboxWithLicense'; Expression = { + @{ Name = 'SharedMailboxWithLicense'; Expression = { $ID = $_.id $Shared = if ($_.'RecipientTypeDetails' -eq 'SharedMailbox') { $true } else { $false } - if (($users | Where-Object -Property ID -EQ $ID).assignedLicenses.skuid -and $Shared) { $true } else { $false } - } + if (($users | Where-Object -Property ID -EQ $ID).assignedLicenses.skuid -and $Shared) { $true } else { $false } + } }, @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } }, @{ Name = 'recipientType'; Expression = { $_.'RecipientType' } }, @{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } }, - @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ", " } } + @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } } $StatusCode = [HttpStatusCode]::OK -} -catch { +} catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message $StatusCode = [HttpStatusCode]::Forbidden $GraphRequest = $ErrorMessage diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailboxRestore.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailboxRestore.ps1 new file mode 100644 index 000000000000..f483d40ae7a7 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailboxRestore.ps1 @@ -0,0 +1,54 @@ +function Invoke-ExecMailboxRestore { + Param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.TenantFilter + $RequestName = $Request.Body.RequestName + $SourceMailbox = $Request.Body.SourceMailbox + $TargetMailbox = $Request.Body.TargetMailbox + + try { + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'New-MailboxRestoreRequest' + cmdParams = @{ + Name = $RequestName + SourceMailbox = $SourceMailbox + TargetMailbox = $TargetMailbox + AllowLegacyDNMismatch = $true + } + } + if ([bool]$Request.Body.AcceptLargeDataLoss -eq $true) { + $ExoRequest.cmdParams.AcceptLargeDataLoss = $true + } + if ([int]$Request.Body.BadItemLimit -gt 0) { + $ExoRequest.cmdParams.BadItemLimit = $Request.Body.BadItemLimit + } + if ([int]$Request.Body.LargeItemLimit -gt 0) { + $ExoRequest.cmdParams.LargeItemLimit = $Request.Body.LargeItemLimit + } + + $GraphRequest = New-ExoRequest @ExoRequest + + $Body = @{ + RestoreRequest = $GraphRequest + Results = @('Mailbox restore request started successfully') + } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::Forbidden + $Body = @{ + RestoreRequest = $null + Results = @($ErrorMessage) + } + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRestores.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRestores.ps1 new file mode 100644 index 000000000000..f47458585c09 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRestores.ps1 @@ -0,0 +1,43 @@ +function Invoke-ListMailboxRestores { + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.TenantFilter + try { + if ([bool]$Request.Query.Statistics -eq $true -and $Request.Query.Identity) { + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'Get-MailboxRestoreRequestStatistics' + cmdParams = @{ Identity = $Request.Query.Identity } + } + + if ([bool]$Request.Query.IncludeReport -eq $true) { + $ExoRequest.cmdParams.IncludeReport = $true + } + $GraphRequest = New-ExoRequest @ExoRequest + + } else { + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'Get-MailboxRestoreRequest' + } + + $RestoreRequests = (New-ExoRequest @ExoRequest) + $GraphRequest = $RestoreRequests + } + + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::Forbidden + $GraphRequest = $ErrorMessage + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @($GraphRequest) + }) +} From 1e787060775ce9c36da9198ba06344d7607aaf53 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 3 Nov 2023 19:01:35 +0100 Subject: [PATCH 20/20] upped to 4.5.5 --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index ae153944ee8b..50021202769b 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -4.5.0 \ No newline at end of file +4.5.5 \ No newline at end of file