diff --git a/Config/standards.json b/Config/standards.json index 92ef2e2806f5..dcc0335ddb71 100644 --- a/Config/standards.json +++ b/Config/standards.json @@ -1722,6 +1722,35 @@ "powershellEquivalent": "New-ProtectionAlert and Set-ProtectionAlert", "recommendedBy": [] }, + { + "name": "standards.SafeLinksTemplatePolicy", + "label": "SafeLinks Policy Template", + "cat": "Templates", + "multiple": false, + "disabledFeatures": { + "report": false, + "warn": false, + "remediate": false + }, + "impact": "Medium Impact", + "addedDate": "2025-04-29", + "helpText": "Deploy and manage SafeLinks policy templates to protect against malicious URLs in emails and Office documents.", + "addedComponent": [ + { + "type": "autoComplete", + "multiple": true, + "creatable": false, + "name": "standards.SafeLinksTemplatePolicy.TemplateIds", + "label": "Select SafeLinks Policy Templates", + "api": { + "url": "/api/ListSafeLinksPolicyTemplates", + "labelField": "TemplateName", + "valueField": "GUID", + "queryKey": "ListSafeLinksPolicyTemplates" + } + } + ] + }, { "name": "standards.SafeLinksPolicy", "cat": "Defender Standards", diff --git a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 index 455b2dcad9c4..1b6674d6e203 100644 --- a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 @@ -7,21 +7,22 @@ function Add-CIPPGroupMember( [string]$APIName = 'Add Group Member' ) { try { - if ($member -like '*#EXT#*') { $member = [System.Web.HttpUtility]::UrlEncode($member) } - $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id - $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" + if ($Member -like '*#EXT#*') { $Member = [System.Web.HttpUtility]::UrlEncode($Member) } + $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Member)" -tenantid $TenantFilter).id + $AddMemberBody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { - $Params = @{ Identity = $GroupId; Member = $member; BypassSecurityGroupManagerCheck = $true } - $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + $Params = @{ Identity = $GroupId; Member = $Member; BypassSecurityGroupManagerCheck = $true } + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $Params -UseSystemMailbox $true } else { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $addmemberbody -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $AddMemberBody -Verbose } - $Message = "Successfully added user $($Member) to $($GroupId)." - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -Sev 'Info' - return $message + $Results = "Successfully added user $($Member) to $($GroupId)." + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev 'Info' + return $Results } catch { - $message = "Failed to add user $($Member) to $($GroupId) - $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $message -Sev 'error' -LogData (Get-CippException -Exception $_) - return $message + $ErrorMessage = Get-CippException -Exception $_ + $Results = "Failed to add user $($Member) to $($GroupId) - $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev 'error' -LogData $ErrorMessage + throw $Results } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 index e2eefeff06ce..30e2515432c3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 @@ -12,14 +12,14 @@ Function Invoke-ExecModifyCalPerms { $APIName = $Request.Params.CIPPEndpoint Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' - + $Username = $request.body.userID $Tenantfilter = $request.body.tenantfilter $Permissions = $request.body.permissions Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing request for user: $Username, tenant: $Tenantfilter" -Sev 'Debug' - if ($username -eq $null) { + if ($username -eq $null) { Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Username is null' -Sev 'Error' $body = [pscustomobject]@{'Results' = @('Username is required') } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -28,12 +28,11 @@ Function Invoke-ExecModifyCalPerms { }) return } - + try { $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id Write-LogMessage -headers $Request.Headers -API $APINAME-message "Retrieved user ID: $userid" -Sev 'Debug' - } - catch { + } catch { Write-LogMessage -headers $Request.Headers -API $APINAME-message "Failed to get user ID: $($_.Exception.Message)" -Sev 'Error' $body = [pscustomobject]@{'Results' = @("Failed to get user ID: $($_.Exception.Message)") } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -50,8 +49,7 @@ Function Invoke-ExecModifyCalPerms { if ($Permissions -is [PSCustomObject]) { if ($Permissions.PSObject.Properties.Name -match '^\d+$') { $Permissions = $Permissions.PSObject.Properties.Value - } - else { + } else { $Permissions = @($Permissions) } } @@ -60,13 +58,14 @@ Function Invoke-ExecModifyCalPerms { foreach ($Permission in $Permissions) { Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing permission: $($Permission | ConvertTo-Json)" -Sev 'Debug' - + $PermissionLevel = $Permission.PermissionLevel.value ?? $Permission.PermissionLevel $Modification = $Permission.Modification $CanViewPrivateItems = $Permission.CanViewPrivateItems ?? $false - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Permission Level: $PermissionLevel, Modification: $Modification, CanViewPrivateItems: $CanViewPrivateItems" -Sev 'Debug' - + $FolderName = $Permission.FolderName ?? 'Calendar' + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Permission Level: $PermissionLevel, Modification: $Modification, CanViewPrivateItems: $CanViewPrivateItems, FolderName: $FolderName" -Sev 'Debug' + # Handle UserID as array or single value $TargetUsers = @($Permission.UserID | ForEach-Object { $_.value ?? $_ }) @@ -75,48 +74,24 @@ Function Invoke-ExecModifyCalPerms { foreach ($TargetUser in $TargetUsers) { try { Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing target user: $TargetUser" -Sev 'Debug' - - if ($Modification -eq 'Remove') { - try { - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxFolderPermission' -cmdParams @{ - Identity = "$($userid):\Calendar" - User = $TargetUser - Confirm = $false - } - $null = $results.Add("Removed $($TargetUser) from $($username) Calendar permissions") - } - catch { - $null = $results.Add("No existing permissions to remove for $($TargetUser)") - } - } - else { - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Setting permissions with AccessRights: $PermissionLevel" -Sev 'Debug' - - $cmdParams = @{ - Identity = "$($userid):\Calendar" - User = $TargetUser - AccessRights = $PermissionLevel - Confirm = $false - } - - if ($CanViewPrivateItems) { - $cmdParams['SharingPermissionFlags'] = 'Delegate,CanViewPrivateItems' - } - - try { - # Try Add first - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxFolderPermission' -cmdParams $cmdParams - $null = $results.Add("Granted $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") - } - catch { - # If Add fails, try Set - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-MailboxFolderPermission' -cmdParams $cmdParams - $null = $results.Add("Updated $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") - } + $Params = @{ + APIName = $APIName + Headers = $Request.Headers + RemoveAccess = if ($Modification -eq 'Remove') { $TargetUser } else { $null } + TenantFilter = $Tenantfilter + UserID = $userid + folderName = $FolderName + UserToGetPermissions = $TargetUser + LoggingName = $TargetUser + Permissions = $PermissionLevel + CanViewPrivateItems = $CanViewPrivateItems } + + $Result = Set-CIPPCalendarPermission @Params + + $null = $results.Add($Result) Write-LogMessage -headers $Request.Headers -API $APINAME-message "Successfully executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter - } - catch { + } catch { $HasErrors = $true Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") @@ -137,4 +112,4 @@ Function Invoke-ExecModifyCalPerms { StatusCode = if ($HasErrors) { [HttpStatusCode]::InternalServerError } else { [HttpStatusCode]::OK } Body = $Body }) -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 index 222c3561e579..cddc705a3556 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 @@ -12,13 +12,13 @@ Function Invoke-ExecModifyMBPerms { $APIName = $Request.Params.CIPPEndpoint Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' - + $Username = $request.body.userID $Tenantfilter = $request.body.tenantfilter $Permissions = $request.body.permissions if ($username -eq $null) { exit } - + $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id $Results = [System.Collections.ArrayList]::new() @@ -33,10 +33,18 @@ Function Invoke-ExecModifyMBPerms { } foreach ($Permission in $Permissions) { - $PermissionLevel = $Permission.PermissionLevel + $PermissionLevels = $Permission.PermissionLevel $Modification = $Permission.Modification $AutoMap = if ($Permission.PSObject.Properties.Name -contains 'AutoMap') { $Permission.AutoMap } else { $true } - + + # Handle multiple permission levels separated by commas + if ($PermissionLevels -like "*,*") { + $PermissionLevelArray = $PermissionLevels -split ',' | ForEach-Object { $_.Trim() } + } + else { + $PermissionLevelArray = @($PermissionLevels.Trim()) + } + # Handle UserID as array of objects or single value $TargetUsers = if ($Permission.UserID -is [array]) { $Permission.UserID | ForEach-Object { $_.value } @@ -46,79 +54,136 @@ Function Invoke-ExecModifyMBPerms { } foreach ($TargetUser in $TargetUsers) { - try { - switch ($PermissionLevel) { - 'FullAccess' { - if ($Modification -eq 'Remove') { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-mailboxpermission' -cmdParams @{ - Identity = $userid - user = $TargetUser - accessRights = @('FullAccess') - Confirm = $false + foreach ($PermissionLevel in $PermissionLevelArray) { + try { + switch ($PermissionLevel) { + 'FullAccess' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-mailboxpermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('FullAccess') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Shared Mailbox permissions (FullAccess)") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('FullAccess') + automapping = $AutoMap + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) Mailbox (FullAccess) with automapping set to $($AutoMap)") } - $null = $results.Add("Removed $($TargetUser) from $($username) Shared Mailbox permissions") } - else { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxPermission' -cmdParams @{ - Identity = $userid - user = $TargetUser - accessRights = @('FullAccess') - automapping = $AutoMap - Confirm = $false + 'SendAs' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-RecipientPermission' -cmdParams @{ + Identity = $userid + Trustee = $TargetUser + accessRights = @('SendAs') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) with Send As permissions") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-RecipientPermission' -cmdParams @{ + Identity = $userid + Trustee = $TargetUser + accessRights = @('SendAs') + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) with Send As permissions") } - $null = $results.Add("Granted $($TargetUser) access to $($username) Mailbox with automapping set to $($AutoMap)") } - } - 'SendAs' { - if ($Modification -eq 'Remove') { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-RecipientPermission' -cmdParams @{ - Identity = $userid - Trustee = $TargetUser - accessRights = @('SendAs') - Confirm = $false + 'SendOnBehalf' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ + Identity = $userid + GrantSendonBehalfTo = @{ + '@odata.type' = '#Exchange.GenericHashTable' + remove = $TargetUser + } + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Send on Behalf Permissions") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ + Identity = $userid + GrantSendonBehalfTo = @{ + '@odata.type' = '#Exchange.GenericHashTable' + add = $TargetUser + } + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) with Send On Behalf Permissions") } - $null = $results.Add("Removed $($TargetUser) from $($username) with Send As permissions") } - else { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-RecipientPermission' -cmdParams @{ - Identity = $userid - Trustee = $TargetUser - accessRights = @('SendAs') - Confirm = $false + 'ReadPermission' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('ReadPermission') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Read Permissions") } - $null = $results.Add("Granted $($TargetUser) access to $($username) with Send As permissions") } - } - 'SendOnBehalf' { - if ($Modification -eq 'Remove') { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ - Identity = $userid - GrantSendonBehalfTo = @{ - '@odata.type' = '#Exchange.GenericHashTable' - remove = $TargetUser + 'ExternalAccount' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('ExternalAccount') + Confirm = $false } - Confirm = $false + $null = $results.Add("Removed $($TargetUser) from $($username) Read Permissions") } - $null = $results.Add("Removed $($TargetUser) from $($username) Send on Behalf Permissions") } - else { - $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ - Identity = $userid - GrantSendonBehalfTo = @{ - '@odata.type' = '#Exchange.GenericHashTable' - add = $TargetUser + 'DeleteItem' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('DeleteItem') + Confirm = $false } - Confirm = $false + $null = $results.Add("Removed $($TargetUser) from $($username) Read Permissions") + } + } + 'ChangePermission' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('ChangePermission') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Read Permissions") + } + } + 'ChangeOwner' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('ChangeOwner') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Read Permissions") } - $null = $results.Add("Granted $($TargetUser) access to $($username) with Send On Behalf Permissions") } } + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter + } + catch { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Error' -tenant $TenantFilter + $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") } - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter - } - catch { - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Error' -tenant $TenantFilter - $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") } } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListSafeLinksFilters.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListSafeLinksFilters.ps1 deleted file mode 100644 index c9e395c05de7..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListSafeLinksFilters.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -function Invoke-ListSafeLinksFilters { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - Exchange.SpamFilter.Read - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $APIName = $Request.Params.CIPPEndpoint - $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter - $Policies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksPolicy' | Select-Object -Property * - $Rules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksRule' | Select-Object -Property * - - $Output = $Policies | Select-Object -Property *, - @{ Name = 'RuleName'; Expression = { foreach ($item in $Rules) { if ($item.SafeLinksPolicy -eq $_.Name) { $item.Name } } } }, - @{ Name = 'Priority'; Expression = { foreach ($item in $Rules) { if ($item.SafeLinksPolicy -eq $_.Name) { $item.Priority } } } }, - @{ Name = 'RecipientDomainIs'; Expression = { foreach ($item in $Rules) { if ($item.SafeLinksPolicy -eq $_.Name) { $item.RecipientDomainIs } } } }, - @{ Name = 'State'; Expression = { foreach ($item in $Rules) { if ($item.SafeLinksPolicy -eq $_.Name) { $item.State } } } } - - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Output - }) -} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-EditSafeLinksFilter.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-EditSafeLinksFilter.ps1 deleted file mode 100644 index fd7b11144b48..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-EditSafeLinksFilter.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -function Invoke-EditSafeLinksFilter { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - Exchange.SpamFilter.Read - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $APIName = $Request.Params.CIPPEndpoint - $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter - $RuleName = $Request.Query.RuleName ?? $Request.Body.RuleName - $State = $Request.Query.State ?? $Request.Body.State - - try { - $ExoRequestParam = @{ - tenantid = $TenantFilter - cmdParams = @{ - Identity = $RuleName - } - useSystemMailbox = $true - } - - switch ($State) { - 'Enable' { - $ExoRequestParam.Add('cmdlet', 'Enable-SafeLinksRule') - } - 'Disable' { - $ExoRequestParam.Add('cmdlet', 'Disable-SafeLinksRule') - } - Default { - throw 'Invalid state' - } - } - $null = New-ExoRequest @ExoRequestParam - - $Result = "Successfully set SafeLinks rule $($RuleName) to $($State)" - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Info - $StatusCode = [HttpStatusCode]::OK - } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed setting SafeLinks rule $($RuleName) to $($State). Error: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' - $StatusCode = [HttpStatusCode]::InternalServerError - } - - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $StatusCode - Body = @{Results = $Result } - }) -} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Devices/Invoke-ExecDeviceDelete.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Devices/Invoke-ExecDeviceDelete.ps1 index 1916328d51da..8ca7b8fef050 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Devices/Invoke-ExecDeviceDelete.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Devices/Invoke-ExecDeviceDelete.ps1 @@ -12,28 +12,25 @@ Function Invoke-ExecDeviceDelete { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with body parameters or the body of the request. - $TenantFilter = $Request.body.tenantFilter ?? $Request.Query.tenantFilter - $Action = $Request.body.action ?? $Request.Query.action - $DeviceID = $Request.body.ID ?? $Request.Query.ID + $TenantFilter = $Request.Body.tenantFilter ?? $Request.Query.tenantFilter + $Action = $Request.Body.action ?? $Request.Query.action + $DeviceID = $Request.Body.ID ?? $Request.Query.ID try { - $Results = Set-CIPPDeviceState -Action $Action -DeviceID $DeviceID -TenantFilter $TenantFilter -Headers $Request.Headers -APIName $APINAME + $Results = Set-CIPPDeviceState -Action $Action -DeviceID $DeviceID -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName $StatusCode = [HttpStatusCode]::OK } catch { $Results = $_.Exception.Message $StatusCode = [HttpStatusCode]::BadRequest } - Write-Host $Results - $body = [pscustomobject]@{'Results' = "$Results" } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $body + Body = @{ 'Results' = $Results } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddGuest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddGuest.ps1 index d84dd6bc8bcb..cc9db658bf78 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddGuest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddGuest.ps1 @@ -15,47 +15,45 @@ Function Invoke-AddGuest { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' $TenantFilter = $Request.Body.tenantFilter - - $Results = [System.Collections.ArrayList]@() $UserObject = $Request.Body + try { if ($UserObject.RedirectURL) { - $BodyToship = [pscustomobject] @{ + $BodyToShip = [pscustomobject] @{ 'InvitedUserDisplayName' = $UserObject.DisplayName 'InvitedUserEmailAddress' = $($UserObject.mail) 'inviteRedirectUrl' = $($UserObject.RedirectURL) 'sendInvitationMessage' = [bool]$UserObject.SendInvite } } else { - $BodyToship = [pscustomobject] @{ + $BodyToShip = [pscustomobject] @{ 'InvitedUserDisplayName' = $UserObject.DisplayName 'InvitedUserEmailAddress' = $($UserObject.mail) 'sendInvitationMessage' = [bool]$UserObject.SendInvite 'inviteRedirectUrl' = 'https://myapps.microsoft.com' } } - $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress - $null = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/invitations' -tenantid $TenantFilter -type POST -body $BodyToship -verbose + $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToShip -Compress + $null = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/invitations' -tenantid $TenantFilter -type POST -body $BodyToShip -Verbose if ($UserObject.SendInvite -eq $true) { - $Results.Add('Invited Guest. Invite Email sent') - Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message "Invited Guest $($UserObject.DisplayName) with Email Invite " -Sev 'Info' + $Result = "Invited Guest $($UserObject.DisplayName) with Email Invite" + Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Info' } else { - $Results.Add('Invited Guest. No Invite Email was sent') - Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message "Invited Guest $($UserObject.DisplayName) with no Email Invite " -Sev 'Info' + $Result = "Invited Guest $($UserObject.DisplayName) with no Email Invite" + Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Info' } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-CippException -Exception $_ $Result = "Failed to Invite Guest. $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Error' -LogData $ErrorMessage - $Results.Add($Result) $StatusCode = [HttpStatusCode]::BadRequest } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @{Results = @($Results) } + Body = @{ 'Results' = @($Result) } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 index 019ab34d981f..f67050101183 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 @@ -10,20 +10,21 @@ Function Invoke-AddUser { [CmdletBinding()] param($Request, $TriggerMetadata) - $APIName = 'AddUser' - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $UserObj = $Request.body + $UserObj = $Request.Body if ($UserObj.Scheduled.Enabled) { $TaskBody = [pscustomobject]@{ - TenantFilter = $UserObj.tenantfilter + TenantFilter = $UserObj.tenantFilter Name = "New user creation: $($UserObj.mailNickname)@$($UserObj.PrimDomain.value)" Command = @{ value = 'New-CIPPUserTask' label = 'New-CIPPUserTask' } - Parameters = [pscustomobject]@{ userobj = $UserObj } + Parameters = [pscustomobject]@{ UserObj = $UserObj } ScheduledTime = $UserObj.Scheduled.date PostExecution = @{ Webhook = [bool]$Request.Body.PostExecution.Webhook @@ -31,20 +32,20 @@ Function Invoke-AddUser { PSA = [bool]$Request.Body.PostExecution.PSA } } - Add-CIPPScheduledTask -Task $TaskBody -hidden $false -DisallowDuplicateName $true -Headers $Request.Headers + Add-CIPPScheduledTask -Task $TaskBody -hidden $false -DisallowDuplicateName $true -Headers $Headers $body = [pscustomobject] @{ 'Results' = @("Successfully created scheduled task to create user $($UserObj.DisplayName)") } } else { - $CreationResults = New-CIPPUserTask -userobj $UserObj -APIName $APINAME -Headers $Request.Headers + $CreationResults = New-CIPPUserTask -UserObj $UserObj -APIName $APIName -Headers $Headers $body = [pscustomobject] @{ 'Results' = @( $CreationResults.Results[0], $CreationResults.Results[1], @{ 'resultText' = $CreationResults.Results[2] - 'copyField' = $CreationResults.password - 'state' = 'success' + 'copyField' = $CreationResults.password + 'state' = 'success' } ) 'CopyFrom' = @{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 index 9a017652bd39..3bb2c6b06a53 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 @@ -10,9 +10,12 @@ function Invoke-AddUserBulk { [CmdletBinding()] param($Request, $TriggerMetadata) - $APIName = 'AddUserBulk' - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $TenantFilter = $Request.body.TenantFilter + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with body parameters or the body of the request. + $TenantFilter = $Request.Body.tenantFilter $BulkUsers = $Request.Body.BulkUser $AssignedLicenses = $Request.Body.licenses @@ -78,7 +81,7 @@ function Invoke-AddUserBulk { # Add all other properties foreach ($key in $User.PSObject.Properties.Name) { if ($key -notin @('displayName', 'mailNickName', 'domain', 'password', 'usageLocation', 'businessPhones')) { - if (![string]::IsNullOrEmpty($User.$key) -and $UserBody.$key -eq $null) { + if (![string]::IsNullOrEmpty($User.$key) -and $null -eq $UserBody.$key) { $UserBody.$key = $User.$key } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 3bb2fc720957..50e985309f1a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -27,7 +27,7 @@ function Invoke-CIPPOffboardingJob { Remove-CIPPGroups -userid $userid -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username "$Username" } { $_.HideFromGAL -eq $true } { - Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -hidefromgal $true -Headers $Headers -APIName $APIName + Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -HideFromGAL $true -Headers $Headers -APIName $APIName } { $_.DisableSignIn -eq $true } { Set-CIPPSignInState -TenantFilter $TenantFilter -userid $username -AccountEnabled $false -Headers $Headers -APIName $APIName @@ -99,7 +99,7 @@ function Invoke-CIPPOffboardingJob { Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers } { $_.'ClearImmutableId' -eq $true } { - Clear-CIPPImmutableId -userid $userid -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName + Clear-CIPPImmutableID -UserID $userid -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName } } return $Return diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 index 08969005537f..d40f20130797 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 @@ -69,18 +69,18 @@ function Invoke-EditUser { } $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $UserObj.tenantFilter -type PATCH -body $BodyToship -verbose - $null = $Results.Add( 'Success. The user has been edited.' ) + $Results.Add( 'Success. The user has been edited.' ) Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Edited user $($UserObj.DisplayName) with id $($UserObj.id)" -Sev Info if ($UserObj.password) { $passwordProfile = [pscustomobject]@{'passwordProfile' = @{ 'password' = $UserObj.password; 'forceChangePasswordNextSignIn' = [boolean]$UserObj.MustChangePass } } | ConvertTo-Json - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $UserObj.tenantFilter -type PATCH -body $PasswordProfile -verbose - $null = $Results.Add("Success. The password has been set to $($UserObj.password)") + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $UserObj.tenantFilter -type PATCH -body $PasswordProfile -Verbose + $Results.Add("Success. The password has been set to $($UserObj.password)") Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Reset $($UserObj.DisplayName)'s Password" -Sev Info } } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message "User edit API failed. $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage - $null = $Results.Add( "Failed to edit user. $($ErrorMessage.NormalizedError)") + $Results.Add( "Failed to edit user. $($ErrorMessage.NormalizedError)") } @@ -90,7 +90,7 @@ function Invoke-EditUser { if ($licenses -or $UserObj.removeLicenses) { if ($UserObj.sherwebLicense.value) { $null = Set-SherwebSubscription -Headers $Headers -TenantFilter $UserObj.tenantFilter -SKU $UserObj.sherwebLicense.value -Add 1 - $null = $Results.Add('Added Sherweb License, scheduling assignment') + $Results.Add('Added Sherweb License, scheduling assignment') $taskObject = [PSCustomObject]@{ TenantFilter = $UserObj.tenantFilter Name = "Assign License: $UserPrincipalName" @@ -115,16 +115,16 @@ function Invoke-EditUser { #if the list of skuIds in $CurrentLicenses.assignedLicenses is EXACTLY the same as $licenses, we don't need to do anything, but the order in both can be different. if (($CurrentLicenses.assignedLicenses.skuId -join ',') -eq ($licenses -join ',') -and $UserObj.removeLicenses -eq $false) { Write-Host "$($CurrentLicenses.assignedLicenses.skuId -join ',') $(($licenses -join ','))" - $null = $Results.Add( 'Success. User license is already correct.' ) + $Results.Add( 'Success. User license is already correct.' ) } else { if ($UserObj.removeLicenses) { $licResults = Set-CIPPUserLicense -UserId $UserObj.id -TenantFilter $UserObj.tenantFilter -RemoveLicenses $CurrentLicenses.assignedLicenses.skuId -Headers $Headers - $null = $Results.Add($licResults) + $Results.Add($licResults) } else { #Remove all objects from $CurrentLicenses.assignedLicenses.skuId that are in $licenses $RemoveLicenses = $CurrentLicenses.assignedLicenses.skuId | Where-Object { $_ -notin $licenses } $licResults = Set-CIPPUserLicense -UserId $UserObj.id -TenantFilter $UserObj.tenantFilter -RemoveLicenses $RemoveLicenses -AddLicenses $licenses -Headers $headers - $null = $Results.Add($licResults) + $Results.Add($licResults) } } @@ -134,7 +134,7 @@ function Invoke-EditUser { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message "License assign API failed. $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage - $null = $Results.Add( "We've failed to assign the license. $($ErrorMessage.NormalizedError)") + $Results.Add( "We've failed to assign the license. $($ErrorMessage.NormalizedError)") Write-Warning "License assign API failed. $($_.Exception.Message)" Write-Information $_.InvocationInfo.PositionMessage } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-SetUserAliases.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUserAliases.ps1 similarity index 71% rename from Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-SetUserAliases.ps1 rename to Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUserAliases.ps1 index 3ca90f60b72a..d8f254cead0a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-SetUserAliases.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUserAliases.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-SetUserAliases { +Function Invoke-EditUserAliases { <# .FUNCTIONALITY Entrypoint @@ -12,9 +12,11 @@ Function Invoke-SetUserAliases { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $ApiName -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' $UserObj = $Request.Body + $TenantFilter = $UserObj.tenantFilter + if ([string]::IsNullOrWhiteSpace($UserObj.id)) { $body = @{'Results' = @('Failed to manage aliases. No user ID provided') } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -31,8 +33,8 @@ Function Invoke-SetUserAliases { try { if ($Aliases -or $RemoveAliases -or $UserObj.MakePrimary) { # Get current mailbox - $CurrentMailbox = New-ExoRequest -tenantid $UserObj.tenantFilter -cmdlet 'Get-Mailbox' -cmdParams @{ Identity = $UserObj.id } -UseSystemMailbox $true - + $CurrentMailbox = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-Mailbox' -cmdParams @{ Identity = $UserObj.id } -UseSystemMailbox $true + if (-not $CurrentMailbox) { throw 'Could not find mailbox for user' } @@ -40,26 +42,26 @@ Function Invoke-SetUserAliases { $CurrentProxyAddresses = @($CurrentMailbox.EmailAddresses) Write-Host "Current proxy addresses: $($CurrentProxyAddresses -join ', ')" $NewProxyAddresses = @($CurrentProxyAddresses) - + # Handle setting primary address if ($UserObj.MakePrimary) { $PrimaryAddress = $UserObj.MakePrimary Write-Host "Attempting to set primary address: $PrimaryAddress" - + # Normalize the primary address format if ($PrimaryAddress -notlike 'SMTP:*') { $PrimaryAddress = "SMTP:$($PrimaryAddress -replace '^smtp:', '')" } Write-Host "Normalized primary address: $PrimaryAddress" - + # Check if the address exists in the current addresses (case-insensitive) - $ExistingAddress = $CurrentProxyAddresses | Where-Object { + $ExistingAddress = $CurrentProxyAddresses | Where-Object { $current = $_.ToLower() $target = $PrimaryAddress.ToLower() Write-Host "Comparing: '$current' with '$target'" $current -eq $target } - + if (-not $ExistingAddress) { Write-Host "Available addresses: $($CurrentProxyAddresses -join ', ')" throw "Cannot set primary address. Address $($PrimaryAddress -replace '^SMTP:', '') not found in user's addresses." @@ -69,23 +71,22 @@ Function Invoke-SetUserAliases { $NewProxyAddresses = $NewProxyAddresses | ForEach-Object { if ($_ -like 'SMTP:*') { $_.ToLower() - } - else { + } else { $_ } } # Remove any existing version of the address (case-insensitive) - $NewProxyAddresses = $NewProxyAddresses | Where-Object { - $_.ToLower() -ne $PrimaryAddress.ToLower() + $NewProxyAddresses = $NewProxyAddresses | Where-Object { + $_.ToLower() -ne $PrimaryAddress.ToLower() } # Add the new primary address at the beginning $NewProxyAddresses = @($PrimaryAddress) + $NewProxyAddresses - - Write-LogMessage -API $ApiName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Set primary address for $($CurrentMailbox.DisplayName)" -Sev Info - $null = $results.Add('Success. Set new primary address.') + + Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message "Set primary address for $($CurrentMailbox.DisplayName)" -Sev Info + $Results.Add('Success. Set new primary address.') } - + # Remove specified aliases if ($RemoveAliases) { foreach ($Alias in $RemoveAliases) { @@ -94,12 +95,12 @@ Function Invoke-SetUserAliases { $Alias = "smtp:$Alias" } # Remove the alias case-insensitively - $NewProxyAddresses = $NewProxyAddresses | Where-Object { - $_.ToLower() -ne $Alias.ToLower() + $NewProxyAddresses = $NewProxyAddresses | Where-Object { + $_.ToLower() -ne $Alias.ToLower() } } - Write-LogMessage -API $ApiName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Removed Aliases from $($CurrentMailbox.DisplayName)" -Sev Info - $null = $results.Add('Success. Removed specified aliases from user.') + Write-LogMessage -API $ApiName -tenant $TenantFilter -headers $Headers -message "Removed Aliases from $($CurrentMailbox.DisplayName)" -Sev Info + $Results.Add('Success. Removed specified aliases from user.') } # Add new aliases @@ -117,8 +118,8 @@ Function Invoke-SetUserAliases { } if ($AliasesToAdd.Count -gt 0) { $NewProxyAddresses = $NewProxyAddresses + $AliasesToAdd - Write-LogMessage -API $ApiName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Added Aliases to $($CurrentMailbox.DisplayName)" -Sev Info - $null = $results.Add('Success. Added new aliases to user.') + Write-LogMessage -API $ApiName -tenant ($TenantFilter) -headers $Headers -message "Added Aliases to $($CurrentMailbox.DisplayName)" -Sev Info + $Results.Add('Success. Added new aliases to user.') } } @@ -127,21 +128,18 @@ Function Invoke-SetUserAliases { Identity = $UserObj.id EmailAddresses = $NewProxyAddresses } - $null = New-ExoRequest -tenantid $UserObj.tenantFilter -cmdlet 'Set-Mailbox' -cmdParams $Params -UseSystemMailbox $true + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams $Params -UseSystemMailbox $true + } else { + $Results.Add('No alias changes specified.') } - else { - $null = $results.Add('No alias changes specified.') - } - } - catch { + } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -API $ApiName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Alias management failed. $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage - $null = $results.Add("Failed to manage aliases: $($ErrorMessage.NormalizedError)") + Write-LogMessage -API $ApiName -tenant ($TenantFilter) -headers $Headers -message "Alias management failed. $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + $Results.Add("Failed to manage aliases: $($ErrorMessage.NormalizedError)") } - $body = @{'Results' = @($results) } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $Body + Body = @{'Results' = @($Results) } }) -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECCheck.ps1 index 27389fffd877..533e02eea899 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECCheck.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECCheck.ps1 @@ -19,7 +19,7 @@ Function Invoke-ExecBECCheck { $Batch = @{ 'FunctionName' = 'BECRun' 'UserID' = $Request.Query.userid - 'TenantFilter' = $Request.Query.tenantfilter + 'TenantFilter' = $Request.Query.tenantFilter 'userName' = $Request.Query.userName } @@ -40,7 +40,7 @@ Function Invoke-ExecBECCheck { SkipLog = $true } #Write-Host ($InputObject | ConvertTo-Json) - $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) + $null = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ( ConvertTo-Json -InputObject $InputObject -Depth 5 -Compress ) @{ GUID = $Request.Query.userid } } else { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 index 50374bc33a1b..b234dfbbf136 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 @@ -11,23 +11,24 @@ Function Invoke-ExecClrImmId { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev Debug + + # Interact with body parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter - Write-LogMessage -headers $Request.Headers -API $APIName -message 'Accessed this API' -Sev Debug $UserID = $Request.Query.ID ?? $Request.Body.ID - Try { - $Result = Clear-CIPPImmutableId -userid $UserID -TenantFilter $TenantFilter -Headers $Request.Headers -APIName $APIName + try { + $Result = Clear-CIPPImmutableID -UserID $UserID -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Result = $ErrorMessage.NormalizedError + $Result = $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } - $Results = [pscustomobject]@{'Results' = $Result } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecCreateTAP.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecCreateTAP.ps1 index 58187b2f6640..d8b6022c735d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecCreateTAP.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecCreateTAP.ps1 @@ -26,14 +26,14 @@ Function Invoke-ExecCreateTAP { $TAPResult, @{ resultText = "User ID: $UserID" - copyField = $UserID - state = 'success' + copyField = $UserID + state = 'success' } ) $StatusCode = [HttpStatusCode]::OK } catch { - $Results = Get-NormalizedError -message $($_.Exception.Message) + $Results = Get-NormalizedError -message $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 index 67a2b036cffb..c1e6748b7f32 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 @@ -29,11 +29,10 @@ Function Invoke-ExecDisableUser { $StatusCode = [HttpStatusCode]::InternalServerError } - $Results = [pscustomobject]@{'Results' = "$Result" } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{ 'Results' = "$Result" } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDismissRiskyUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDismissRiskyUser.ps1 index d22cca0f8e9e..a1e07c97a31a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDismissRiskyUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDismissRiskyUser.ps1 @@ -29,8 +29,8 @@ function Invoke-ExecDismissRiskyUser { try { $GraphResults = New-GraphPostRequest @GraphRequest - Write-LogMessage -API $APIName -tenant $TenantFilter -message "Dismissed user risk for $userDisplayName" -sev 'Info' $Result = "Successfully dismissed User Risk for user $userDisplayName. $GraphResults" + Write-LogMessage -API $APIName -tenant $TenantFilter -message $Result -sev 'Info' $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-CippException -Exception $_ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index e67fb1cb32d0..ae45ad066f45 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -125,14 +125,14 @@ function Invoke-ExecJITAdmin { if ($Request.Body.existingUser.value -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.existingUser.value)" -tenantid $TenantFilter).userPrincipalName } - Write-LogMessage -Headers $User -API $APINAME -message "Executing JIT Admin for $Username" -tenant $TenantFilter -Sev 'Info' + Write-LogMessage -Headers $User -API $APIName -message "Executing JIT Admin for $Username" -tenant $TenantFilter -Sev 'Info' $Start = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.StartDate)).DateTime.ToLocalTime() $Expiration = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.EndDate)).DateTime.ToLocalTime() $Results = [System.Collections.Generic.List[string]]::new() if ($Request.Body.useraction -eq 'Create') { - Write-LogMessage -Headers $User -API $APINAME -tenant $TenantFilter -message "Creating JIT Admin user $($Request.Body.Username)" -Sev 'Info' + Write-LogMessage -Headers $User -API $APIName -tenant $TenantFilter -message "Creating JIT Admin user $($Request.Body.Username)" -Sev 'Info' Write-Information "Creating JIT Admin user $($Request.Body.username)" $JITAdmin = @{ User = @{ @@ -262,6 +262,8 @@ function Invoke-ExecJITAdmin { } } + # TODO - We should find a way to have this return a HTTP status code based on the success or failure of the operation. This also doesn't return the results of the operation in a Results hash table, like most of the rest of the API. + # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $Body diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboard_Mailboxpermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboard_Mailboxpermissions.ps1 deleted file mode 100644 index 10b4c8576330..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboard_Mailboxpermissions.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -using namespace System.Net - -Function Invoke-ExecOffboard_Mailboxpermissions { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - Exchange.Mailbox.ReadWrite - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - foreach ($Mailbox in $Mailboxes) { - Remove-CIPPMailboxPermissions -PermissionsLevel @('FullAccess', 'SendAs', 'SendOnBehalf') -userid $Mailbox.UserPrincipalName -AccessUser $QueueItem.User -TenantFilter $QueueItem.TenantFilter -APIName $APINAME -Headers $QueueItem.Headers - } - -} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOneDriveShortCut.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOneDriveShortCut.ps1 index 5da791680ea4..5bf461f6a0d5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOneDriveShortCut.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOneDriveShortCut.ps1 @@ -15,16 +15,17 @@ Function Invoke-ExecOneDriveShortCut { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' Try { - $MessageResult = New-CIPPOneDriveShortCut -username $Request.Body.username -userid $Request.Body.userid -TenantFilter $Request.Body.tenantFilter -URL $Request.Body.siteUrl.value -Headers $Request.Headers - $Results = [pscustomobject]@{ 'Results' = "$MessageResult" } + $Result = New-CIPPOneDriveShortCut -username $Request.Body.username -userid $Request.Body.userid -TenantFilter $Request.Body.tenantFilter -URL $Request.Body.siteUrl.value -Headers $Request.Headers + $StatusCode = [HttpStatusCode]::OK } catch { - $Results = [pscustomobject]@{'Results' = "OneDrive Shortcut creation failed: $($_.Exception.Message)" } + $Result = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOnedriveProvision.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOnedriveProvision.ps1 index d86806c39f68..48d6be9274fb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOnedriveProvision.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOnedriveProvision.ps1 @@ -11,18 +11,23 @@ Function Invoke-ExecOneDriveProvision { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - $Params = $Request.Body ?? $Request.Query + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + $UserPrincipalName = $Request.Body.UserPrincipalName ?? $Request.Query.UserPrincipalName + $TenantFilter = $Request.Body.tenantFilter ?? $Request.Query.tenantFilter + try { - $State = Request-CIPPSPOPersonalSite -TenantFilter $Params.TenantFilter -UserEmails $Params.UserPrincipalName -Headers $Request.Headers -APIName $APINAME - $Results = [pscustomobject]@{'Results' = "$State" } + $Result = Request-CIPPSPOPersonalSite -TenantFilter $TenantFilter -UserEmails $UserPrincipalName -Headers $Headers -APIName $APIName + $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $Results = [pscustomobject]@{'Results' = "Failed. $ErrorMessage" } + $Result = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 deleted file mode 100644 index 7a7c296b4016..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -function Invoke-ExecPerUserMFAAllUsers { - <# - .FUNCTIONALITY - Entrypoint - - .ROLE - Identity.User.ReadWrite - #> - Param($Request, $TriggerMetadata) - - $APIName = $Request.Params.CIPPEndpoint - $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - # XXX Seems to be an unused endpoint? - Bobby - - $TenantFilter = $request.Query.tenantFilter - $Users = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter - $Request = @{ - userId = $Users.id - TenantFilter = $TenantFilter - State = $Request.Query.State - Headers = $Request.Headers - APIName = $APIName - } - $Result = Set-CIPPPerUserMFA @Request - $Body = @{ - Results = @($Result) - } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Body - }) -} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 index 4cb4c25db418..a72a7f82e40e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 @@ -12,17 +12,17 @@ Function Invoke-ExecResetMFA { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter $UserID = $Request.Query.ID ?? $Request.Body.ID try { $Result = Remove-CIPPUserMFA -UserPrincipalName $UserID -TenantFilter $TenantFilter -Headers $Headers - if ($Result -match 'Failed') { throw $Result } + if ($Result -match '^Failed') { throw $Result } $StatusCode = [HttpStatusCode]::OK } catch { - $Result = "$($_.Exception.Message)" + $Result = $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 index 11fed934022b..d2080b726ab8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 @@ -23,21 +23,18 @@ Function Invoke-ExecResetPass { $MustChange = [System.Convert]::ToBoolean($MustChange) try { - $Result = Set-CIPPResetPassword -UserID $ID -tenantFilter $TenantFilter -APIName $APINAME -Headers $Request.Headers -forceChangePasswordNextSignIn $MustChange -DisplayName $DisplayName + $Result = Set-CIPPResetPassword -UserID $ID -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers -forceChangePasswordNextSignIn $MustChange -DisplayName $DisplayName if ($Result.state -eq 'Error') { throw $Result.resultText } $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message - Write-LogMessage -headers $Request.Headers -API $APINAME -message $Result -Sev 'Error' $StatusCode = [HttpStatusCode]::InternalServerError - } - $Results = [pscustomobject]@{'Results' = $Result } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRestoreDeleted.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRestoreDeleted.ps1 index cf4300a8054d..36daf3d8914c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRestoreDeleted.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRestoreDeleted.ps1 @@ -39,11 +39,10 @@ Function Invoke-ExecRestoreDeleted { $StatusCode = [HttpStatusCode]::InternalServerError } - $Results = [pscustomobject]@{'Results' = $Result } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 index 822c6356dcf1..4d39d67fbbec 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 @@ -21,18 +21,17 @@ Function Invoke-ExecRevokeSessions { try { $Result = Revoke-CIPPSessions -UserID $ID -TenantFilter $TenantFilter -Username $Username -APIName $APIName -Headers $Request.Headers - if ($Result -like 'Revoke Session Failed*') { throw $Result } + if ($Result -match '^Failed') { throw $Result } $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } - $Results = [pscustomobject]@{'Results' = $Result } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListPerUserMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListPerUserMFA.ps1 index 7e6d040a18d9..2c6db5ca62ef 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListPerUserMFA.ps1 @@ -12,9 +12,7 @@ function Invoke-ListPerUserMFA { $APIName = $Request.Params.CIPPEndpoint $User = $Request.Headers - Write-LogMessage -Headers $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' - - + Write-LogMessage -Headers $User -API $APIName -message 'Accessed this API' -Sev 'Debug' # Parse query parameters $Tenant = $Request.query.tenantFilter @@ -30,13 +28,13 @@ function Invoke-ListPerUserMFA { if ($AllUsers -eq $true) { $Results = Get-CIPPPerUserMFA -TenantFilter $Tenant -AllUsers $true } else { - $Results = Get-CIPPPerUserMFA -TenantFilter $Tenant -userId $UserId + $Results = Get-CIPPPerUserMFA -TenantFilter $Tenant -UserId $UserId } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message $Results = "Failed to get MFA State for $UserId : $ErrorMessage" - $StatusCode = [HttpStatusCode]::Forbidden + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 index bd8ce3a7eb27..585fb8c71bbc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 @@ -14,11 +14,10 @@ Function Invoke-ListUserConditionalAccessPolicies { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - + # XXX - Unused endpoint? # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter $UserID = $Request.Query.UserID try { @@ -30,14 +29,14 @@ Function Invoke-ListUserConditionalAccessPolicies { $ConditionalAccessWhatIfDefinition = @{ 'conditionalAccessWhatIfSubject' = @{ '@odata.type' = '#microsoft.graph.userSubject' - 'userId' = "$userId" + 'userId' = "$UserID" } 'conditionalAccessContext' = $CAContext 'conditionalAccessWhatIfConditions' = @{} } - $JSONBody = $ConditionalAccessWhatIfDefinition | ConvertTo-Json -Depth 10 + $JSONBody = ConvertTo-Json -Depth 10 -InputObject $ConditionalAccessWhatIfDefinition -Compress - $GraphRequest = (New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/evaluate' -tenantid $tenantFilter -type POST -body $JsonBody -AsApp $true).value + $GraphRequest = (New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/evaluate' -tenantid $TenantFilter -type POST -body $JsonBody -AsApp $true).value } catch { $GraphRequest = @{} } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserCounts.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserCounts.ps1 index f9522b7648a9..e7d995268ad2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserCounts.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserCounts.ps1 @@ -14,28 +14,25 @@ Function Invoke-ListUserCounts { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - - # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter if ($Request.Query.TenantFilter -eq 'AllTenants') { - $users = 'Not Supported' + $Users = 'Not Supported' $LicUsers = 'Not Supported' $GAs = 'Not Supported' $Guests = 'Not Supported' } else { try { $Users = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $Users = 'Not available' } - try { $LicUsers = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1&`$filter=assignedLicenses/`$count ne 0" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $Licusers = 'Not available' } - try { $GAs = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members?`$count=true" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $Gas = 'Not available' } - try { $guests = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1&`$filter=userType eq 'Guest'" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $Guests = 'Not available' } + try { $LicUsers = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1&`$filter=assignedLicenses/`$count ne 0" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $LicUsers = 'Not available' } + try { $GAs = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members?`$count=true" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $GAs = 'Not available' } + try { $Guests = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1&`$filter=userType eq 'Guest'" -CountOnly -ComplexFilter -tenantid $TenantFilter } catch { $Guests = 'Not available' } } $StatusCode = [HttpStatusCode]::OK $Counts = @{ - Users = $users + Users = $Users LicUsers = $LicUsers - Gas = $Gas - Guests = $guests + Gas = $GAs + Guests = $Guests } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserDevices.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserDevices.ps1 index d37bc6e836b2..427b61b05f60 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserDevices.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserDevices.ps1 @@ -14,11 +14,8 @@ Function Invoke-ListUserDevices { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - - # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter $UserID = $Request.Query.UserID function Get-EPMID { @@ -33,8 +30,8 @@ Function Invoke-ListUserDevices { } } try { - $EPMDevices = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserID/managedDevices" -Tenantid $tenantfilter - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserID/ownedDevices?`$top=999" -Tenantid $tenantfilter | Select-Object @{ Name = 'ID'; Expression = { $_.'id' } }, + $EPMDevices = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserID/managedDevices" -Tenantid $TenantFilter + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserID/ownedDevices?`$top=999" -Tenantid $TenantFilter | Select-Object @{ Name = 'ID'; Expression = { $_.'id' } }, @{ Name = 'accountEnabled'; Expression = { $_.'accountEnabled' } }, @{ Name = 'approximateLastSignInDateTime'; Expression = { $_.'approximateLastSignInDateTime' | Out-String } }, @{ Name = 'createdDateTime'; Expression = { $_.'createdDateTime' | Out-String } }, diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 index e1181a6893fc..86bb0bc44c5b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 @@ -115,7 +115,7 @@ function Invoke-ListUserMailboxDetails { # Parse permissions - #Implemented as an arraylist that uses .add(). + #Implemented as an ArrayList that uses .add(). $ParsedPerms = [System.Collections.ArrayList]::new() foreach ($PermSet in @($PermsRequest, $PermsRequest2)) { foreach ($Perm in $PermSet) { @@ -177,7 +177,7 @@ function Invoke-ListUserMailboxDetails { $TotalArchiveItemCount = try { [math]::Round($ArchiveSizeRequest.ItemCount, 2) } catch { 0 } } - # Parse InPlaceHolds to determine hold types if avaliable + # Parse InPlaceHolds to determine hold types if available $InPlaceHold = $false $EDiscoveryHold = $false $PurviewRetentionHold = $false diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxRules.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxRules.ps1 index 74def3815f6d..750062e50315 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxRules.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxRules.ps1 @@ -11,33 +11,28 @@ Function Invoke-ListUserMailboxRules { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - $User = $Request.Headers - Write-LogMessage -Headers $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' - - - + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter + $UserID = $Request.Query.UserID try { - $TenantFilter = $Request.Query.TenantFilter - $UserID = $Request.Query.UserID $UserEmail = if ([string]::IsNullOrWhiteSpace($Request.Query.userEmail)) { $UserID } else { $Request.Query.userEmail } - $GraphRequest = New-ExoRequest -Anchor $UserID -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{mailbox = $UserID; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | Select-Object * -ExcludeProperty RuleIdentity + $Result = New-ExoRequest -Anchor $UserID -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{mailbox = $UserID; IncludeHidden = $true } | + Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | Select-Object * -ExcludeProperty RuleIdentity + $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -Headers $User -API $APINAME -message "Failed to retrieve mailbox rules $($UserEmail): $($ErrorMessage.NormalizedError) " -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = '500' - Body = $ErrorMessage.NormalizedError - }) - exit + $Result = "Failed to retrieve mailbox rules for $UserEmail : Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -Headers $Headers -tenant $TenantFilter -API $APIName -message $Result -Sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = @($GraphRequest) + StatusCode = $StatusCode + Body = @($Result) }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserPhoto.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserPhoto.ps1 index 154dc1fffaa6..19ceb7562341 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserPhoto.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserPhoto.ps1 @@ -10,7 +10,12 @@ Function Invoke-ListUserPhoto { [CmdletBinding()] param($Request, $TriggerMetadata) - $tenantFilter = $Request.Query.TenantFilter + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $tenantFilter = $Request.Query.tenantFilter $userId = $Request.Query.UserID $URI = "/users/$userId/photo/`$value" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 index 858ddfb27da4..224584ab81af 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 @@ -12,12 +12,13 @@ function Invoke-ListUserSettings { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails + + $Username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails try { $Table = Get-CippTable -tablename 'UserSettings' $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'allUsers'" - if (!$UserSettings) { $userSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$username'" } + if (!$UserSettings) { $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$Username'" } $UserSettings = $UserSettings.JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue $StatusCode = [HttpStatusCode]::OK $Results = $UserSettings diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSigninLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSigninLogs.ps1 index a0009394dd72..1804d4b4384a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSigninLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSigninLogs.ps1 @@ -18,26 +18,22 @@ Function Invoke-ListUserSigninLogs { # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter $UserID = $Request.Query.UserID + $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$UserID')&`$top=$top&`$orderby=createdDateTime desc" + try { - $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$UserID')&`$top=$top&`$orderby=createdDateTime desc" - Write-Host $URI - $GraphRequest = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose - Write-Host $GraphRequest - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = @($GraphRequest) - }) + $Result = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose + $StatusCode = [HttpStatusCode]::OK } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Failed to retrieve Sign In report: $($_.Exception.message) " -Sev 'Error' -tenant $TenantFilter - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = '500' - Body = $(Get-NormalizedError -message $_.Exception.message) - }) + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to retrieve Sign In report for user $UserID : Error: $($ErrorMessage.NormalizedError)" + $StatusCode = [HttpStatusCode]::InternalServerError } - + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @($Result) + }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUsers.ps1 index d2a5c2685bd3..5e81ea1c684b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUsers.ps1 @@ -16,7 +16,7 @@ Function Invoke-ListUsers { $ConvertTable = Import-Csv ConversionTable.csv | Sort-Object -Property 'guid' -Unique # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter $GraphFilter = $Request.Query.graphFilter $userid = $Request.Query.UserID @@ -26,7 +26,7 @@ Function Invoke-ListUsers { $_ | Add-Member -MemberType NoteProperty -Name 'username' -Value ($_.userPrincipalName -split '@' | Select-Object -First 1) -Force $_ | Add-Member -MemberType NoteProperty -Name 'Aliases' -Value ($_.ProxyAddresses -join ', ') -Force $SkuID = $_.AssignedLicenses.skuid - $_ | Add-Member -MemberType NoteProperty -Name 'LicJoined' -Value (($ConvertTable | Where-Object { $_.guid -in $skuid }).'Product_Display_Name' -join ', ') -Force + $_ | Add-Member -MemberType NoteProperty -Name 'LicJoined' -Value (($ConvertTable | Where-Object { $_.guid -in $SkuID }).'Product_Display_Name' -join ', ') -Force $_ | Add-Member -MemberType NoteProperty -Name 'primDomain' -Value @{value = ($_.userPrincipalName -split '@' | Select-Object -Last 1); label = ($_.userPrincipalName -split '@' | Select-Object -Last 1); } -Force $_ } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveDeletedObject.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveDeletedObject.ps1 index da4e516d6462..f41f79d68485 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveDeletedObject.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveDeletedObject.ps1 @@ -39,11 +39,10 @@ Function Invoke-RemoveDeletedObject { $StatusCode = [HttpStatusCode]::InternalServerError } - $Results = [pscustomobject]@{'Results' = $Result } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 new file mode 100644 index 000000000000..fe506d605367 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 @@ -0,0 +1,227 @@ +using namespace System.Net + +Function Invoke-AddSafeLinksPolicyFromTemplate { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.SafeLinks.ReadWrite + .DESCRIPTION + This function deploys SafeLinks policies and rules from templates to selected tenants. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + try { + $RequestBody = $Request.Body + + # Extract tenant IDs from selectedTenants + $SelectedTenants = $RequestBody.selectedTenants | ForEach-Object { $_.value } + if ('AllTenants' -in $SelectedTenants) { + $SelectedTenants = (Get-Tenants).defaultDomainName + } + + # Extract templates from TemplateList + $Templates = $RequestBody.TemplateList | ForEach-Object { $_.value } + + if (-not $Templates -or $Templates.Count -eq 0) { + throw "No templates provided in TemplateList" + } + + # Helper function to process array fields with cleaner logic + function ConvertTo-SafeArray { + param($Field) + + if ($null -eq $Field) { return @() } + + # Handle arrays + if ($Field -is [array]) { + return $Field | ForEach-Object { + if ($_ -is [string]) { $_ } + elseif ($_.value) { $_.value } + elseif ($_.userPrincipalName) { $_.userPrincipalName } + elseif ($_.id) { $_.id } + else { $_.ToString() } + } + } + + # Handle single objects + if ($Field -is [hashtable] -or $Field -is [PSCustomObject]) { + if ($Field.value) { return @($Field.value) } + if ($Field.userPrincipalName) { return @($Field.userPrincipalName) } + if ($Field.id) { return @($Field.id) } + } + + # Handle strings + if ($Field -is [string]) { return @($Field) } + + return @($Field) + } + + function Test-PolicyExists { + param($TenantFilter, $PolicyName) + + $ExistingPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksPolicy' -useSystemMailbox $true + return $ExistingPolicies | Where-Object { $_.Name -eq $PolicyName } + } + + function Test-RuleExists { + param($TenantFilter, $RuleName) + + $ExistingRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksRule' -useSystemMailbox $true + return $ExistingRules | Where-Object { $_.Name -eq $RuleName } + } + + function New-SafeLinksPolicyFromTemplate { + param($TenantFilter, $Template) + + $PolicyName = $Template.PolicyName + $RuleName = $Template.RuleName ?? "$($PolicyName)_Rule" + + # Check if policy already exists + if (Test-PolicyExists -TenantFilter $TenantFilter -PolicyName $PolicyName) { + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -Sev 'Warning' + return "Policy '$PolicyName' already exists in tenant $TenantFilter" + } + + # Check if rule already exists + if (Test-RuleExists -TenantFilter $TenantFilter -RuleName $RuleName) { + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -Sev 'Warning' + return "Rule '$RuleName' already exists in tenant $TenantFilter" + } + + # Process array fields + $DoNotRewriteUrls = ConvertTo-SafeArray -Field $Template.DoNotRewriteUrls + $SentTo = ConvertTo-SafeArray -Field $Template.SentTo + $SentToMemberOf = ConvertTo-SafeArray -Field $Template.SentToMemberOf + $RecipientDomainIs = ConvertTo-SafeArray -Field $Template.RecipientDomainIs + $ExceptIfSentTo = ConvertTo-SafeArray -Field $Template.ExceptIfSentTo + $ExceptIfSentToMemberOf = ConvertTo-SafeArray -Field $Template.ExceptIfSentToMemberOf + $ExceptIfRecipientDomainIs = ConvertTo-SafeArray -Field $Template.ExceptIfRecipientDomainIs + + # Create policy parameters + $PolicyParams = @{ Name = $PolicyName } + + # Policy configuration mapping + $PolicyMappings = @{ + 'EnableSafeLinksForEmail' = 'EnableSafeLinksForEmail' + 'EnableSafeLinksForTeams' = 'EnableSafeLinksForTeams' + 'EnableSafeLinksForOffice' = 'EnableSafeLinksForOffice' + 'TrackClicks' = 'TrackClicks' + 'AllowClickThrough' = 'AllowClickThrough' + 'ScanUrls' = 'ScanUrls' + 'EnableForInternalSenders' = 'EnableForInternalSenders' + 'DeliverMessageAfterScan' = 'DeliverMessageAfterScan' + 'DisableUrlRewrite' = 'DisableUrlRewrite' + 'AdminDisplayName' = 'AdminDisplayName' + 'CustomNotificationText' = 'CustomNotificationText' + 'EnableOrganizationBranding' = 'EnableOrganizationBranding' + } + + foreach ($templateKey in $PolicyMappings.Keys) { + if ($null -ne $Template.$templateKey) { + $PolicyParams[$PolicyMappings[$templateKey]] = $Template.$templateKey + } + } + + if ($DoNotRewriteUrls.Count -gt 0) { + $PolicyParams['DoNotRewriteUrls'] = $DoNotRewriteUrls + } + + # Create SafeLinks Policy + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'New-SafeLinksPolicy' -cmdParams $PolicyParams -useSystemMailbox $true + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Created SafeLinks policy '$PolicyName'" -Sev 'Info' + + # Create rule parameters + $RuleParams = @{ + Name = $RuleName + SafeLinksPolicy = $PolicyName + } + + # Rule configuration mapping + $RuleMappings = @{ + 'Priority' = 'Priority' + 'TemplateDescription' = 'Comments' + } + + foreach ($templateKey in $RuleMappings.Keys) { + if ($null -ne $Template.$templateKey) { + $RuleParams[$RuleMappings[$templateKey]] = $Template.$templateKey + } + } + + # Add array parameters if they have values + $ArrayMappings = @{ + 'SentTo' = $SentTo + 'SentToMemberOf' = $SentToMemberOf + 'RecipientDomainIs' = $RecipientDomainIs + 'ExceptIfSentTo' = $ExceptIfSentTo + 'ExceptIfSentToMemberOf' = $ExceptIfSentToMemberOf + 'ExceptIfRecipientDomainIs' = $ExceptIfRecipientDomainIs + } + + foreach ($paramName in $ArrayMappings.Keys) { + if ($ArrayMappings[$paramName].Count -gt 0) { + $RuleParams[$paramName] = $ArrayMappings[$paramName] + } + } + + # Create SafeLinks Rule + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'New-SafeLinksRule' -cmdParams $RuleParams -useSystemMailbox $true + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Created SafeLinks rule '$RuleName'" -Sev 'Info' + + # Handle rule state + $StateMessage = "" + if ($null -ne $Template.State) { + $IsState = switch ($Template.State) { + "Enabled" { $true } + "Disabled" { $false } + $true { $true } + $false { $false } + default { $null } + } + + if ($null -ne $IsState) { + $Cmdlet = $IsState ? 'Enable-SafeLinksRule' : 'Disable-SafeLinksRule' + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet $Cmdlet -cmdParams @{ Identity = $RuleName } -useSystemMailbox $true + $StateMessage = " (rule $($IsState ? 'enabled' : 'disabled'))" + } + } + + return "Successfully deployed SafeLinks policy '$PolicyName' and rule '$RuleName' to tenant $TenantFilter$StateMessage" + } + + # Process each tenant and template combination + $Results = foreach ($TenantFilter in $SelectedTenants) { + foreach ($Template in $Templates) { + try { + New-SafeLinksPolicyFromTemplate -TenantFilter $TenantFilter -Template $Template + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $ErrorDetail = "Failed to deploy template '$($Template.TemplateName)' to tenant $TenantFilter. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $ErrorDetail -Sev 'Error' + $ErrorDetail + } + } + } + + $StatusCode = [HttpStatusCode]::OK + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Results = "Failed to process template deployment request. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Results -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Return response + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ Results = $Results } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyTemplate.ps1 new file mode 100644 index 000000000000..c4e4a467fa0e --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyTemplate.ps1 @@ -0,0 +1,96 @@ +using namespace System.Net +Function Invoke-AddSafeLinksPolicyTemplate { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev Debug + + # Debug: Log the incoming request body + Write-LogMessage -Headers $Headers -API $APINAME -message "Request body: $($Request.body | ConvertTo-Json -Depth 5 -Compress)" -Sev Debug + + try { + $GUID = (New-Guid).GUID + + # Validate required fields + if ([string]::IsNullOrEmpty($Request.body.Name)) { + throw "Template name is required but was not provided" + } + + if ([string]::IsNullOrEmpty($Request.body.PolicyName)) { + throw "Policy name is required but was not provided" + } + + # Create a new ordered hashtable to store selected properties + $policyObject = [ordered]@{} + + # Set name and comments - prioritize template-specific fields + $policyObject["TemplateName"] = $Request.body.TemplateName + $policyObject["TemplateDescription"] = $Request.body.TemplateDescription + + # For templates, if no specific policy description is provided, use template description as default + if ([string]::IsNullOrEmpty($Request.body.AdminDisplayName) -and -not [string]::IsNullOrEmpty($Request.body.Description)) { + $Request.body.AdminDisplayName = $Request.body.Description + Write-LogMessage -Headers $Headers -API $APINAME -message "Using template description as default policy description" -Sev Debug + } + + # Log what we're using for template name and description + Write-LogMessage -Headers $Headers -API $APINAME -message "Template Name: '$($policyObject.TemplateName)', Description: '$($policyObject.TemplateDescription)'" -Sev Debug + + # Copy specific properties we want to keep + $propertiesToKeep = @( + # Policy properties + "PolicyName", "EnableSafeLinksForEmail", "EnableSafeLinksForTeams", "EnableSafeLinksForOffice", + "TrackClicks", "AllowClickThrough", "ScanUrls", "EnableForInternalSenders", + "DeliverMessageAfterScan", "DisableUrlRewrite", "DoNotRewriteUrls", + "AdminDisplayName", "CustomNotificationText", "EnableOrganizationBranding", + # Rule properties + "RuleName", "Priority", "State", "Comments", + "SentTo", "SentToMemberOf", "RecipientDomainIs", + "ExceptIfSentTo", "ExceptIfSentToMemberOf", "ExceptIfRecipientDomainIs" + ) + + # Copy each property if it exists + foreach ($prop in $propertiesToKeep) { + if ($null -ne $Request.body.$prop) { + $policyObject[$prop] = $Request.body.$prop + Write-LogMessage -Headers $Headers -API $APINAME -message "Added property '$prop' with value '$($Request.body.$prop)'" -Sev Debug + } + } + + # Convert to JSON + $JSON = $policyObject | ConvertTo-Json -Depth 10 + Write-LogMessage -Headers $Headers -API $APINAME -message "Final JSON: $JSON" -Sev Debug + + # Save the template to Azure Table Storage + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$GUID" + PartitionKey = 'SafeLinksTemplate' + } + + Write-LogMessage -Headers $Headers -API $APINAME -message "Created SafeLinks Policy Template '$($policyObject.TemplateName)' with GUID $GUID" -Sev Info + $body = [pscustomobject]@{'Results' = "Created SafeLinks Policy Template '$($policyObject.TemplateName)' with GUID $GUID" } + $StatusCode = [HttpStatusCode]::OK + + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -Headers $Headers -API $APINAME -message "Failed to create SafeLinks policy template: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to create SafeLinks policy template: $($ErrorMessage.NormalizedError)" } + $StatusCode = [HttpStatusCode]::Forbidden + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-CreateSafeLinksPolicyTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-CreateSafeLinksPolicyTemplate.ps1 new file mode 100644 index 000000000000..8023f9b62bbb --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-CreateSafeLinksPolicyTemplate.ps1 @@ -0,0 +1,77 @@ +using namespace System.Net + +Function Invoke-CreateSafeLinksPolicyTemplate { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.ReadWrite + .DESCRIPTION + This function creates a new Safe Links policy template from scratch. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev Debug + + try { + $GUID = (New-Guid).GUID + + # Create a new ordered hashtable to store selected properties + $policyObject = [ordered]@{} + + # Set name and comments first + $policyObject["TemplateName"] = $Request.body.TemplateName + $policyObject["TemplateDescription"] = $Request.body.TemplateDescription + + # Copy specific properties we want to keep + $propertiesToKeep = @( + # Policy properties + "PolicyName", "EnableSafeLinksForEmail", "EnableSafeLinksForTeams", "EnableSafeLinksForOffice", + "TrackClicks", "AllowClickThrough", "ScanUrls", "EnableForInternalSenders", + "DeliverMessageAfterScan", "DisableUrlRewrite", "DoNotRewriteUrls", + "AdminDisplayName", "CustomNotificationText", "EnableOrganizationBranding", + + # Rule properties + "RuleName", "Priority", "State", "Comments", + "SentTo", "SentToMemberOf", "RecipientDomainIs", + "ExceptIfSentTo", "ExceptIfSentToMemberOf", "ExceptIfRecipientDomainIs" + ) + + # Copy each property if it exists + foreach ($prop in $propertiesToKeep) { + if ($null -ne $Request.body.$prop) { + $policyObject[$prop] = $Request.body.$prop + } + } + + # Convert to JSON + $JSON = $policyObject | ConvertTo-Json -Depth 10 + + # Save the template to Azure Table Storage + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$GUID" + PartitionKey = 'SafeLinksTemplate' + } + + Write-LogMessage -Headers $Headers -API $APINAME -message "Created SafeLinks Policy Template $($policyObject.TemplateName) with GUID $GUID" -Sev Info + $body = [pscustomobject]@{'Results' = "Created SafeLinks Policy Template $($policyObject.TemplateName) with GUID $GUID" } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -Headers $Headers -API $APINAME -message "Failed to create SafeLinks policy template: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to create SafeLinks policy template: $($ErrorMessage.NormalizedError)" } + $StatusCode = [HttpStatusCode]::Forbidden + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicy.ps1 new file mode 100644 index 000000000000..c763a14a21e1 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicy.ps1 @@ -0,0 +1,217 @@ +using namespace System.Net + +function Invoke-EditSafeLinksPolicy { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.SpamFilter.ReadWrite + .DESCRIPTION + This function modifies an existing Safe Links policy and its associated rule. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + $PolicyName = $Request.Query.PolicyName ?? $Request.Body.PolicyName + $RuleName = $Request.Query.RuleName ?? $Request.Body.RuleName + + # Helper function to process array fields + function Process-ArrayField { + param ( + [Parameter(Mandatory = $false)] + $Field + ) + + if ($null -eq $Field) { return @() } + + # If already an array, process each item + if ($Field -is [array]) { + $result = [System.Collections.ArrayList]@() + foreach ($item in $Field) { + if ($item -is [string]) { + $result.Add($item) | Out-Null + } + elseif ($item -is [hashtable] -or $item -is [PSCustomObject]) { + # Extract value from object + if ($null -ne $item.value) { + $result.Add($item.value) | Out-Null + } + elseif ($null -ne $item.userPrincipalName) { + $result.Add($item.userPrincipalName) | Out-Null + } + elseif ($null -ne $item.id) { + $result.Add($item.id) | Out-Null + } + else { + $result.Add($item.ToString()) | Out-Null + } + } + else { + $result.Add($item.ToString()) | Out-Null + } + } + return $result.ToArray() + } + + # If it's a single object + if ($Field -is [hashtable] -or $Field -is [PSCustomObject]) { + if ($null -ne $Field.value) { return @($Field.value) } + if ($null -ne $Field.userPrincipalName) { return @($Field.userPrincipalName) } + if ($null -ne $Field.id) { return @($Field.id) } + } + + # If it's a string, return as an array with one item + if ($Field -is [string]) { + return @($Field) + } + + return @($Field) + } + + # Extract policy parameters from body + $EnableSafeLinksForEmail = $Request.Body.EnableSafeLinksForEmail + $EnableSafeLinksForTeams = $Request.Body.EnableSafeLinksForTeams + $EnableSafeLinksForOffice = $Request.Body.EnableSafeLinksForOffice + $TrackClicks = $Request.Body.TrackClicks + $AllowClickThrough = $Request.Body.AllowClickThrough + $ScanUrls = $Request.Body.ScanUrls + $EnableForInternalSenders = $Request.Body.EnableForInternalSenders + $DeliverMessageAfterScan = $Request.Body.DeliverMessageAfterScan + $DisableUrlRewrite = $Request.Body.DisableUrlRewrite + $DoNotRewriteUrls = Process-ArrayField -Field $Request.Body.DoNotRewriteUrls + $AdminDisplayName = $Request.Body.AdminDisplayName + $CustomNotificationText = $Request.Body.CustomNotificationText + $EnableOrganizationBranding = $Request.Body.EnableOrganizationBranding + + # Extract rule parameters from body + $Priority = $Request.Body.Priority + $Comments = $Request.Body.Comments + $State = $Request.Body.State + + # Process recipient-related parameters + $SentTo = Process-ArrayField -Field $Request.Body.SentTo + $SentToMemberOf = Process-ArrayField -Field $Request.Body.SentToMemberOf + $RecipientDomainIs = Process-ArrayField -Field $Request.Body.RecipientDomainIs + $ExceptIfSentTo = Process-ArrayField -Field $Request.Body.ExceptIfSentTo + $ExceptIfSentToMemberOf = Process-ArrayField -Field $Request.Body.ExceptIfSentToMemberOf + $ExceptIfRecipientDomainIs = Process-ArrayField -Field $Request.Body.ExceptIfRecipientDomainIs + + $Results = [System.Collections.ArrayList]@() + $hasPolicyParams = $false + $hasRuleParams = $false + $hasRuleOperation = $false + $ruleMessages = [System.Collections.ArrayList]@() + + try { + # Check which types of updates we need to perform + # PART 1: Build and check policy parameters + $policyParams = @{ + Identity = $PolicyName + } + + # Only add parameters that are explicitly provided + if ($null -ne $EnableSafeLinksForEmail) { $policyParams.Add('EnableSafeLinksForEmail', $EnableSafeLinksForEmail); $hasPolicyParams = $true } + if ($null -ne $EnableSafeLinksForTeams) { $policyParams.Add('EnableSafeLinksForTeams', $EnableSafeLinksForTeams); $hasPolicyParams = $true } + if ($null -ne $EnableSafeLinksForOffice) { $policyParams.Add('EnableSafeLinksForOffice', $EnableSafeLinksForOffice); $hasPolicyParams = $true } + if ($null -ne $TrackClicks) { $policyParams.Add('TrackClicks', $TrackClicks); $hasPolicyParams = $true } + if ($null -ne $AllowClickThrough) { $policyParams.Add('AllowClickThrough', $AllowClickThrough); $hasPolicyParams = $true } + if ($null -ne $ScanUrls) { $policyParams.Add('ScanUrls', $ScanUrls); $hasPolicyParams = $true } + if ($null -ne $EnableForInternalSenders) { $policyParams.Add('EnableForInternalSenders', $EnableForInternalSenders); $hasPolicyParams = $true } + if ($null -ne $DeliverMessageAfterScan) { $policyParams.Add('DeliverMessageAfterScan', $DeliverMessageAfterScan); $hasPolicyParams = $true } + if ($null -ne $DisableUrlRewrite) { $policyParams.Add('DisableUrlRewrite', $DisableUrlRewrite); $hasPolicyParams = $true } + if ($null -ne $DoNotRewriteUrls -and $DoNotRewriteUrls.Count -gt 0) { $policyParams.Add('DoNotRewriteUrls', $DoNotRewriteUrls); $hasPolicyParams = $true } + if ($null -ne $AdminDisplayName) { $policyParams.Add('AdminDisplayName', $AdminDisplayName); $hasPolicyParams = $true } + if ($null -ne $CustomNotificationText) { $policyParams.Add('CustomNotificationText', $CustomNotificationText); $hasPolicyParams = $true } + if ($null -ne $EnableOrganizationBranding) { $policyParams.Add('EnableOrganizationBranding', $EnableOrganizationBranding); $hasPolicyParams = $true } + + # PART 2: Build and check rule parameters + $ruleParams = @{ + Identity = $RuleName + } + + # Add parameters that are explicitly provided + if ($null -ne $Comments) { $ruleParams.Add('Comments', $Comments); $hasRuleParams = $true } + if ($null -ne $Priority) { $ruleParams.Add('Priority', $Priority); $hasRuleParams = $true } + if ($SentTo.Count -gt 0) { $ruleParams.Add('SentTo', $SentTo); $hasRuleParams = $true } + if ($SentToMemberOf.Count -gt 0) { $ruleParams.Add('SentToMemberOf', $SentToMemberOf); $hasRuleParams = $true } + if ($RecipientDomainIs.Count -gt 0) { $ruleParams.Add('RecipientDomainIs', $RecipientDomainIs); $hasRuleParams = $true } + if ($ExceptIfSentTo.Count -gt 0) { $ruleParams.Add('ExceptIfSentTo', $ExceptIfSentTo); $hasRuleParams = $true } + if ($ExceptIfSentToMemberOf.Count -gt 0) { $ruleParams.Add('ExceptIfSentToMemberOf', $ExceptIfSentToMemberOf); $hasRuleParams = $true } + if ($ExceptIfRecipientDomainIs.Count -gt 0) { $ruleParams.Add('ExceptIfRecipientDomainIs', $ExceptIfRecipientDomainIs); $hasRuleParams = $true } + + # Now perform only the necessary operations + + # PART 1: Update policy if needed + if ($hasPolicyParams) { + $ExoPolicyRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'Set-SafeLinksPolicy' + cmdParams = $policyParams + useSystemMailbox = $true + } + + $null = New-ExoRequest @ExoPolicyRequestParam + $Results.Add("Successfully updated SafeLinks policy '$PolicyName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Updated SafeLinks policy '$PolicyName'" -Sev 'Info' + } + + # PART 2: Update rule if needed + if ($hasRuleParams) { + $ExoRuleRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'Set-SafeLinksRule' + cmdParams = $ruleParams + useSystemMailbox = $true + } + + $null = New-ExoRequest @ExoRuleRequestParam + $hasRuleOperation = $true + $ruleMessages.Add("updated properties") | Out-Null + } + + # Handle enable/disable if needed + if ($null -ne $State) { + $EnableCmdlet = $State ? 'Enable-SafeLinksRule' : 'Disable-SafeLinksRule' + $EnableRequestParam = @{ + tenantid = $TenantFilter + cmdlet = $EnableCmdlet + cmdParams = @{ + Identity = $RuleName + } + useSystemMailbox = $true + } + + $null = New-ExoRequest @EnableRequestParam + $hasRuleOperation = $true + $State = $State ? "enabled" : "disabled" + $ruleMessages.Add($State) | Out-Null + } + + # Add combined rule message if any rule operations were performed + if ($hasRuleOperation) { + $ruleOperations = $ruleMessages -join " and " + $Results.Add("Successfully $ruleOperations SafeLinks rule '$RuleName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "$ruleOperations SafeLinks rule '$RuleName'" -Sev 'Info' + } + + $StatusCode = [HttpStatusCode]::OK + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Results.Add("Failed updating SafeLinks configuration '$PolicyName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed updating SafeLinks configuration '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{Results = $Results } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicyTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicyTemplate.ps1 new file mode 100644 index 000000000000..3e6ece129dc8 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-EditSafeLinksPolicyTemplate.ps1 @@ -0,0 +1,89 @@ +using namespace System.Net + +Function Invoke-EditSafeLinksPolicyTemplate { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.ReadWrite + .DESCRIPTION + This function updates an existing Safe Links policy template. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev Debug + + try { + $ID = $Request.Body.ID + + if (-not $ID) { + throw "Template ID is required" + } + + # Check if template exists + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'SafeLinksTemplate' and RowKey eq '$ID'" + $ExistingTemplate = Get-CIPPAzDataTableEntity @Table -Filter $Filter + + if (-not $ExistingTemplate) { + throw "Template with ID '$ID' not found" + } + + # Create a new ordered hashtable to store selected properties + $policyObject = [ordered]@{} + + # Set name and comments + $policyObject["TemplateName"] = $Request.body.TemplateName + $policyObject["TemplateDescription"] = $Request.body.TemplateDescription + + # Copy specific properties we want to keep + $propertiesToKeep = @( + # Policy properties + "PolicyName", "EnableSafeLinksForEmail", "EnableSafeLinksForTeams", "EnableSafeLinksForOffice", + "TrackClicks", "AllowClickThrough", "ScanUrls", "EnableForInternalSenders", + "DeliverMessageAfterScan", "DisableUrlRewrite", "DoNotRewriteUrls", + "AdminDisplayName", "CustomNotificationText", "EnableOrganizationBranding", + + # Rule properties + "RuleName", "Priority", "State", "Comments", + "SentTo", "SentToMemberOf", "RecipientDomainIs", + "ExceptIfSentTo", "ExceptIfSentToMemberOf", "ExceptIfRecipientDomainIs" + ) + + # Copy each property if it exists + foreach ($prop in $propertiesToKeep) { + if ($null -ne $Request.body.$prop) { + $policyObject[$prop] = $Request.body.$prop + } + } + + # Convert to JSON + $JSON = $policyObject | ConvertTo-Json -Depth 10 + + # Update the template in Azure Table Storage + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$ID" + PartitionKey = 'SafeLinksTemplate' + } + + Write-LogMessage -Headers $Headers -API $APINAME -message "Updated SafeLinks Policy Template $($policyObject.TemplateName) with ID $ID" -Sev Info + $body = [pscustomobject]@{'Results' = "Updated SafeLinks Policy Template $($policyObject.TemplateName) with ID $ID" } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -Headers $Headers -API $APINAME -message "Failed to update SafeLinks policy template: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to update SafeLinks policy template: $($ErrorMessage.NormalizedError)" } + $StatusCode = [HttpStatusCode]::Forbidden + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 new file mode 100644 index 000000000000..6bd622381129 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 @@ -0,0 +1,94 @@ +using namespace System.Net +function Invoke-ExecDeleteSafeLinksPolicy { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.SpamFilter.ReadWrite + .DESCRIPTION + This function deletes a Safe Links rule and its associated policy. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + $RuleName = $Request.Query.RuleName ?? $Request.Body.RuleName + $PolicyName = $Request.Query.PolicyName ?? $Request.Body.PolicyName + + $ResultMessages = [System.Collections.ArrayList]@() + + try { + # Only try to delete the rule if a name was provided + if ($RuleName) { + try { + $ExoRequestRuleParam = @{ + tenantid = $TenantFilter + cmdlet = 'Remove-SafeLinksRule' + cmdParams = @{ + Identity = $RuleName + Confirm = $false + } + useSystemMailbox = $true + } + $null = New-ExoRequest @ExoRequestRuleParam + $ResultMessages.Add("Successfully deleted SafeLinks rule '$RuleName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Successfully deleted SafeLinks rule '$RuleName'" -Sev 'Info' + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $ResultMessages.Add("Failed to delete SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + } + } + else { + $ResultMessages.Add("No rule name provided, skipping rule deletion") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "No rule name provided, skipping rule deletion" -Sev 'Info' + } + + # Only try to delete the policy if a name was provided + if ($PolicyName) { + try { + $ExoRequestPolicyParam = @{ + tenantid = $TenantFilter + cmdlet = 'Remove-SafeLinksPolicy' + cmdParams = @{ + Identity = $PolicyName + Confirm = $false + } + useSystemMailbox = $true + } + $null = New-ExoRequest @ExoRequestPolicyParam + $ResultMessages.Add("Successfully deleted SafeLinks policy '$PolicyName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Successfully deleted SafeLinks policy '$PolicyName'" -Sev 'Info' + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $ResultMessages.Add("Failed to delete SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + } + } + else { + $ResultMessages.Add("No policy name provided, skipping policy deletion") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "No policy name provided, skipping policy deletion" -Sev 'Info' + } + + # Combine all result messages + $Result = $ResultMessages -join " | " + $StatusCode = [HttpStatusCode]::OK + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "An unexpected error occurred: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{Results = $Result } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 new file mode 100644 index 000000000000..91945d8736ca --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 @@ -0,0 +1,228 @@ +using namespace System.Net + +function Invoke-ExecNewSafeLinksPolicy { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.SpamFilter.ReadWrite + .DESCRIPTION + This function creates a new Safe Links policy and an associated rule. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + + # Extract policy settings from body + $PolicyName = $Request.Body.PolicyName + $EnableSafeLinksForEmail = $Request.Body.EnableSafeLinksForEmail + $EnableSafeLinksForTeams = $Request.Body.EnableSafeLinksForTeams + $EnableSafeLinksForOffice = $Request.Body.EnableSafeLinksForOffice + $TrackClicks = $Request.Body.TrackClicks + $AllowClickThrough = $Request.Body.AllowClickThrough + $ScanUrls = $Request.Body.ScanUrls + $EnableForInternalSenders = $Request.Body.EnableForInternalSenders + $DeliverMessageAfterScan = $Request.Body.DeliverMessageAfterScan + $DisableUrlRewrite = $Request.Body.DisableUrlRewrite + $DoNotRewriteUrls = $Request.Body.DoNotRewriteUrls + $AdminDisplayName = $Request.Body.AdminDisplayName + $CustomNotificationText = $Request.Body.CustomNotificationText + $EnableOrganizationBranding = $Request.Body.EnableOrganizationBranding + + # Extract rule settings from body + $Priority = $Request.Body.Priority + $Comments = $Request.Body.Comments + $State = $Request.Body.State + $RuleName = $Request.Body.RuleName + + # Extract recipient fields and handle different input formats + $SentTo = $Request.Body.SentTo + $SentToMemberOf = $Request.Body.SentToMemberOf + $RecipientDomainIs = $Request.Body.RecipientDomainIs + $ExceptIfSentTo = $Request.Body.ExceptIfSentTo + $ExceptIfSentToMemberOf = $Request.Body.ExceptIfSentToMemberOf + $ExceptIfRecipientDomainIs = $Request.Body.ExceptIfRecipientDomainIs + + function Test-PolicyExists { + param($TenantFilter, $PolicyName) + $ExistingPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksPolicy' -useSystemMailbox $true + return $ExistingPolicies | Where-Object { $_.Name -eq $PolicyName } + } + + function Test-RuleExists { + param($TenantFilter, $RuleName) + $ExistingRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksRule' -useSystemMailbox $true + return $ExistingRules | Where-Object { $_.Name -eq $RuleName } + } + + # Helper function to process array fields + function Process-ArrayField { + param ( + [Parameter(Mandatory = $false)] + $Field + ) + + if ($null -eq $Field) { return @() } + + # If already an array, process each item + if ($Field -is [array]) { + $result = [System.Collections.ArrayList]@() + foreach ($item in $Field) { + if ($item -is [string]) { + $result.Add($item) | Out-Null + } + elseif ($item -is [hashtable] -or $item -is [PSCustomObject]) { + # Extract value from object + if ($null -ne $item.value) { + $result.Add($item.value) | Out-Null + } + elseif ($null -ne $item.userPrincipalName) { + $result.Add($item.userPrincipalName) | Out-Null + } + elseif ($null -ne $item.id) { + $result.Add($item.id) | Out-Null + } + else { + $result.Add($item.ToString()) | Out-Null + } + } + else { + $result.Add($item.ToString()) | Out-Null + } + } + return $result.ToArray() + } + + # If it's a single object + if ($Field -is [hashtable] -or $Field -is [PSCustomObject]) { + if ($null -ne $Field.value) { return @($Field.value) } + if ($null -ne $Field.userPrincipalName) { return @($Field.userPrincipalName) } + if ($null -ne $Field.id) { return @($Field.id) } + } + + # If it's a string, return as an array with one item + if ($Field -is [string]) { + return @($Field) + } + + return @($Field) + } + + # Process all array fields + $SentTo = Process-ArrayField -Field $SentTo + $SentToMemberOf = Process-ArrayField -Field $SentToMemberOf + $RecipientDomainIs = Process-ArrayField -Field $RecipientDomainIs + $ExceptIfSentTo = Process-ArrayField -Field $ExceptIfSentTo + $ExceptIfSentToMemberOf = Process-ArrayField -Field $ExceptIfSentToMemberOf + $ExceptIfRecipientDomainIs = Process-ArrayField -Field $ExceptIfRecipientDomainIs + $DoNotRewriteUrls = Process-ArrayField -Field $DoNotRewriteUrls + + try { + # Check if policy already exists + if (Test-PolicyExists -TenantFilter $TenantFilter -PolicyName $PolicyName) { + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -Sev 'Warning' + return "Policy '$PolicyName' already exists in tenant $TenantFilter" + } + + # Check if rule already exists + if (Test-RuleExists -TenantFilter $TenantFilter -RuleName $RuleName) { + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -Sev 'Warning' + return "Rule '$RuleName' already exists in tenant $TenantFilter" + } + + # Build command parameters for policy + $policyParams = @{ + Name = $PolicyName + } + + # Only add parameters that are explicitly provided + if ($null -ne $EnableSafeLinksForEmail) { $policyParams.Add('EnableSafeLinksForEmail', $EnableSafeLinksForEmail) } + if ($null -ne $EnableSafeLinksForTeams) { $policyParams.Add('EnableSafeLinksForTeams', $EnableSafeLinksForTeams) } + if ($null -ne $EnableSafeLinksForOffice) { $policyParams.Add('EnableSafeLinksForOffice', $EnableSafeLinksForOffice) } + if ($null -ne $TrackClicks) { $policyParams.Add('TrackClicks', $TrackClicks) } + if ($null -ne $AllowClickThrough) { $policyParams.Add('AllowClickThrough', $AllowClickThrough) } + if ($null -ne $ScanUrls) { $policyParams.Add('ScanUrls', $ScanUrls) } + if ($null -ne $EnableForInternalSenders) { $policyParams.Add('EnableForInternalSenders', $EnableForInternalSenders) } + if ($null -ne $DeliverMessageAfterScan) { $policyParams.Add('DeliverMessageAfterScan', $DeliverMessageAfterScan) } + if ($null -ne $DisableUrlRewrite) { $policyParams.Add('DisableUrlRewrite', $DisableUrlRewrite) } + if ($null -ne $DoNotRewriteUrls -and $DoNotRewriteUrls.Count -gt 0) { $policyParams.Add('DoNotRewriteUrls', $DoNotRewriteUrls) } + if ($null -ne $AdminDisplayName) { $policyParams.Add('AdminDisplayName', $AdminDisplayName) } + if ($null -ne $CustomNotificationText) { $policyParams.Add('CustomNotificationText', $CustomNotificationText) } + if ($null -ne $EnableOrganizationBranding) { $policyParams.Add('EnableOrganizationBranding', $EnableOrganizationBranding) } + + $ExoPolicyRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'New-SafeLinksPolicy' + cmdParams = $policyParams + useSystemMailbox = $true + } + + $null = New-ExoRequest @ExoPolicyRequestParam + $PolicyResult = "Successfully created new SafeLinks policy '$PolicyName'" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $PolicyResult -Sev 'Info' + + # Build command parameters for rule + $ruleParams = @{ + Name = $RuleName + SafeLinksPolicy = $PolicyName + } + + # Only add parameters that are explicitly provided + if ($null -ne $Priority) { $ruleParams.Add('Priority', $Priority) } + if ($null -ne $Comments) { $ruleParams.Add('Comments', $Comments) } + if ($null -ne $SentTo -and $SentTo.Count -gt 0) { $ruleParams.Add('SentTo', $SentTo) } + if ($null -ne $SentToMemberOf -and $SentToMemberOf.Count -gt 0) { $ruleParams.Add('SentToMemberOf', $SentToMemberOf) } + if ($null -ne $RecipientDomainIs -and $RecipientDomainIs.Count -gt 0) { $ruleParams.Add('RecipientDomainIs', $RecipientDomainIs) } + if ($null -ne $ExceptIfSentTo -and $ExceptIfSentTo.Count -gt 0) { $ruleParams.Add('ExceptIfSentTo', $ExceptIfSentTo) } + if ($null -ne $ExceptIfSentToMemberOf -and $ExceptIfSentToMemberOf.Count -gt 0) { $ruleParams.Add('ExceptIfSentToMemberOf', $ExceptIfSentToMemberOf) } + if ($null -ne $ExceptIfRecipientDomainIs -and $ExceptIfRecipientDomainIs.Count -gt 0) { $ruleParams.Add('ExceptIfRecipientDomainIs', $ExceptIfRecipientDomainIs) } + + $ExoRuleRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'New-SafeLinksRule' + cmdParams = $ruleParams + useSystemMailbox = $true + } + + $null = New-ExoRequest @ExoRuleRequestParam + + # If State is specified, enable or disable the rule + if ($null -ne $State) { + $EnableCmdlet = $State ? 'Enable-SafeLinksRule' : 'Disable-SafeLinksRule' + $EnableRequestParam = @{ + tenantid = $TenantFilter + cmdlet = $EnableCmdlet + cmdParams = @{ + Identity = $RuleName + } + useSystemMailbox = $true + } + + $null = New-ExoRequest @EnableRequestParam + } + + $RuleResult = "Successfully created new SafeLinks rule '$RuleName'" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $RuleResult -Sev 'Info' + + $Result = "Successfully created new SafeLinks policy '$PolicyName'and rule '$RuleName'" + $StatusCode = [HttpStatusCode]::OK + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed creating new SafeLinks policy '$PolicyName'and rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{Results = $Result } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 new file mode 100644 index 000000000000..a18d03c2aba1 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 @@ -0,0 +1,201 @@ +using namespace System.Net +Function Invoke-ListSafeLinksPolicy { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Security.SafeLinksPolicy.Read + .DESCRIPTION + This function is used to list the Safe Links policies in the tenant, including unmatched rules and policies. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Tenantfilter = $request.Query.tenantfilter + + try { + $Policies = New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-SafeLinksPolicy' | Select-Object -Property * -ExcludeProperty '*@odata.type' , '*@data.type' + $Rules = New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-SafeLinksRule' | Select-Object -Property * -ExcludeProperty '*@odata.type' , '*@data.type' + $BuiltInRules = New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-EOPProtectionPolicyRule' | Select-Object -Property * -ExcludeProperty '*@odata.type' , '*@data.type' + + # Track matched items to identify orphans + $MatchedRules = [System.Collections.Generic.HashSet[string]]::new() + $MatchedPolicies = [System.Collections.Generic.HashSet[string]]::new() + $MatchedBuiltInRules = [System.Collections.Generic.HashSet[string]]::new() + $Output = [System.Collections.Generic.List[PSCustomObject]]::new() + + # First pass: Process policies with their associated rules + foreach ($policy in $Policies) { + $policyName = $policy.Name + $MatchedPolicies.Add($policyName) | Out-Null + + # Find associated rule (single lookup per policy) + $associatedRule = $null + foreach ($rule in $Rules) { + if ($rule.SafeLinksPolicy -eq $policyName) { + $associatedRule = $rule + $MatchedRules.Add($rule.Name) | Out-Null + break + } + } + + # Find matching built-in rule (single lookup per policy) + $matchingBuiltInRule = $null + foreach ($builtInRule in $BuiltInRules) { + if ($policyName -like "$($builtInRule.Name)*") { + $matchingBuiltInRule = $builtInRule + $MatchedBuiltInRules.Add($builtInRule.Name) | Out-Null + break + } + } + + # Create output object for matched policy + $OutputItem = [PSCustomObject]@{ + # Copy all original policy properties + Name = $policy.Name + AdminDisplayName = $policy.AdminDisplayName + EnableSafeLinksForEmail = $policy.EnableSafeLinksForEmail + EnableSafeLinksForTeams = $policy.EnableSafeLinksForTeams + EnableSafeLinksForOffice = $policy.EnableSafeLinksForOffice + TrackClicks = $policy.TrackClicks + AllowClickThrough = $policy.AllowClickThrough + ScanUrls = $policy.ScanUrls + EnableForInternalSenders = $policy.EnableForInternalSenders + DeliverMessageAfterScan = $policy.DeliverMessageAfterScan + DisableUrlRewrite = $policy.DisableUrlRewrite + DoNotRewriteUrls = $policy.DoNotRewriteUrls + CustomNotificationText = $policy.CustomNotificationText + EnableOrganizationBranding = $policy.EnableOrganizationBranding + + # Calculated properties + PolicyName = $policyName + RuleName = $associatedRule.Name + Priority = if ($matchingBuiltInRule) { $matchingBuiltInRule.Priority } else { $associatedRule.Priority } + State = if ($matchingBuiltInRule) { $matchingBuiltInRule.State } else { $associatedRule.State } + SentTo = $associatedRule.SentTo + SentToMemberOf = $associatedRule.SentToMemberOf + RecipientDomainIs = $associatedRule.RecipientDomainIs + ExceptIfSentTo = $associatedRule.ExceptIfSentTo + ExceptIfSentToMemberOf = $associatedRule.ExceptIfSentToMemberOf + ExceptIfRecipientDomainIs = $associatedRule.ExceptIfRecipientDomainIs + Description = $policy.AdminDisplayName + IsBuiltIn = ($matchingBuiltInRule -ne $null) + IsValid = $policy.IsValid + ConfigurationStatus = if ($associatedRule) { "Complete" } else { "Policy Only (Missing Rule)" } + } + $Output.Add($OutputItem) + } + + # Second pass: Add unmatched rules (orphaned rules without policies) + foreach ($rule in $Rules) { + if (-not $MatchedRules.Contains($rule.Name)) { + # This rule doesn't have a matching policy + $OutputItem = [PSCustomObject]@{ + # Policy properties (null since no policy exists) + Name = $null + AdminDisplayName = $null + EnableSafeLinksForEmail = $null + EnableSafeLinksForTeams = $null + EnableSafeLinksForOffice = $null + TrackClicks = $null + AllowClickThrough = $null + ScanUrls = $null + EnableForInternalSenders = $null + DeliverMessageAfterScan = $null + DisableUrlRewrite = $null + DoNotRewriteUrls = $null + CustomNotificationText = $null + EnableOrganizationBranding = $null + + # Rule properties + PolicyName = $rule.SafeLinksPolicy + RuleName = $rule.Name + Priority = $rule.Priority + State = $rule.State + SentTo = $rule.SentTo + SentToMemberOf = $rule.SentToMemberOf + RecipientDomainIs = $rule.RecipientDomainIs + ExceptIfSentTo = $rule.ExceptIfSentTo + ExceptIfSentToMemberOf = $rule.ExceptIfSentToMemberOf + ExceptIfRecipientDomainIs = $rule.ExceptIfRecipientDomainIs + Description = $rule.Comments + IsBuiltIn = $false + ConfigurationStatus = "Rule Only (Missing Policy: $($rule.SafeLinksPolicy))" + } + $Output.Add($OutputItem) + } + } + + # Third pass: Add unmatched built-in rules + foreach ($builtInRule in $BuiltInRules) { + if (-not $MatchedBuiltInRules.Contains($builtInRule.Name)) { + # Check if this built-in rule might be SafeLinks related + if ($builtInRule.Name -like "*SafeLinks*" -or $builtInRule.Name -like "*Safe*Links*") { + $OutputItem = [PSCustomObject]@{ + # Policy properties (null since no policy exists) + Name = $null + AdminDisplayName = $null + EnableSafeLinksForEmail = $null + EnableSafeLinksForTeams = $null + EnableSafeLinksForOffice = $null + TrackClicks = $null + AllowClickThrough = $null + ScanUrls = $null + EnableForInternalSenders = $null + DeliverMessageAfterScan = $null + DisableUrlRewrite = $null + DoNotRewriteUrls = $null + CustomNotificationText = $null + EnableOrganizationBranding = $null + + # Built-in rule properties + PolicyName = $null + RuleName = $builtInRule.Name + Priority = $builtInRule.Priority + State = $builtInRule.State + SentTo = $builtInRule.SentTo + SentToMemberOf = $builtInRule.SentToMemberOf + RecipientDomainIs = $builtInRule.RecipientDomainIs + ExceptIfSentTo = $builtInRule.ExceptIfSentTo + ExceptIfSentToMemberOf = $builtInRule.ExceptIfSentToMemberOf + ExceptIfRecipientDomainIs = $builtInRule.ExceptIfRecipientDomainIs + Description = $builtInRule.Comments + IsBuiltIn = $true + ConfigurationStatus = "Built-In Rule Only (No Associated Policy)" + } + $Output.Add($OutputItem) + } + } + } + + # Sort output by ConfigurationStatus and Name for better organization + $SortedOutput = $Output.ToArray() | Sort-Object ConfigurationStatus, Name, RuleName + + # Generate summary statistics + $CompleteConfigs = ($SortedOutput | Where-Object { $_.ConfigurationStatus -eq "Complete" }).Count + $PolicyOnlyConfigs = ($SortedOutput | Where-Object { $_.ConfigurationStatus -like "*Policy Only*" }).Count + $RuleOnlyConfigs = ($SortedOutput | Where-Object { $_.ConfigurationStatus -like "*Rule Only*" }).Count + $BuiltInOnlyConfigs = ($SortedOutput | Where-Object { $_.ConfigurationStatus -like "*Built-In Rule Only*" }).Count + + if ($PolicyOnlyConfigs -gt 0 -or $RuleOnlyConfigs -gt 0) { + Write-LogMessage -headers $Headers -API $APIName -message "Found $($PolicyOnlyConfigs + $RuleOnlyConfigs) orphaned SafeLinks configurations that may need attention" -Sev 'Warning' + } + + $StatusCode = [HttpStatusCode]::OK + $FinalOutput = $SortedOutput + } + catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -headers $Headers -API $APIName -message "Error retrieving Safe Links policies: $ErrorMessage" -Sev 'Error' + $StatusCode = [HttpStatusCode]::Forbidden + $FinalOutput = $ErrorMessage + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $FinalOutput + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 new file mode 100644 index 000000000000..c0b11a9c3ad9 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 @@ -0,0 +1,106 @@ +using namespace System.Net +function Invoke-ListSafeLinksPolicyDetails { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.SpamFilter.Read + .DESCRIPTION + This function retrieves details for a specific Safe Links policy and rule. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + $PolicyName = $Request.Query.PolicyName ?? $Request.Body.PolicyName + $RuleName = $Request.Query.RuleName ?? $Request.Body.RuleName + + $Result = @{} + $LogMessages = [System.Collections.ArrayList]@() + + try { + # Get policy details if PolicyName is provided + if ($PolicyName) { + try { + $PolicyRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'Get-SafeLinksPolicy' + cmdParams = @{ + Identity = $PolicyName + } + useSystemMailbox = $true + } + $PolicyDetails = New-ExoRequest @PolicyRequestParam + $Result.Policy = $PolicyDetails + $Result.PolicyName = $PolicyDetails.Name + $LogMessages.Add("Successfully retrieved details for SafeLinks policy '$PolicyName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Successfully retrieved details for SafeLinks policy '$PolicyName'" -Sev 'Info' + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $LogMessages.Add("Failed to retrieve details for SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + $Result.PolicyError = "Failed to retrieve: $($ErrorMessage.NormalizedError)" + } + } + else { + $LogMessages.Add("No policy name provided, skipping policy retrieval") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "No policy name provided, skipping policy retrieval" -Sev 'Info' + } + + # Get rule details if RuleName is provided + if ($RuleName) { + try { + $RuleRequestParam = @{ + tenantid = $TenantFilter + cmdlet = 'Get-SafeLinksRule' + cmdParams = @{ + Identity = $RuleName + } + useSystemMailbox = $true + } + $RuleDetails = New-ExoRequest @RuleRequestParam + $Result.Rule = $RuleDetails + $Result.RuleName = $RuleDetails.Name + $LogMessages.Add("Successfully retrieved details for SafeLinks rule '$RuleName'") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Successfully retrieved details for SafeLinks rule '$RuleName'" -Sev 'Info' + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $LogMessages.Add("Failed to retrieve details for SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + $Result.RuleError = "Failed to retrieve: $($ErrorMessage.NormalizedError)" + } + } + else { + $LogMessages.Add("No rule name provided, skipping rule retrieval") | Out-Null + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "No rule name provided, skipping rule retrieval" -Sev 'Info' + } + + # If no valid retrievals were performed, throw an error + if (-not ($Result.Policy -or $Result.Rule)) { + throw "No valid policy or rule details could be retrieved" + } + + # Set success status + $StatusCode = [HttpStatusCode]::OK + $Result.Message = $LogMessages -join " | " + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Operation failed: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{Results = $Result } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplateDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplateDetails.ps1 new file mode 100644 index 000000000000..26f19bceb065 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplateDetails.ps1 @@ -0,0 +1,57 @@ +using namespace System.Net +Function Invoke-ListSafeLinksPolicyTemplateDetails { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.Read + .DESCRIPTION + This function retrieves details for a specific Safe Links policy template. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Get the template ID from query parameters + $ID = $Request.Query.ID ?? $Request.Body.ID + + $Result = @{} + + try { + if (-not $ID) { + throw "Template ID is required" + } + + # Get the specific template from Azure Table Storage + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'SafeLinksTemplate' and RowKey eq '$ID'" + $Template = Get-CIPPAzDataTableEntity @Table -Filter $Filter + + if (-not $Template) { + throw "Template with ID '$ID' not found" + } + + # Parse the JSON data and add metadata + $TemplateData = $Template.JSON | ConvertFrom-Json + $TemplateData | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $Template.RowKey -Force + + $Result = $TemplateData + $StatusCode = [HttpStatusCode]::OK + Write-LogMessage -headers $Headers -API $APIName -message "Successfully retrieved template details for ID '$ID'" -Sev 'Info' + } + catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to retrieve template details for ID '$ID'. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' + $StatusCode = [HttpStatusCode]::InternalServerError + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{Results = $Result } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplates.ps1 new file mode 100644 index 000000000000..5c4477985199 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyTemplates.ps1 @@ -0,0 +1,39 @@ +using namespace System.Net +Function Invoke-ListSafeLinksPolicyTemplates { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Table = Get-CippTable -tablename 'templates' + $Templates = Get-ChildItem 'Config\*.SafeLinksTemplate.json' | ForEach-Object { + $Entity = @{ + JSON = "$(Get-Content $_)" + RowKey = "$($_.name)" + PartitionKey = 'SafeLinksTemplate' + GUID = "$($_.name)" + } + Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force + } + #List policies + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'SafeLinksTemplate'" + $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) | ForEach-Object { + $GUID = $_.RowKey + $data = $_.JSON | ConvertFrom-Json + $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $GUID + $data + } + if ($Request.query.ID) { $Templates = $Templates | Where-Object -Property RowKey -EQ $Request.query.id } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Templates) + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 new file mode 100644 index 000000000000..676b72e4b17e --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 @@ -0,0 +1,35 @@ +using namespace System.Net + +Function Invoke-RemoveSafeLinksPolicyTemplate { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Exchange.SafeLinks.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $User = $Request.Headers + Write-LogMessage -Headers $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $ID = $request.query.ID ?? $request.body.ID + try { + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'SafeLinksTemplate' and RowKey eq '$id'" + $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey + Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + $Result = "Removed SafeLinks Policy Template with ID $ID." + Write-LogMessage -Headers $User -API $APINAME -message $Result -Sev 'Info' + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to remove SafeLinks Policy template with ID $ID. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -Headers $User -API $APINAME -message $Result -Sev 'Error' -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::Forbidden + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ Results = $Result } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 index e8f19acf0534..13eb06e766b1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 @@ -14,21 +14,23 @@ Function Invoke-AddSite { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $SharePointObj = $Request.body + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.tenantFilter + $SharePointObj = $Request.Body try { - $SharePointSite = New-CIPPSharepointSite -SiteName $SharePointObj.siteName -SiteDescription $SharePointObj.siteDescription -SiteOwner $SharePointObj.siteOwner.value -TemplateName $SharePointObj.templateName.value -SiteDesign $SharePointObj.siteDesign.value -SensitivityLabel $SharePointObj.sensitivityLabel -TenantFilter $SharePointObj.tenantFilter - $body = [pscustomobject]@{'Results' = $SharePointSite } + $Result = New-CIPPSharepointSite -Headers $Headers -SiteName $SharePointObj.siteName -SiteDescription $SharePointObj.siteDescription -SiteOwner $SharePointObj.siteOwner.value -TemplateName $SharePointObj.templateName.value -SiteDesign $SharePointObj.siteDesign.value -SensitivityLabel $SharePointObj.sensitivityLabel -TenantFilter $TenantFilter + $StatusCode = [HttpStatusCode]::OK } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($userobj.tenantid) -message "Adding SharePoint Site failed. Error: $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed. Error message: $($_.Exception.Message)" } + $StatusCode = [HttpStatusCode]::InternalServerError + $Result = "Failed to create SharePoint Site: $($_.Exception.Message)" } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Body + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSiteBulk.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSiteBulk.ps1 index 8ca292b2c52f..bbb63aebc399 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSiteBulk.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSiteBulk.ps1 @@ -15,22 +15,20 @@ Function Invoke-AddSiteBulk { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $Results = [System.Collections.ArrayList]@() + $Results = [System.Collections.Generic.List[System.Object]]::new() - foreach ($sharepointObj in $Request.Body.bulkSites) { + foreach ($sharePointObj in $Request.Body.bulkSites) { try { - $SharePointSite = New-CIPPSharepointSite -SiteName $SharePointObj.siteName -SiteDescription $SharePointObj.siteDescription -SiteOwner $SharePointObj.siteOwner -TemplateName $SharePointObj.templateName -SiteDesign $SharePointObj.siteDesign -SensitivityLabel $SharePointObj.sensitivityLabel -TenantFilter $Request.body.TenantFilter - $Results.add($SharePointSite) + $SharePointSite = New-CIPPSharepointSite -Headers $Headers -SiteName $sharePointObj.siteName -SiteDescription $sharePointObj.siteDescription -SiteOwner $sharePointObj.siteOwner -TemplateName $sharePointObj.templateName -SiteDesign $sharePointObj.siteDesign -SensitivityLabel $sharePointObj.sensitivityLabel -TenantFilter $Request.body.tenantFilter + $Results.Add($SharePointSite) } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($userobj.tenantid) -message "Adding SharePoint Site failed. Error: $($_.Exception.Message)" -Sev 'Error' - $Results.add("Failed to create $($sharepointObj.siteName) Error message: $($_.Exception.Message)") + $Results.Add("Failed to create $($sharePointObj.siteName) Error message: $($_.Exception.Message)") } } - $Body = [pscustomobject]@{'Results' = $Results } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $Body + Body = @{'Results' = $Results } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 index b022aee68d46..2e1835cba80e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 @@ -14,19 +14,26 @@ Function Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $tenantFilter = $Request.Body.TenantFilter + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.tenantFilter + $AssignedTo = $Request.Body.AssignedTo + $PhoneNumber = $Request.Body.PhoneNumber + $PhoneNumberType = $Request.Body.PhoneNumberType + try { - $null = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Remove-CsPhoneNumberAssignment' -CmdParams @{Identity = $Request.Body.AssignedTo; PhoneNumber = $Request.Body.PhoneNumber; PhoneNumberType = $Request.Body.PhoneNumberType; ErrorAction = 'stop'} - $Results = [pscustomobject]@{'Results' = "Successfully unassigned $($Request.Body.PhoneNumber) from $($Request.Body.AssignedTo)"} - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Info' + $null = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Remove-CsPhoneNumberAssignment' -CmdParams @{Identity = $AssignedTo; PhoneNumber = $PhoneNumber; PhoneNumberType = $PhoneNumberType; ErrorAction = 'Stop' } + $Result = "Successfully unassigned $PhoneNumber from $AssignedTo" + Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Info' + $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $Results = [pscustomobject]@{'Results' = $ErrorMessage} - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to unassign $PhoneNumber from $AssignedTo. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 index 0894c81fbdf1..3d8e9947ff77 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 @@ -13,28 +13,34 @@ Function Invoke-ExecSetSharePointMember { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $TenantFilter = $Request.body.tenantFilter - - - - if ($Request.body.SharePointType -eq 'Group') { - $GroupId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=mail eq '$($Request.Body.GroupID)' or proxyAddresses/any(x:endsWith(x,'$($Request.Body.GroupID)'))&`$count=true" -ComplexFilter -tenantid $TenantFilter).id - if ($Request.body.Add -eq $true) { - $Results = Add-CIPPGroupMember -GroupType 'Team' -GroupID $GroupID -Member $Request.Body.user.value -TenantFilter $TenantFilter -Headers $Request.Headers + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.tenantFilter + + try { + if ($Request.Body.SharePointType -eq 'Group') { + $GroupId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=mail eq '$($Request.Body.GroupID)' or proxyAddresses/any(x:endsWith(x,'$($Request.Body.GroupID)'))&`$count=true" -ComplexFilter -tenantid $TenantFilter).id + if ($Request.Body.Add -eq $true) { + $Results = Add-CIPPGroupMember -GroupType 'Team' -GroupID $GroupID -Member $Request.Body.user.value -TenantFilter $TenantFilter -Headers $Headers + } else { + $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.user.value)" -tenantid $TenantFilter).id + $Results = Remove-CIPPGroupMember -GroupType 'Team' -GroupID $GroupID -Member $UserID -TenantFilter $TenantFilter -Headers $Headers + } } else { - $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.user.value)" -tenantid $TenantFilter).id - $Results = Remove-CIPPGroupMember -GroupType 'Team' -GroupID $GroupID -Member $UserID -TenantFilter $TenantFilter -Headers $Request.Headers + $StatusCode = [HttpStatusCode]::BadRequest + $Results = 'This type of SharePoint site is not supported.' } - } else { - $Results = 'This type of SharePoint site is not supported.' + } catch { + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } - $body = [pscustomobject]@{'Results' = $Results } + # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $body + StatusCode = $StatusCode + Body = @{ 'Results' = $Results } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointPerms.ps1 index e2c91f14a12e..2952dc7c645b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointPerms.ps1 @@ -11,11 +11,11 @@ Function Invoke-ExecSharePointPerms { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - $tenantFilter = $Request.Body.tenantFilter $Headers = $Request.Headers - Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev Debug + $TenantFilter = $Request.Body.tenantFilter + Write-Host '====================================' Write-Host 'Request Body:' Write-Host (ConvertTo-Json $Request.body -Depth 10) @@ -31,7 +31,7 @@ Function Invoke-ExecSharePointPerms { try { - $State = Set-CIPPSharePointPerms -tenantFilter $tenantFilter ` + $State = Set-CIPPSharePointPerms -tenantFilter $TenantFilter ` -UserId $UserId ` -OnedriveAccessUser $OnedriveAccessUser ` -Headers $Headers ` @@ -41,8 +41,8 @@ Function Invoke-ExecSharePointPerms { $Result = "$State" $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed. $($ErrorMessage.NormalizedError)" + $ErrorMessage = $_.Exception.Message + $Result = "Failed. Error: $ErrorMessage" $StatusCode = [HttpStatusCode]::BadRequest } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointQuota.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointQuota.ps1 index d09419fcc476..d32656423abb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointQuota.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointQuota.ps1 @@ -15,30 +15,30 @@ Function Invoke-ListSharepointQuota { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter - if ($Request.Query.TenantFilter -eq 'AllTenants') { + if ($TenantFilter -eq 'AllTenants') { $UsedStoragePercentage = 'Not Supported' } else { try { - $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] + $TenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] - $sharepointToken = (Get-GraphToken -scope "https://$($tenantName)-admin.sharepoint.com/.default" -tenantid $TenantFilter) - $sharepointToken.Add('accept', 'application/json') - # Implement a try catch later to deal with sharepoint guest user settings - $sharepointQuota = (Invoke-RestMethod -Method 'GET' -Headers $sharepointToken -Uri "https://$($tenantName)-admin.sharepoint.com/_api/StorageQuotas()?api-version=1.3.2" -ErrorAction Stop).value | Sort-Object -Property GeoUsedStorageMB -Descending | Select-Object -First 1 + $SharePointToken = (Get-GraphToken -scope "https://$($TenantName)-admin.sharepoint.com/.default" -tenantid $TenantFilter) + $SharePointToken.Add('accept', 'application/json') + # Implement a try catch later to deal with SharePoint guest user settings + $SharePointQuota = (Invoke-RestMethod -Method 'GET' -Headers $SharePointToken -Uri "https://$($TenantName)-admin.sharepoint.com/_api/StorageQuotas()?api-version=1.3.2" -ErrorAction Stop).value | Sort-Object -Property GeoUsedStorageMB -Descending | Select-Object -First 1 - if ($sharepointQuota) { - $UsedStoragePercentage = [int](($sharepointQuota.GeoUsedStorageMB / $sharepointQuota.TenantStorageMB) * 100) + if ($SharePointQuota) { + $UsedStoragePercentage = [int](($SharePointQuota.GeoUsedStorageMB / $SharePointQuota.TenantStorageMB) * 100) } } catch { $UsedStoragePercentage = 'Not available' } } - $sharepointQuotaDetails = @{ - GeoUsedStorageMB = $sharepointQuota.GeoUsedStorageMB - TenantStorageMB = $sharepointQuota.TenantStorageMB + $SharePointQuotaDetails = @{ + GeoUsedStorageMB = $SharePointQuota.GeoUsedStorageMB + TenantStorageMB = $SharePointQuota.TenantStorageMB Percentage = $UsedStoragePercentage Dashboard = "$($UsedStoragePercentage) / 100" } @@ -48,7 +48,7 @@ Function Invoke-ListSharepointQuota { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $sharepointQuotaDetails + Body = $SharePointQuotaDetails }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointSettings.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointSettings.ps1 index cb84b80a8b6e..fdf4db9675f2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointSettings.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSharepointSettings.ps1 @@ -14,21 +14,17 @@ Function Invoke-ListSharepointSettings { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - + # XXX - Seems to be an unused endpoint? -Bobby # Interact with query parameters or the body of the request. - $tenant = $Request.Query.TenantFilter - $User = $Request.query.user - $USERToGet = $Request.query.usertoGet - $body = '{"isResharingByExternalUsersEnabled": "False"}' - $Request = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -Type patch -Body $body -ContentType 'application/json' + $Tenant = $Request.Query.tenantFilter + $Request = New-GraphGetRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' - Write-LogMessage -API 'Standards' -tenant $tenantFilter -message 'Disabled Password Expiration' -sev Info # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = @($GraphRequest) + Body = @($Request) }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 index 2d23640c9fcd..84b64bbeb7ae 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 @@ -10,6 +10,10 @@ Function Invoke-ListSites { [CmdletBinding()] param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $TenantFilter = $Request.Query.TenantFilter $Type = $request.query.Type $UserUPN = $request.query.UserUPN @@ -93,7 +97,7 @@ Function Invoke-ListSites { try { $Requests = (New-GraphBulkRequest -tenantid $TenantFilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true).body.value | Where-Object { $_.list.template -eq 'DocumentLibrary' } } catch { - Write-LogMessage -Message "Error getting auto map urls: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter -API 'ListSites' -LogData (Get-CippException -Exception $_) + Write-LogMessage -Headers $Headers -Message "Error getting auto map urls: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter -API 'ListSites' -LogData (Get-CippException -Exception $_) } $GraphRequest = foreach ($Site in $GraphRequest) { $ListId = ($Requests | Where-Object { $_.parentReference.siteId -like "*$($Site.siteId)*" }).id diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 index 38637679c79c..4d6d55619c15 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 @@ -14,13 +14,10 @@ Function Invoke-ListTeams { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - - # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter if ($request.query.type -eq 'List') { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayname,description,visibility,mailNickname" -tenantid $TenantFilter | Sort-Object -Property displayName + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayName,description,visibility,mailNickname" -tenantid $TenantFilter | Sort-Object -Property displayName } $TeamID = $request.query.ID Write-Host $TeamID diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsActivity.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsActivity.ps1 index 342f8096d272..9323a71f30a1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsActivity.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsActivity.ps1 @@ -10,12 +10,14 @@ Function Invoke-ListTeamsActivity { [CmdletBinding()] param($Request, $TriggerMetadata) - + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter - $type = $request.query.Type + $TenantFilter = $Request.Query.tenantFilter + $type = $request.Query.Type $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/get$($type)Detail(period='D30')" -tenantid $TenantFilter | ConvertFrom-Csv | Select-Object @{ Name = 'UPN'; Expression = { $_.'User Principal Name' } }, @{ Name = 'LastActive'; Expression = { $_.'Last Activity Date' } }, @{ Name = 'TeamsChat'; Expression = { $_.'Team Chat Message Count' } }, diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 index 8fcf39248cd2..72872dce00a6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 @@ -12,30 +12,31 @@ Function Invoke-AddAlert { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $Tenants = $request.body.tenantFilter - $Conditions = $request.body.conditions | ConvertTo-Json -Compress -Depth 10 | Out-String + + # Interact with query parameters or the body of the request. + $Tenants = $Request.Body.tenantFilter + $Conditions = $Request.Body.conditions | ConvertTo-Json -Compress -Depth 10 | Out-String $TenantsJson = $Tenants | ConvertTo-Json -Compress -Depth 10 | Out-String - $excludedTenantsJson = $request.body.excludedTenants | ConvertTo-Json -Compress -Depth 10 | Out-String - $Actions = $request.body.actions | ConvertTo-Json -Compress -Depth 10 | Out-String - $RowKey = $Request.body.RowKey ? $Request.body.RowKey : (New-Guid).ToString() + $excludedTenantsJson = $Request.Body.excludedTenants | ConvertTo-Json -Compress -Depth 10 | Out-String + $Actions = $Request.Body.actions | ConvertTo-Json -Compress -Depth 10 | Out-String + $RowKey = $Request.Body.RowKey ? $Request.Body.RowKey : (New-Guid).ToString() $CompleteObject = @{ Tenants = [string]$TenantsJson excludedTenants = [string]$excludedTenantsJson Conditions = [string]$Conditions Actions = [string]$Actions - type = $request.body.logbook.value + type = $Request.Body.logbook.value RowKey = $RowKey PartitionKey = 'Webhookv2' } - $WebhookTable = get-cipptable -TableName 'WebhookRules' + $WebhookTable = Get-CippTable -TableName 'WebhookRules' Add-CIPPAzDataTableEntity @WebhookTable -Entity $CompleteObject -Force $Results = "Added Audit Log Alert for $($Tenants.count) tenants. It may take up to four hours before Microsoft starts delivering these alerts." - $body = [pscustomobject]@{'Results' = @($results) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $body + Body = @{ 'Results' = @($Results) } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ExecAuditLogSearch.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ExecAuditLogSearch.ps1 index 9df2ffaf6737..dc54f2259f53 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ExecAuditLogSearch.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ExecAuditLogSearch.ps1 @@ -8,6 +8,10 @@ function Invoke-ExecAuditLogSearch { [CmdletBinding()] param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Query = $Request.Body if (!$Query.TenantFilter) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogTest.ps1 index d51774c11c2e..f7ed737d6a55 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogTest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogTest.ps1 @@ -8,6 +8,10 @@ function Invoke-ListAuditLogTest { #> Param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $AuditLogQuery = @{ TenantFilter = $Request.Query.TenantFilter SearchId = $Request.Query.SearchId diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListWebhookAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListWebhookAlert.ps1 index 42c6bf3e8787..12abc8bd4e6a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListWebhookAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListWebhookAlert.ps1 @@ -13,8 +13,10 @@ Function Invoke-ListWebhookAlert { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $Table = get-cipptable -TableName 'SchedulerConfig' - $WebhookRow = foreach ($Webhook in Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -EQ 'WebhookAlert') { + + # Interact with query parameters or the body of the request. + $Table = Get-CippTable -TableName 'SchedulerConfig' + $WebhookRow = foreach ($Webhook in (Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -EQ 'WebhookAlert')) { $Webhook.If = $Webhook.If | ConvertFrom-Json $Webhook.execution = $Webhook.execution | ConvertFrom-Json $Webhook diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index b2eaf9309790..d0828455b311 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -8,12 +8,16 @@ function Invoke-PublicWebhooks { #> param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + Set-Location (Get-Item $PSScriptRoot).Parent.FullName $WebhookTable = Get-CIPPTable -TableName webhookTable $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable Write-Host 'Received request' - $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 + $url = ($Headers.'x-ms-original-url').split('/API') | Select-Object -First 1 $CIPPURL = [string]$url Write-Host $url if ($Webhooks.Resource -eq 'M365AuditLogs') { @@ -21,22 +25,22 @@ function Invoke-PublicWebhooks { $body = 'This webhook is not authorized, its an old entry.' $StatusCode = [HttpStatusCode]::Forbidden } - if ($Request.query.ValidationToken) { + if ($Request.Query.ValidationToken) { Write-Host 'Validation token received - query ValidationToken' - $body = $request.query.ValidationToken + $body = $Request.Query.ValidationToken $StatusCode = [HttpStatusCode]::OK - } elseif ($Request.body.validationCode) { + } elseif ($Request.Body.validationCode) { Write-Host 'Validation token received - body validationCode' - $body = $request.body.validationCode + $body = $Request.Body.validationCode $StatusCode = [HttpStatusCode]::OK - } elseif ($Request.query.validationCode) { + } elseif ($Request.Query.validationCode) { Write-Host 'Validation token received - query validationCode' - $body = $request.query.validationCode + $body = $Request.Query.validationCode $StatusCode = [HttpStatusCode]::OK } elseif ($Request.Query.CIPPID -in $Webhooks.RowKey) { Write-Host 'Found matching CIPPID' - $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 - $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID + $url = ($Headers.'x-ms-original-url').split('/API') | Select-Object -First 1 + $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.Query.CIPPID if ($Request.Query.Type -eq 'GraphSubscription') { # Graph Subscriptions diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApproval.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApproval.ps1 index 26c9b127959e..42e0b5d0ebf6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApproval.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApproval.ps1 @@ -18,11 +18,11 @@ Function Invoke-ExecAppApproval { Write-Host "$($Request.query.ID)" # Interact with query parameters or the body of the request. - $applicationid = if ($request.query.applicationid) { $request.query.applicationid } else { $env:ApplicationID } - $Results = get-tenants | ForEach-Object { + $ApplicationId = if ($Request.Query.ApplicationId) { $Request.Query.ApplicationId } else { $env:ApplicationID } + $Results = Get-Tenants | ForEach-Object { [PSCustomObject]@{ defaultDomainName = $_.defaultDomainName - link = "https://login.microsoftonline.com/$($_.customerId)/v2.0/adminconsent?client_id=$applicationid&scope=$applicationid/.default" + link = "https://login.microsoftonline.com/$($_.customerId)/v2.0/adminconsent?client_id=$ApplicationId&scope=$ApplicationId/.default" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 index f604328df1c1..9368600072bb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 @@ -13,7 +13,7 @@ function Invoke-ExecAppApprovalTemplate { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' $Table = Get-CIPPTable -TableName 'templates' - $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json + $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Headers.'x-ms-client-principal')) | ConvertFrom-Json $Action = $Request.Query.Action ?? $Request.Body.Action @@ -58,12 +58,12 @@ function Invoke-ExecAppApprovalTemplate { } ) - Write-LogMessage -headers $Request.Headers -API $APIName -message "App Deployment Template Saved: $($Request.Body.TemplateName)" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APIName -message "App Deployment Template Saved: $($Request.Body.TemplateName)" -Sev 'Info' } catch { $Body = @{ 'Results' = $_.Exception.Message } - Write-LogMessage -headers $Request.Headers -API $APIName -message "App Deployment Template Save failed: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -headers $Headers -API $APIName -message "App Deployment Template Save failed: $($_.Exception.Message)" -Sev 'Error' } } 'Delete' { @@ -83,7 +83,7 @@ function Invoke-ExecAppApprovalTemplate { $Body = @{ 'Results' = "Successfully deleted template '$TemplateName'" } - Write-LogMessage -headers $Request.Headers -API $APIName -message "App Deployment Template deleted: $TemplateName" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APIName -message "App Deployment Template deleted: $TemplateName" -Sev 'Info' } else { $Body = @{ 'Results' = 'No template found with the provided ID' @@ -93,7 +93,7 @@ function Invoke-ExecAppApprovalTemplate { $Body = @{ 'Results' = "Failed to delete template: $($_.Exception.Message)" } - Write-LogMessage -headers $Request.Headers -API $APIName -message "App Deployment Template Delete failed: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -headers $Headers -API $APIName -message "App Deployment Template Delete failed: $($_.Exception.Message)" -Sev 'Error' } } 'Get' { @@ -102,7 +102,7 @@ function Invoke-ExecAppApprovalTemplate { if ($Request.Query.TemplateId) { $templateId = $Request.Query.TemplateId $filter = "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$templateId'" - Write-LogMessage -headers $Request.Headers -API $APIName -message "Retrieved specific template: $templateId" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APIName -message "Retrieved specific template: $templateId" -Sev 'Info' } $Templates = Get-CIPPAzDataTableEntity @Table -Filter $filter diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 index 87f8185a6628..4c92415403d2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 @@ -8,9 +8,13 @@ function Invoke-ExecAppPermissionTemplate { [CmdletBinding()] param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Table = Get-CIPPTable -TableName 'AppPermissions' - $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json + $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Headers.'x-ms-client-principal')) | ConvertFrom-Json $Action = $Request.Query.Action ?? $Request.Body.Action @@ -34,7 +38,7 @@ function Invoke-ExecAppPermissionTemplate { 'TemplateId' = $RowKey } } - Write-LogMessage -headers $Request.Headers -API 'ExecAppPermissionTemplate' -message "Permissions Saved for template: $($Request.Body.TemplateName)" -Sev 'Info' -LogData $Permissions + Write-LogMessage -headers $Headers -API 'ExecAppPermissionTemplate' -message "Permissions Saved for template: $($Request.Body.TemplateName)" -Sev 'Info' -LogData $Permissions } catch { Write-Information "Failed to save template: $($_.Exception.Message) - $($_.InvocationInfo.PositionMessage)" $Body = @{ @@ -53,7 +57,7 @@ function Invoke-ExecAppPermissionTemplate { $Body = @{ 'Results' = "Successfully deleted template '$TemplateName'" } - Write-LogMessage -headers $Request.Headers -API 'ExecAppPermissionTemplate' -message "Permission template deleted: $TemplateName" -Sev 'Info' + Write-LogMessage -headers $Headers -API 'ExecAppPermissionTemplate' -message "Permission template deleted: $TemplateName" -Sev 'Info' } else { $Body = @{ 'Results' = 'No Template ID provided for deletion' @@ -71,7 +75,7 @@ function Invoke-ExecAppPermissionTemplate { if ($Request.Query.TemplateId) { $templateId = $Request.Query.TemplateId $filter = "PartitionKey eq 'Templates' and RowKey eq '$templateId'" - Write-LogMessage -headers $Request.Headers -API 'ExecAppPermissionTemplate' -message "Retrieved specific template: $templateId" -Sev 'Info' + Write-LogMessage -headers $Headers -API 'ExecAppPermissionTemplate' -message "Retrieved specific template: $templateId" -Sev 'Info' } $Body = Get-CIPPAzDataTableEntity @Table -Filter $filter | ForEach-Object { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecAddSPN.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecAddSPN.ps1 index 45baf77621b4..743eb49291ad 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecAddSPN.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecAddSPN.ps1 @@ -15,18 +15,21 @@ Function Invoke-ExecAddSPN { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. - $Body = if ($Request.Query.Enable) { '{"accountEnabled":"true"}' } else { '{"accountEnabled":"false"}' } try { - $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -tenantid $env:TenantID -type POST -Body "{ `"appId`": `"2832473f-ec63-45fb-976f-5d45a7d4bb91`" }" -NoAuthCheck $true - $Results = [pscustomobject]@{'Results' = "Successfully completed request. Add your GDAP migration permissions to your SAM application here: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$($env:ApplicationID)/isMSAApp/ " } + $null = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -tenantid $env:TenantID -type POST -Body "{ `"appId`": `"2832473f-ec63-45fb-976f-5d45a7d4bb91`" }" -NoAuthCheck $true + $Result = "Successfully completed request. Add your GDAP migration permissions to your SAM application here: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$($env:ApplicationID)/isMSAApp/ " + $StatusCode = [HttpStatusCode]::OK } catch { - $Results = [pscustomobject]@{'Results' = "Failed to add SPN. Please manually execute 'New-AzureADServicePrincipal -AppId 2832473f-ec63-45fb-976f-5d45a7d4bb91' The error was $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to add SPN. The error was: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $env:TenantID -message $Result -Sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 index 4afdfb601df5..6abc2c461279 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 @@ -9,23 +9,25 @@ Function Invoke-ExecOffboardTenant { #> [CmdletBinding()] param($Request, $TriggerMetadata) + $APIName = $Request.Params.CIPPEndpoint - try { - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + try { $TenantQuery = $Request.Body.TenantFilter.value ?? $Request.Body.TenantFilter $Tenant = Get-Tenants -IncludeAll -TenantFilter $TenantQuery $TenantId = $Tenant.customerId $TenantFilter = $Tenant.defaultDomainName - $results = [System.Collections.ArrayList]@() - $errors = [System.Collections.ArrayList]@() + $Results = [System.Collections.Generic.List[object]]::new() + $Errors = [System.Collections.Generic.List[object]]::new() if (!$Tenant) { - $results.Add('Tenant has already been offboarded') + $Results.Add('Tenant has already been offboarded') } elseif ($TenantId -eq $env:TenantID) { - $errors.Add('You cannot offboard the CSP tenant') + $Errors.Add('You cannot offboard the CSP tenant') } else { if ($request.body.RemoveCSPGuestUsers -eq $true) { # Delete guest users who's domains match the CSP tenants @@ -33,9 +35,9 @@ Function Invoke-ExecOffboardTenant { try { $domains = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/domains?`$select=id" -tenantid $env:TenantID -NoAuthCheck:$true).id $DomainFilter = ($Domains | ForEach-Object { "endswith(mail, '$_')" }) -join ' or ' - $CSPGuestUsers = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,mail&`$filter=userType eq 'Guest' and ($DomainFilter)&`$count=true" -tenantid $Tenantfilter -ComplexFilter) + $CSPGuestUsers = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,mail&`$filter=userType eq 'Guest' and ($DomainFilter)&`$count=true" -tenantid $TenantFilter -ComplexFilter) } catch { - $errors.Add("Failed to retrieve guest users: $($_.Exception.message)") + $Errors.Add("Failed to retrieve guest users: $($_.Exception.message)") } if ($CSPGuestUsers) { @@ -90,29 +92,28 @@ Function Invoke-ExecOffboardTenant { $patchContactBody = if (!($newPropertyContent)) { "{ `"$($property)`" : [] }" } else { [pscustomobject]@{ $property = $newPropertyContent } | ConvertTo-Json } try { - New-GraphPostRequest -type PATCH -body $patchContactBody -Uri "https://graph.microsoft.com/v1.0/organization/$($orgContacts.id)" -tenantid $Tenantfilter -ContentType 'application/json' - $results.Add("Successfully removed notification contacts from $($property): $(($propertyContacts | Where-Object { $domains -contains $_.Split('@')[1] }))") + New-GraphPostRequest -type PATCH -body $patchContactBody -Uri "https://graph.microsoft.com/v1.0/organization/$($orgContacts.id)" -tenantid $TenantFilter -ContentType 'application/json' + $Results.Add("Successfully removed notification contacts from $($property): $(($propertyContacts | Where-Object { $domains -contains $_.Split('@')[1] }))") Write-LogMessage -headers $Request.Headers -API $APIName -message "Contacts were removed from $($property)" -Sev 'Info' -tenant $TenantFilter } catch { - $errors.Add("Failed to update property $($property): $($_.Exception.message)") + $Errors.Add("Failed to update property $($property): $($_.Exception.message)") } } else { $results.Add("No notification contacts found in $($property)") } } - # Add logic for privacyProfile later - rvdwegen + # TODO Add logic for privacyProfile later - rvdwegen } $VendorApps = $Request.Body.vendorApplications if ($VendorApps) { $VendorApps | ForEach-Object { try { - $delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.value)" -tenantid $Tenantfilter) - $results.Add("Successfully removed app $($_.label)") - Write-LogMessage -headers $Request.Headers -API $APIName -message "App $($_.label) was removed" -Sev 'Info' -tenant $TenantFilter + $null = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.value)" -tenantid $TenantFilter) + $Results.Add("Successfully removed app $($_.label)") + Write-LogMessage -headers $Headers -API $APIName -message "App $($_.label) was removed" -Sev 'Info' -tenant $TenantFilter } catch { - #$results.Add("Failed to removed app $($_.displayName)") - $errors.Add("Failed to removed app $($_.label)") + $Errors.Add("Failed to removed app $($_.label)") } } } @@ -121,21 +122,21 @@ Function Invoke-ExecOffboardTenant { if ($request.body.RemoveMultitenantCSPApps -eq $true) { # Remove multi-tenant apps with the CSP tenant as origin try { - $multitenantCSPApps = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$count=true&`$select=displayName,appId,id,appOwnerOrganizationId&`$filter=appOwnerOrganizationId eq $($env:TenantID)" -tenantid $Tenantfilter -ComplexFilter) - $sortedArray = $multitenantCSPApps | Sort-Object @{Expression = { if ($_.appId -eq $env:ApplicationID) { 1 } else { 0 } }; Ascending = $true } + $MultiTenantCSPApps = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$count=true&`$select=displayName,appId,id,appOwnerOrganizationId&`$filter=appOwnerOrganizationId eq $($env:TenantID)" -tenantid $TenantFilter -ComplexFilter) + $sortedArray = $MultiTenantCSPApps | Sort-Object @{Expression = { if ($_.appId -eq $env:ApplicationID) { 1 } else { 0 } }; Ascending = $true } $sortedArray | ForEach-Object { try { - $delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.id)" -tenantid $Tenantfilter) - $results.Add("Successfully removed app $($_.displayName)") + $null = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.id)" -tenantid $TenantFilter) + $Results.Add("Successfully removed app $($_.displayName)") Write-LogMessage -headers $Request.Headers -API $APIName -message "App $($_.displayName) was removed" -Sev 'Info' -tenant $TenantFilter } catch { - #$results.Add("Failed to removed app $($_.displayName)") - $errors.Add("Failed to removed app $($_.displayName)") + #$Results.Add("Failed to removed app $($_.displayName)") + $Errors.Add("Failed to removed app $($_.displayName)") } } } catch { - #$results.Add("Failed to retrieve multitenant apps, no apps have been removed: $($_.Exception.message)") - $errors.Add("Failed to retrieve multitenant CSP apps, no apps have been removed: $($_.Exception.message)") + #$Results.Add("Failed to retrieve multi-tenant apps, no apps have been removed: $($_.Exception.message)") + $Errors.Add("Failed to retrieve multi-tenant CSP apps, no apps have been removed: $($_.Exception.message)") } } $ClearCache = $false @@ -146,8 +147,8 @@ Function Invoke-ExecOffboardTenant { $delegatedAdminRelationships = (New-GraphGETRequest -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/delegatedAdminRelationships?`$filter=(status eq 'active') AND (customer/tenantId eq '$tenantid')" -tenantid $env:TenantID) $delegatedAdminRelationships | ForEach-Object { try { - $terminate = (New-GraphPostRequest -type 'POST' -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/delegatedAdminRelationships/$($_.id)/requests" -body '{"action":"terminate"}' -ContentType 'application/json' -tenantid $env:TenantID) - $results.Add("Successfully terminated GDAP relationship $($_.displayName) from tenant $TenantFilter") + $null = (New-GraphPostRequest -type 'POST' -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/delegatedAdminRelationships/$($_.id)/requests" -body '{"action":"terminate"}' -ContentType 'application/json' -tenantid $env:TenantID) + $Results.Add("Successfully terminated GDAP relationship $($_.displayName) from tenant $TenantFilter") Write-LogMessage -headers $Request.Headers -API $APIName -message "GDAP Relationship $($_.displayName) has been terminated" -Sev 'Info' -tenant $TenantFilter } catch { @@ -158,20 +159,20 @@ Function Invoke-ExecOffboardTenant { } } catch { $($_.Exception.message) - #$results.Add("Failed to retrieve GDAP relationships, no relationships have been terminated: $($_.Exception.message)") - $errors.Add("Failed to retrieve GDAP relationships, no relationships have been terminated: $($_.Exception.message)") + #$Results.Add("Failed to retrieve GDAP relationships, no relationships have been terminated: $($_.Exception.message)") + $Errors.Add("Failed to retrieve GDAP relationships, no relationships have been terminated: $($_.Exception.message)") } } if ($request.body.TerminateContract -eq $true) { # Terminate contract relationship try { - $terminate = (New-GraphPostRequest -type 'PATCH' -body '{ "relationshipToPartner": "none" }' -Uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter" -ContentType 'application/json' -scope 'https://api.partnercenter.microsoft.com/user_impersonation' -tenantid $env:TenantID) - $results.Add('Successfully terminated contract relationship') - Write-LogMessage -headers $Request.Headers -API $APIName -message 'Contract relationship terminated' -Sev 'Info' -tenant $TenantFilter + $null = (New-GraphPostRequest -type 'PATCH' -body '{ "relationshipToPartner": "none" }' -Uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter" -ContentType 'application/json' -scope 'https://api.partnercenter.microsoft.com/user_impersonation' -tenantid $env:TenantID) + $Results.Add('Successfully terminated contract relationship') + Write-LogMessage -headers $Headers -API $APIName -message 'Contract relationship terminated' -Sev 'Info' -tenant $TenantFilter } catch { - #$results.Add("Failed to terminate contract relationship: $($_.Exception.message)") - $errors.Add("Failed to terminate contract relationship: $($_.Exception.message)") + #$Results.Add("Failed to terminate contract relationship: $($_.Exception.message)") + $Errors.Add("Failed to terminate contract relationship: $($_.Exception.message)") } } } @@ -181,11 +182,11 @@ Function Invoke-ExecOffboardTenant { $Results.Add('Tenant cache has been cleared') } - Write-LogMessage -headers $Request.Headers -API $APIName -message 'Offboarding completed' -Sev 'Info' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message 'Offboarding completed' -Sev 'Info' -tenant $TenantFilter $StatusCode = [HttpStatusCode]::OK $body = [pscustomobject]@{ - 'Results' = @($results) - 'Errors' = @($errors) + 'Results' = @($Results) + 'Errors' = @($Errors) } } catch { $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 index ad61daf3ddd2..fdf1b97e79ba 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 @@ -9,8 +9,11 @@ function Invoke-ExecOnboardTenant { #> param($Request, $TriggerMetadata) - $APIName = 'ExecOnboardTenant' - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. $Id = $Request.Body.id if ($Id) { try { @@ -84,7 +87,7 @@ function Invoke-ExecOnboardTenant { Batch = @($Item) } $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Onboarding job $Id started" -Sev 'Info' -LogData @{ 'InstanceId' = $InstanceId } + Write-LogMessage -headers $Headers -API $APIName -message "Onboarding job $Id started" -Sev 'Info' -LogData @{ 'InstanceId' = $InstanceId } } $Steps = $TenantOnboarding.OnboardingSteps | ConvertFrom-Json diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 index 406a029c49c4..bf38d341040c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 @@ -15,22 +15,29 @@ Function Invoke-ExecUpdateSecureScore { Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.TenantFilter + $ControlName = $Request.Body.ControlName $Body = @{ - comment = $request.body.reason - state = $request.body.resolutionType.value - vendorInformation = $request.body.vendorInformation + comment = $Request.Body.reason + state = $Request.Body.resolutionType.value + vendorInformation = $Request.Body.vendorInformation } try { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/security/secureScoreControlProfiles/$($Request.body.ControlName)" -tenantid $Request.body.TenantFilter -type PATCH -Body $($Body | ConvertTo-Json -Compress) - $Results = [pscustomobject]@{'Results' = "Successfully set control to $($Body.state) " } + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/security/secureScoreControlProfiles/$ControlName" -tenantid $TenantFilter -type PATCH -Body (ConvertTo-Json -InputObject $Body -Compress) + $StatusCode = [HttpStatusCode]::OK + $Result = "Successfully set control $ControlName to $($Body.state)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Info' } catch { - $Results = [pscustomobject]@{'Results' = "Failed to set Control to $($Body.state) $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to set control $ControlName to $($Body.state). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results + StatusCode = $StatusCode + Body = @{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 index 55f5fdd82b76..89472cd5821a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 @@ -10,14 +10,15 @@ function Invoke-ListAppConsentRequests { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - $TenantFilter = $Request.Query.TenantFilter - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter try { - if ($Request.Query.TenantFilter -eq 'AllTenants') { + if ($TenantFilter -eq 'AllTenants') { throw 'AllTenants is not yet supported' - } else { - $TenantFilter = $Request.Query.TenantFilter } $appConsentRequests = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $TenantFilter # Need the beta endpoint to get consentType @@ -49,9 +50,10 @@ function Invoke-ListAppConsentRequests { } $StatusCode = [HttpStatusCode]::OK } catch { - $StatusCode = [HttpStatusCode]::OK - Write-LogMessage -Headers $Headers -API $APIName -message 'app consent request list failed' -Sev 'Error' -tenant $TenantFilter - $Results = @{ appDisplayName = "Error: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + $StatusCode = [HttpStatusCode]::InternalServerError + Write-LogMessage -Headers $Headers -API $APIName -message 'app consent request list failed' -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $Results = @{ appDisplayName = "Error: $($ErrorMessage.NormalizedError)" } } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListDomains.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListDomains.ps1 index ece424ca883d..cf11e6b09050 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListDomains.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListDomains.ps1 @@ -14,24 +14,20 @@ Function Invoke-ListDomains { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - - # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter try { - $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $TenantFilter | Select-Object id, isdefault, isinitial | Sort-Object isdefault -Descending + $Result = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $TenantFilter | Select-Object id, isdefault, isinitial | Sort-Object isdefault -Descending $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $StatusCode = [HttpStatusCode]::Forbidden - $GraphRequest = $ErrorMessage + $Result = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest) + Body = @($Result) }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListTenantOnboarding.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListTenantOnboarding.ps1 index 843248a2d2ff..ce5bafeedcd3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListTenantOnboarding.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListTenantOnboarding.ps1 @@ -10,7 +10,7 @@ function Invoke-ListTenantOnboarding { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' try { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 index 9e089ff3f77c..5c5d3c2839b3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 @@ -21,13 +21,13 @@ function Invoke-SetAuthMethod { $Result = Set-CIPPAuthenticationPolicy -Tenant $TenantFilter -APIName $APIName -AuthenticationMethodId $AuthenticationMethodId -Enabled $State -Headers $Headers $StatusCode = [HttpStatusCode]::OK } catch { - $Result = $_ - $StatusCode = [HttpStatusCode]::Forbidden + $Result = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = [pscustomobject]@{'Results' = "$Result" } + Body = [pscustomobject]@{'Results' = $Result } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-AddTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-AddTenant.ps1 index 65a355bf337f..1234c4523d2d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-AddTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-AddTenant.ps1 @@ -9,7 +9,10 @@ function Invoke-AddTenant { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with query parameters or the body of the request. $Action = $Request.Body.Action ?? $Request.Query.Action $TenantName = $Request.Body.TenantName ?? $Request.Query.TenantName $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 index b0fa498d20ed..e18c9ba35954 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 @@ -12,9 +12,9 @@ function Invoke-EditTenant { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - Write-LogMessage -headers $Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' - + # Interact with query parameters or the body of the request. $customerId = $Request.Body.customerId $tenantAlias = $Request.Body.tenantAlias $tenantGroups = $Request.Body.tenantGroups diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 index 786a2e4cb673..8f3bbccc0063 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 @@ -11,37 +11,34 @@ Function Invoke-ListTenantDetails { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' - - $tenantfilter = $Request.Query.TenantFilter + # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Query.tenantFilter try { - $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenantfilter | Select-Object displayName, id, city, country, countryLetterCode, street, state, postalCode, + $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter | Select-Object displayName, id, city, country, countryLetterCode, street, state, postalCode, @{ Name = 'businessPhones'; Expression = { $_.businessPhones -join ', ' } }, @{ Name = 'technicalNotificationMails'; Expression = { $_.technicalNotificationMails -join ', ' } }, tenantType, createdDateTime, onPremisesLastPasswordSyncDateTime, onPremisesLastSyncDateTime, onPremisesSyncEnabled, assignedPlans - $customProperties = Get-TenantProperties -customerId $tenantfilter + $customProperties = Get-TenantProperties -customerId $TenantFilter $org | Add-Member -MemberType NoteProperty -Name 'customProperties' -Value $customProperties - $Groups = (Get-TenantGroups -TenantFilter $tenantfilter) ?? @() + $Groups = (Get-TenantGroups -TenantFilter $TenantFilter) ?? @() $org | Add-Member -MemberType NoteProperty -Name 'Groups' -Value @($Groups) - - # Respond with the successful output - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $org - }) } catch { - # Log the exception message - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Error: $($_.Exception.Message)" -Sev 'Error' - - # Respond with a 500 error and include the exception message in the response body - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::InternalServerError - Body = Get-NormalizedError -message $_.Exception.Message - }) + $ErrorMessage = Get-CippException -Exception $_ + $org = "Failed to retrieve tenant details: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $org -Sev 'Error' -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $org + }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 index d91f6d5a2392..624e16be31c7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 @@ -11,8 +11,10 @@ function Invoke-ListTenants { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' + # Interact with query parameters or the body of the request. $TenantAccess = Test-CIPPAccess -Request $Request -TenantList Write-Host "Tenant Access: $TenantAccess" @@ -67,7 +69,7 @@ function Invoke-ListTenants { } } try { - $tenantfilter = $Request.Query.TenantFilter + $TenantFilter = $Request.Query.tenantFilter $Tenants = Get-Tenants -IncludeErrors -SkipDomains if ($TenantAccess -notcontains 'AllTenants') { $Tenants = $Tenants | Where-Object -Property customerId -In $TenantAccess @@ -107,12 +109,12 @@ function Invoke-ListTenants { } } else { - $body = $Tenants | Where-Object -Property defaultDomainName -EQ $Tenantfilter + $body = $Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter } - Write-LogMessage -headers $Request.Headers -tenant $Tenantfilter -API $APINAME -message 'Listed Tenant Details' -Sev 'Debug' + Write-LogMessage -headers $Headers -tenant $TenantFilter -API $APIName -message 'Listed Tenant Details' -Sev 'Debug' } catch { - Write-LogMessage -headers $Request.Headers -tenant $Tenantfilter -API $APINAME -message "List Tenant failed. The error is: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -headers $Headers -tenant $TenantFilter -API $APIName -message "List Tenant failed. The error is: $($_.Exception.Message)" -Sev 'Error' $body = [pscustomobject]@{ 'Results' = "Failed to retrieve tenants: $($_.Exception.Message)" defaultDomainName = '' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListCSPsku.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListCSPsku.ps1 index dcf8fb9a523f..ff61308e7023 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListCSPsku.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListCSPsku.ps1 @@ -24,15 +24,17 @@ Function Invoke-ListCSPsku { } else { $GraphRequest = Get-SherwebCatalog -TenantFilter $TenantFilter } + $StatusCode = [HttpStatusCode]::OK } catch { $GraphRequest = [PSCustomObject]@{ name = @(@{value = 'Error getting catalog' }) sku = $_.Exception.Message } + $StatusCode = [HttpStatusCode]::InternalServerError } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK + StatusCode = $StatusCode Body = @($GraphRequest) }) -Clobber diff --git a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 index 0187ef3bda0f..28ecc040f452 100644 --- a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 @@ -2,23 +2,23 @@ function Get-CIPPPerUserMFA { [CmdletBinding()] param( $TenantFilter, - $userId, + $UserId, $Headers, $AllUsers = $false ) try { if ($AllUsers -eq $true) { - $AllUsers = New-graphGetRequest -Uri "https://graph.microsoft.com/v1.0/users?`$top=999&`$select=UserPrincipalName,Id,perUserMfaState" -tenantid $tenantfilter + $AllUsers = New-GraphGetRequest -Uri "https://graph.microsoft.com/v1.0/users?`$top=999&`$select=UserPrincipalName,Id,perUserMfaState" -tenantid $TenantFilter return $AllUsers } else { - $MFAState = New-graphGetRequest -Uri "https://graph.microsoft.com/v1.0/users/$($userId)?`$select=UserPrincipalName,Id,perUserMfaState" -tenantid $tenantfilter + $MFAState = New-GraphGetRequest -Uri "https://graph.microsoft.com/v1.0/users/$($UserId)?`$select=UserPrincipalName,Id,perUserMfaState" -tenantid $TenantFilter return [PSCustomObject]@{ PerUserMFAState = $MFAState.perUserMfaState - UserPrincipalName = $userId + UserPrincipalName = $UserId } } } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - "Failed to get MFA State for $id : $ErrorMessage" + throw "Failed to get MFA State for $UserId : $ErrorMessage" } } diff --git a/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 b/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 index a7ce98992420..29764fc40e34 100644 --- a/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 @@ -2,21 +2,21 @@ function New-CIPPOneDriveShortCut { [CmdletBinding()] param ( - $username, - $userid, + $Username, + $UserId, $URL, $TenantFilter, $APIName = 'Create OneDrive shortcut', $Headers ) - Write-Host "Received $username and $userid. We're using $url and $TenantFilter" + Write-Host "Received $Username and $UserId. We're using $URL and $TenantFilter" try { - $SiteInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/' -tenantid $TenantFilter -asapp $true | Where-Object -Property weburl -EQ $url + $SiteInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/' -tenantid $TenantFilter -asapp $true | Where-Object -Property weburl -EQ $URL $ListItemUniqueId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/sites/$($siteInfo.id)/drive?`$select=SharepointIds" -tenantid $TenantFilter -asapp $true).SharePointIds $body = [PSCustomObject]@{ name = 'Documents' remoteItem = @{ - sharepointIds = @{ + SharepointIds = @{ listId = $($ListItemUniqueId.listid) listItemUniqueId = 'root' siteId = $($ListItemUniqueId.siteId) @@ -28,11 +28,12 @@ function New-CIPPOneDriveShortCut { } | ConvertTo-Json -Depth 10 New-GraphPOSTRequest -method POST "https://graph.microsoft.com/beta/users/$username/drive/root/children" -body $body -tenantid $TenantFilter -asapp $true Write-LogMessage -API $APIName -headers $Headers -message "Created OneDrive shortcut called $($SiteInfo.displayName) for $($username)" -Sev 'info' - return "Created OneDrive Shortcut for $username called $($SiteInfo.displayName) " + return "Successfully created OneDrive Shortcut for $username called $($SiteInfo.displayName) " } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not add Onedrive shortcut to $username : $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage - return "Could not add Onedrive shortcut to $username : $($ErrorMessage.NormalizedError)" + $Result = "Could not add Onedrive shortcut to $username : $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -LogData $ErrorMessage + throw $Result } } diff --git a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 index 9cf2c95c301f..6a37dbbf088a 100644 --- a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 @@ -60,7 +60,10 @@ function New-CIPPSharepointSite { [string]$Classification, [Parameter(Mandatory = $true)] - [string]$TenantFilter + [string]$TenantFilter, + + $APIName = 'Create SharePoint Site', + $Headers ) $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" @@ -142,11 +145,35 @@ function New-CIPPSharepointSite { $Results = New-GraphPostRequest -scope "$AdminUrl/.default" -uri "$AdminUrl/_api/SPSiteManager/create" -Body ($body | ConvertTo-Json -Compress -Depth 10) -tenantid $TenantFilter -ContentType 'application/json' -AddedHeaders $AddedHeaders } - if ($Results.SiteStatus -eq '4') { - return 'This site already exists. Please choose a different site name.' - } - if ($Results.SiteStatus -eq '2') { - return "The site $($SiteName) was created successfully." + # Check the results. This response is weird. https://learn.microsoft.com/en-us/sharepoint/dev/apis/site-creation-rest + switch ($Results.SiteStatus) { + '0' { + $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. The site doesn't exist." + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error + throw $Results + } + '1' { + $Result = "Successfully created new SharePoint site $SiteName with URL $SiteUrl. The site is however currently being provisioned. Please wait for it to finish." + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Info + return $Results + } + '2' { + $Result = "Successfully created new SharePoint site $SiteName with URL $SiteUrl" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Info + return $Results + } + '3' { + $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. An error occurred while provisioning the site." + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error + throw $Results + } + '4' { + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error + $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. The site already exists." + throw $Result + } + Default {} } + } diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 index 862b4b318610..c723ff2d77e7 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 @@ -7,23 +7,23 @@ function Remove-CIPPGroupMember( [string]$APIName = 'Remove Group Member' ) { try { - if ($member -like '*#EXT#*') { $member = [System.Web.HttpUtility]::UrlEncode($member) } - # $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id - # $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" + if ($Member -like '*#EXT#*') { $Member = [System.Web.HttpUtility]::UrlEncode($Member) } + # $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Member)" -tenantid $TenantFilter).id + # $AddMemberBody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { - $Params = @{ Identity = $GroupId; Member = $member; BypassSecurityGroupManagerCheck = $true } - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + $Params = @{ Identity = $GroupId; Member = $Member; BypassSecurityGroupManagerCheck = $true } + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $Params -UseSystemMailbox $true } else { - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/members/$($Member)/`$ref" -tenantid $TenantFilter -type DELETE -body '{}' -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/members/$($Member)/`$ref" -tenantid $TenantFilter -type DELETE -body '{}' -Verbose } - $Message = "Successfully removed user $($Member) from $($GroupId)." - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -Sev 'Info' - return $message + $Results = "Successfully removed user $($Member) from $($GroupId)." + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev Info + return $Results } catch { $ErrorMessage = Get-CippException -Exception $_ - $message = "Failed to remove user $($Member) from $($GroupId): $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $message -Sev 'error' -LogData $ErrorMessage - return $message + $Results = "Failed to remove user $($Member) from $($GroupId): $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev Error -LogData $ErrorMessage + throw $Results } } diff --git a/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 index 46514ac6dd14..d3707829cc0f 100644 --- a/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 +++ b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 @@ -43,10 +43,11 @@ function Request-CIPPSPOPersonalSite { $Request = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' if (!$Request.IsComplete) { throw } Write-LogMessage -headers $Headers -API $APIName -message "Requested personal site for $($UserEmails -join ', ')" -Sev 'Info' -tenant $TenantFilter - return "Requested personal site for $($UserEmails -join ', ')" + return "Successfully requested personal site for $($UserEmails -join ', ')" } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not request personal site for $($UserEmails -join ', '). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not request personal site for $($UserEmails -join ', '). Error: $($ErrorMessage.NormalizedError)" + $Result = "Failed to request personal site for $($UserEmails -join ', '). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Result } } diff --git a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 index a7f37e6e7854..e9d319008a2d 100644 --- a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 +++ b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 @@ -15,7 +15,9 @@ function Revoke-CIPPSessions { } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Failed to revoke sessions for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Revoke Session Failed: $($ErrorMessage.NormalizedError)" + $Result = "Failed to revoke sessions for $($username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + # TODO - needs to be changed to throw, but the rest of the functions using this cant handle anything but a return. + return $Result } } diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index ff87694f31f0..69b780169c69 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -29,14 +29,16 @@ function Set-CIPPResetPassword { Write-LogMessage -headers $Headers -API $APIName -message "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter if ($UserDetails.onPremisesSyncEnabled -eq $true) { - return [pscustomobject]@{ resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password. WARNING: This user is AD synced. Please confirm passthrough or writeback is enabled." - copyField = $password - state = 'warning' + return [pscustomobject]@{ + resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password. WARNING: This user is AD synced. Please confirm passthrough or writeback is enabled." + copyField = $password + state = 'warning' } } else { - return [pscustomobject]@{ resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" - copyField = $password - state = 'success' + return [pscustomobject]@{ + resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" + copyField = $password + state = 'success' } } } catch { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 new file mode 100644 index 000000000000..e630e12a900b --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 @@ -0,0 +1,489 @@ +function Invoke-CIPPStandardSafeLinksTemplatePolicy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SafeLinksTemplatePolicy + .SYNOPSIS + (Label) SafeLinks Policy Template + .DESCRIPTION + (Helptext) This applies selected SafeLinks policy templates to the tenant, creating or updating as needed + (DocsDescription) This applies selected SafeLinks policy templates to the tenant, creating or updating as needed + .NOTES + CAT + Defender Standards + TAG + "CIS" + "mdo_safelinksforemail" + "mdo_safelinksforOfficeApps" + ADDEDCOMPONENT + {"type":"autoComplete","multiple":true,"name":"standards.SafeLinksTemplatePolicy.TemplateIds","label":"SafeLinks Templates","loadingMessage":"Loading templates...","api":{"url":"/api/ListSafeLinksPolicyTemplates","labelField":"name","valueField":"GUID","queryKey":"ListSafeLinksPolicyTemplates"}} + IMPACT + Low Impact + ADDEDDATE + 2025-04-29 + POWERSHELLEQUIVALENT + New-SafeLinksPolicy, Set-SafeLinksPolicy, New-SafeLinksRule, Set-SafeLinksRule + RECOMMENDEDBY + "CIS" + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/list-standards/defender-standards#low-impact + #> + + param($Tenant, $Settings) + + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Processing SafeLinks template with settings: $($Settings | ConvertTo-Json -Compress)" -sev Debug + + # Verify tenant has necessary license + if (-not (Test-MDOLicense -Tenant $Tenant -Settings $Settings)) { + return + } + + # Normalize template list property + $TemplateList = Get-NormalizedTemplateList -Settings $Settings + if (-not $TemplateList) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "No templates selected for SafeLinks policy deployment" -sev Error + return + } + + # Handle different modes + switch ($true) { + ($Settings.remediate -eq $true) { + Invoke-SafeLinksRemediation -Tenant $Tenant -TemplateList $TemplateList -Settings $Settings + } + ($Settings.alert -eq $true) { + Invoke-SafeLinksAlert -Tenant $Tenant -TemplateList $TemplateList -Settings $Settings + } + ($Settings.report -eq $true) { + Invoke-SafeLinksReport -Tenant $Tenant -TemplateList $TemplateList -Settings $Settings + } + } +} + +function Test-MDOLicense { + param($Tenant, $Settings) + + $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant + $ServicePlans = $ServicePlans.servicePlans.servicePlanName + $MDOLicensed = $ServicePlans -contains 'ATP_ENTERPRISE' + + if (-not $MDOLicensed) { + $Message = 'Tenant does not have Microsoft Defender for Office 365 license' + + if ($Settings.remediate -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply SafeLinks templates: $Message" -sev Error + } + + if ($Settings.alert -eq $true) { + Write-StandardsAlert -message "SafeLinks templates could not be applied: $Message" -object $MDOLicensed -tenant $Tenant -standardName 'SafeLinksTemplatePolicy' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "SafeLinks templates could not be applied: $Message" -sev Info + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'SafeLinksTemplatePolicy' -FieldValue $false -StoreAs bool -Tenant $Tenant + Set-CIPPStandardsCompareField -FieldName 'standards.SafeLinksTemplatePolicy' -FieldValue $false -Tenant $Tenant + } + + return $false + } + + return $true +} + +function Get-NormalizedTemplateList { + param($Settings) + + if ($Settings.'standards.SafeLinksTemplatePolicy.TemplateIds') { + return $Settings.'standards.SafeLinksTemplatePolicy.TemplateIds' + } + elseif ($Settings.TemplateIds) { + return $Settings.TemplateIds + } + + return $null +} + +function Get-SafeLinksTemplateFromStorage { + param($TemplateId) + + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'SafeLinksTemplate' and RowKey eq '$TemplateId'" + $Template = Get-CIPPAzDataTableEntity @Table -Filter $Filter + + if (-not $Template) { + throw "Template with ID $TemplateId not found" + } + + return $Template.JSON | ConvertFrom-Json -ErrorAction Stop +} + +function ConvertTo-SafeArray { + param($Field) + + if ($null -eq $Field) { return @() } + + $ResultList = [System.Collections.Generic.List[string]]::new() + + if ($Field -is [array]) { + foreach ($item in $Field) { + if ($item -is [string]) { + $ResultList.Add($item) + } + elseif ($item.value) { + $ResultList.Add($item.value) + } + elseif ($item.userPrincipalName) { + $ResultList.Add($item.userPrincipalName) + } + elseif ($item.id) { + $ResultList.Add($item.id) + } + else { + $ResultList.Add($item.ToString()) + } + } + return $ResultList.ToArray() + } + + if ($Field -is [hashtable] -or $Field -is [PSCustomObject]) { + if ($Field.value) { + $ResultList.Add($Field.value) + return $ResultList.ToArray() + } + if ($Field.userPrincipalName) { + $ResultList.Add($Field.userPrincipalName) + return $ResultList.ToArray() + } + if ($Field.id) { + $ResultList.Add($Field.id) + return $ResultList.ToArray() + } + } + + if ($Field -is [string]) { + $ResultList.Add($Field) + return $ResultList.ToArray() + } + + $ResultList.Add($Field.ToString()) + return $ResultList.ToArray() +} + +function Get-ExistingSafeLinksObjects { + param($Tenant, $PolicyName, $RuleName) + + $PolicyExists = $null + $RuleExists = $null + + try { + $ExistingPolicies = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksPolicy' -useSystemMailbox $true + $PolicyExists = $ExistingPolicies | Where-Object { $_.Name -eq $PolicyName } + } + catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to retrieve existing policies: $($_.Exception.Message)" -sev Warning + } + + try { + $ExistingRules = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksRule' -useSystemMailbox $true + $RuleExists = $ExistingRules | Where-Object { $_.Name -eq $RuleName } + } + catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to retrieve existing rules: $($_.Exception.Message)" -sev Warning + } + + return @{ + PolicyExists = $PolicyExists + RuleExists = $RuleExists + } +} + +function New-SafeLinksPolicyParameters { + param($Template) + + $PolicyMappings = @{ + 'EnableSafeLinksForEmail' = 'EnableSafeLinksForEmail' + 'EnableSafeLinksForTeams' = 'EnableSafeLinksForTeams' + 'EnableSafeLinksForOffice' = 'EnableSafeLinksForOffice' + 'TrackClicks' = 'TrackClicks' + 'AllowClickThrough' = 'AllowClickThrough' + 'ScanUrls' = 'ScanUrls' + 'EnableForInternalSenders' = 'EnableForInternalSenders' + 'DeliverMessageAfterScan' = 'DeliverMessageAfterScan' + 'DisableUrlRewrite' = 'DisableUrlRewrite' + 'AdminDisplayName' = 'AdminDisplayName' + 'CustomNotificationText' = 'CustomNotificationText' + 'EnableOrganizationBranding' = 'EnableOrganizationBranding' + } + + $PolicyParams = @{} + + foreach ($templateKey in $PolicyMappings.Keys) { + if ($null -ne $Template.$templateKey) { + $PolicyParams[$PolicyMappings[$templateKey]] = $Template.$templateKey + } + } + + $DoNotRewriteUrls = ConvertTo-SafeArray -Field $Template.DoNotRewriteUrls + if ($DoNotRewriteUrls.Count -gt 0) { + $PolicyParams['DoNotRewriteUrls'] = $DoNotRewriteUrls + } + + return $PolicyParams +} + +function New-SafeLinksRuleParameters { + param($Template) + + $RuleParams = @{} + + # Basic rule parameters + if ($null -ne $Template.Priority) { $RuleParams['Priority'] = $Template.Priority } + if ($null -ne $Template.Description) { $RuleParams['Comments'] = $Template.Description } + if ($null -ne $Template.TemplateDescription) { $RuleParams['Comments'] = $Template.TemplateDescription } + + # Array-based rule parameters + $ArrayMappings = @{ + 'SentTo' = ConvertTo-SafeArray -Field $Template.SentTo + 'SentToMemberOf' = ConvertTo-SafeArray -Field $Template.SentToMemberOf + 'RecipientDomainIs' = ConvertTo-SafeArray -Field $Template.RecipientDomainIs + 'ExceptIfSentTo' = ConvertTo-SafeArray -Field $Template.ExceptIfSentTo + 'ExceptIfSentToMemberOf' = ConvertTo-SafeArray -Field $Template.ExceptIfSentToMemberOf + 'ExceptIfRecipientDomainIs' = ConvertTo-SafeArray -Field $Template.ExceptIfRecipientDomainIs + } + + foreach ($paramName in $ArrayMappings.Keys) { + if ($ArrayMappings[$paramName].Count -gt 0) { + $RuleParams[$paramName] = $ArrayMappings[$paramName] + } + } + + return $RuleParams +} + +function Set-SafeLinksRuleState { + param($Tenant, $RuleName, $State) + + if ($null -eq $State) { return } + + $IsEnabled = switch ($State) { + "Enabled" { $true } + "Disabled" { $false } + $true { $true } + $false { $false } + default { $null } + } + + if ($null -ne $IsEnabled) { + $Cmdlet = $IsEnabled ? 'Enable-SafeLinksRule' : 'Disable-SafeLinksRule' + $null = New-ExoRequest -tenantid $Tenant -cmdlet $Cmdlet -cmdParams @{ Identity = $RuleName } -useSystemMailbox $true + return $IsEnabled ? "enabled" : "disabled" + } + + return $null +} + +function Invoke-SafeLinksRemediation { + param($Tenant, $TemplateList, $Settings) + + $OverallSuccess = $true + $TemplateResults = @{} + + foreach ($TemplateItem in $TemplateList) { + $TemplateId = $TemplateItem.value + + try { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Processing SafeLinks template with ID: $TemplateId" -sev Info + + # Get template from storage + $Template = Get-SafeLinksTemplateFromStorage -TemplateId $TemplateId + + $PolicyName = $Template.PolicyName ?? $Template.Name + $RuleName = $Template.RuleName ?? "$($PolicyName)_Rule" + + # Check existing objects + $ExistingObjects = Get-ExistingSafeLinksObjects -Tenant $Tenant -PolicyName $PolicyName -RuleName $RuleName + + $ActionsTaken = [System.Collections.Generic.List[string]]::new() + + # Process Policy + $PolicyParams = New-SafeLinksPolicyParameters -Template $Template + + if ($ExistingObjects.PolicyExists) { + # Update existing policy to keep it in line + $PolicyParams['Identity'] = $PolicyName + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-SafeLinksPolicy' -cmdParams $PolicyParams -useSystemMailbox $true + $ActionsTaken.Add("Updated SafeLinks policy '$PolicyName'") + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Updated SafeLinks policy '$PolicyName'" -sev Info + } + else { + # Create new policy + $PolicyParams['Name'] = $PolicyName + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'New-SafeLinksPolicy' -cmdParams $PolicyParams -useSystemMailbox $true + $ActionsTaken.Add("Created new SafeLinks policy '$PolicyName'") + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Created new SafeLinks policy '$PolicyName'" -sev Info + } + + # Process Rule + $RuleParams = New-SafeLinksRuleParameters -Template $Template + + if ($ExistingObjects.RuleExists) { + # Update existing rule to keep it in line + $RuleParams['Identity'] = $RuleName + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-SafeLinksRule' -cmdParams $RuleParams -useSystemMailbox $true + $ActionsTaken.Add("Updated SafeLinks rule '$RuleName'") + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Updated SafeLinks rule '$RuleName'" -sev Info + } + else { + # Create new rule + $RuleParams['Name'] = $RuleName + $RuleParams['SafeLinksPolicy'] = $PolicyName + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'New-SafeLinksRule' -cmdParams $RuleParams -useSystemMailbox $true + $ActionsTaken.Add("Created new SafeLinks rule '$RuleName'") + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Created new SafeLinks rule '$RuleName'" -sev Info + } + + # Set rule state + $StateResult = Set-SafeLinksRuleState -Tenant $Tenant -RuleName $RuleName -State $Template.State + if ($StateResult) { + $ActionsTaken.Add("Rule $StateResult") + Write-LogMessage -API 'Standards' -tenant $Tenant -message "SafeLinks rule '$RuleName' $StateResult" -sev Info + } + + $TemplateResults[$TemplateId] = @{ + Success = $true + ActionsTaken = $ActionsTaken.ToArray() + TemplateName = $Template.TemplateName ?? $Template.Name + PolicyName = $PolicyName + RuleName = $RuleName + } + + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully applied SafeLinks template '$($Template.TemplateName ?? $Template.Name)'" -sev Info + } + catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $TemplateResults[$TemplateId] = @{ + Success = $false + Message = $ErrorMessage + TemplateName = $Template.TemplateName ?? $Template.Name ?? "Unknown" + } + $OverallSuccess = $false + + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply SafeLinks template ID $TemplateId : $ErrorMessage" -sev Error + } + } + + # Report overall results + if ($OverallSuccess) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully applied all SafeLinks templates" -sev Info + } + else { + $SuccessCount = ($TemplateResults.Values | Where-Object { $_.Success -eq $true }).Count + $TotalCount = $TemplateList.Count + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Applied $SuccessCount out of $TotalCount SafeLinks templates" -sev Info + } +} + +function Invoke-SafeLinksAlert { + param($Tenant, $TemplateList, $Settings) + + $AllTemplatesApplied = $true + $AlertMessages = [System.Collections.Generic.List[string]]::new() + + foreach ($TemplateItem in $TemplateList) { + $TemplateId = $TemplateItem.value + + try { + $Template = Get-SafeLinksTemplateFromStorage -TemplateId $TemplateId + $PolicyName = $Template.PolicyName ?? $Template.Name + $RuleName = $Template.RuleName ?? "$($PolicyName)_Rule" + + $ExistingObjects = Get-ExistingSafeLinksObjects -Tenant $Tenant -PolicyName $PolicyName -RuleName $RuleName + + if (-not $ExistingObjects.PolicyExists -or -not $ExistingObjects.RuleExists) { + $AllTemplatesApplied = $false + $Status = "SafeLinks template '$($Template.TemplateName ?? $Template.Name)' is not applied" + + if (-not $ExistingObjects.PolicyExists) { + $Status = "$Status - policy '$PolicyName' does not exist" + } + + if (-not $ExistingObjects.RuleExists) { + $Status = "$Status - rule '$RuleName' does not exist" + } + + $AlertMessages.Add($Status) + } + } + catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $AlertMessages.Add("Failed to check template with ID $TemplateId : $ErrorMessage") + $AllTemplatesApplied = $false + } + } + + if ($AllTemplatesApplied) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "All SafeLinks templates are correctly applied" -sev Info + } + else { + $AlertMessage = "One or more SafeLinks templates are not correctly applied: " + ($AlertMessages.ToArray() -join " | ") + Write-StandardsAlert -message $AlertMessage -object @{ + Templates = $TemplateList + Issues = $AlertMessages.ToArray() + } -tenant $Tenant -standardName 'SafeLinksTemplatePolicy' -standardId $Settings.standardId + + Write-LogMessage -API 'Standards' -tenant $Tenant -message $AlertMessage -sev Info + } +} + +function Invoke-SafeLinksReport { + param($Tenant, $TemplateList, $Settings) + + $AllTemplatesApplied = $true + $ReportResults = @{} + + foreach ($TemplateItem in $TemplateList) { + $TemplateId = $TemplateItem.value + + try { + $Template = Get-SafeLinksTemplateFromStorage -TemplateId $TemplateId + $PolicyName = $Template.PolicyName ?? $Template.Name + $RuleName = $Template.RuleName ?? "$($PolicyName)_Rule" + + $ExistingObjects = Get-ExistingSafeLinksObjects -Tenant $Tenant -PolicyName $PolicyName -RuleName $RuleName + + $ReportResults[$TemplateId] = @{ + Success = ($ExistingObjects.PolicyExists -and $ExistingObjects.RuleExists) + TemplateName = $Template.TemplateName ?? $Template.Name + PolicyName = $PolicyName + RuleName = $RuleName + PolicyExists = [bool]$ExistingObjects.PolicyExists + RuleExists = [bool]$ExistingObjects.RuleExists + } + + if (-not $ExistingObjects.PolicyExists -or -not $ExistingObjects.RuleExists) { + $AllTemplatesApplied = $false + } + } + catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $ReportResults[$TemplateId] = @{ + Success = $false + Message = $ErrorMessage + } + $AllTemplatesApplied = $false + } + } + + Add-CIPPBPAField -FieldName 'SafeLinksTemplatePolicy' -FieldValue $AllTemplatesApplied -StoreAs bool -Tenant $Tenant + + if ($AllTemplatesApplied) { + Set-CIPPStandardsCompareField -FieldName 'standards.SafeLinksTemplatePolicy' -FieldValue $true -Tenant $Tenant + } + else { + Set-CIPPStandardsCompareField -FieldName 'standards.SafeLinksTemplatePolicy' -FieldValue @{ + TemplateResults = $ReportResults + ProcessedTemplates = $TemplateList.Count + SuccessfulTemplates = ($ReportResults.Values | Where-Object { $_.Success -eq $true }).Count + } -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index bbf74d0e09a4..c650c95ef858 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -75,43 +75,48 @@ function Invoke-CIPPStandardSpamFilterPolicy { $MarkAsSpamWebBugsInHtml = if ($Settings.MarkAsSpamWebBugsInHtml) { 'On' } else { 'Off' } $MarkAsSpamSensitiveWordList = if ($Settings.MarkAsSpamSensitiveWordList) { 'On' } else { 'Off' } - $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and - ($CurrentState.SpamAction -eq $SpamAction) -and - ($CurrentState.SpamQuarantineTag -eq $SpamQuarantineTag) -and - ($CurrentState.HighConfidenceSpamAction -eq $HighConfidenceSpamAction) -and - ($CurrentState.HighConfidenceSpamQuarantineTag -eq $HighConfidenceSpamQuarantineTag) -and - ($CurrentState.BulkSpamAction -eq $BulkSpamAction) -and - ($CurrentState.BulkQuarantineTag -eq $BulkQuarantineTag) -and - ($CurrentState.PhishSpamAction -eq $PhishSpamAction) -and - ($CurrentState.PhishQuarantineTag -eq $PhishQuarantineTag) -and - ($CurrentState.HighConfidencePhishAction -eq 'Quarantine') -and - ($CurrentState.HighConfidencePhishQuarantineTag -eq $HighConfidencePhishQuarantineTag) -and - ($CurrentState.BulkThreshold -eq [int]$Settings.BulkThreshold) -and - ($CurrentState.QuarantineRetentionPeriod -eq 30) -and - ($CurrentState.IncreaseScoreWithImageLinks -eq $IncreaseScoreWithImageLinks) -and - ($CurrentState.IncreaseScoreWithNumericIps -eq 'On') -and - ($CurrentState.IncreaseScoreWithRedirectToOtherPort -eq 'On') -and - ($CurrentState.IncreaseScoreWithBizOrInfoUrls -eq $IncreaseScoreWithBizOrInfoUrls) -and - ($CurrentState.MarkAsSpamEmptyMessages -eq 'On') -and - ($CurrentState.MarkAsSpamJavaScriptInHtml -eq 'On') -and - ($CurrentState.MarkAsSpamFramesInHtml -eq $MarkAsSpamFramesInHtml) -and - ($CurrentState.MarkAsSpamObjectTagsInHtml -eq $MarkAsSpamObjectTagsInHtml) -and - ($CurrentState.MarkAsSpamEmbedTagsInHtml -eq $MarkAsSpamEmbedTagsInHtml) -and - ($CurrentState.MarkAsSpamFormTagsInHtml -eq $MarkAsSpamFormTagsInHtml) -and - ($CurrentState.MarkAsSpamWebBugsInHtml -eq $MarkAsSpamWebBugsInHtml) -and - ($CurrentState.MarkAsSpamSensitiveWordList -eq $MarkAsSpamSensitiveWordList) -and - ($CurrentState.MarkAsSpamSpfRecordHardFail -eq 'On') -and - ($CurrentState.MarkAsSpamFromAddressAuthFail -eq 'On') -and - ($CurrentState.MarkAsSpamNdrBackscatter -eq 'On') -and - ($CurrentState.MarkAsSpamBulkMail -eq 'On') -and - ($CurrentState.InlineSafetyTipsEnabled -eq $true) -and - ($CurrentState.PhishZapEnabled -eq $true) -and - ($CurrentState.SpamZapEnabled -eq $true) -and - ($CurrentState.EnableLanguageBlockList -eq $Settings.EnableLanguageBlockList) -and - ((-not $CurrentState.LanguageBlockList -and -not $Settings.LanguageBlockList.value) -or (!(Compare-Object -ReferenceObject $CurrentState.LanguageBlockList -DifferenceObject $Settings.LanguageBlockList.value))) -and - ($CurrentState.EnableRegionBlockList -eq $Settings.EnableRegionBlockList) -and - ((-not $CurrentState.RegionBlockList -and -not $Settings.RegionBlockList.value) -or (!(Compare-Object -ReferenceObject $CurrentState.RegionBlockList -DifferenceObject $Settings.RegionBlockList.value))) -and - (!(Compare-Object -ReferenceObject $CurrentState.AllowedSenderDomains -DifferenceObject ($Settings.AllowedSenderDomains.value ?? $Settings.AllowedSenderDomains))) + try { + $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and + ($CurrentState.SpamAction -eq $SpamAction) -and + ($CurrentState.SpamQuarantineTag -eq $SpamQuarantineTag) -and + ($CurrentState.HighConfidenceSpamAction -eq $HighConfidenceSpamAction) -and + ($CurrentState.HighConfidenceSpamQuarantineTag -eq $HighConfidenceSpamQuarantineTag) -and + ($CurrentState.BulkSpamAction -eq $BulkSpamAction) -and + ($CurrentState.BulkQuarantineTag -eq $BulkQuarantineTag) -and + ($CurrentState.PhishSpamAction -eq $PhishSpamAction) -and + ($CurrentState.PhishQuarantineTag -eq $PhishQuarantineTag) -and + ($CurrentState.HighConfidencePhishAction -eq 'Quarantine') -and + ($CurrentState.HighConfidencePhishQuarantineTag -eq $HighConfidencePhishQuarantineTag) -and + ($CurrentState.BulkThreshold -eq [int]$Settings.BulkThreshold) -and + ($CurrentState.QuarantineRetentionPeriod -eq 30) -and + ($CurrentState.IncreaseScoreWithImageLinks -eq $IncreaseScoreWithImageLinks) -and + ($CurrentState.IncreaseScoreWithNumericIps -eq 'On') -and + ($CurrentState.IncreaseScoreWithRedirectToOtherPort -eq 'On') -and + ($CurrentState.IncreaseScoreWithBizOrInfoUrls -eq $IncreaseScoreWithBizOrInfoUrls) -and + ($CurrentState.MarkAsSpamEmptyMessages -eq 'On') -and + ($CurrentState.MarkAsSpamJavaScriptInHtml -eq 'On') -and + ($CurrentState.MarkAsSpamFramesInHtml -eq $MarkAsSpamFramesInHtml) -and + ($CurrentState.MarkAsSpamObjectTagsInHtml -eq $MarkAsSpamObjectTagsInHtml) -and + ($CurrentState.MarkAsSpamEmbedTagsInHtml -eq $MarkAsSpamEmbedTagsInHtml) -and + ($CurrentState.MarkAsSpamFormTagsInHtml -eq $MarkAsSpamFormTagsInHtml) -and + ($CurrentState.MarkAsSpamWebBugsInHtml -eq $MarkAsSpamWebBugsInHtml) -and + ($CurrentState.MarkAsSpamSensitiveWordList -eq $MarkAsSpamSensitiveWordList) -and + ($CurrentState.MarkAsSpamSpfRecordHardFail -eq 'On') -and + ($CurrentState.MarkAsSpamFromAddressAuthFail -eq 'On') -and + ($CurrentState.MarkAsSpamNdrBackscatter -eq 'On') -and + ($CurrentState.MarkAsSpamBulkMail -eq 'On') -and + ($CurrentState.InlineSafetyTipsEnabled -eq $true) -and + ($CurrentState.PhishZapEnabled -eq $true) -and + ($CurrentState.SpamZapEnabled -eq $true) -and + ($CurrentState.EnableLanguageBlockList -eq $Settings.EnableLanguageBlockList) -and + ((-not $CurrentState.LanguageBlockList -and -not $Settings.LanguageBlockList.value) -or (!(Compare-Object -ReferenceObject $CurrentState.LanguageBlockList -DifferenceObject $Settings.LanguageBlockList.value))) -and + ($CurrentState.EnableRegionBlockList -eq $Settings.EnableRegionBlockList) -and + ((-not $CurrentState.RegionBlockList -and -not $Settings.RegionBlockList.value) -or (!(Compare-Object -ReferenceObject $CurrentState.RegionBlockList -DifferenceObject $Settings.RegionBlockList.value))) -and + (!(Compare-Object -ReferenceObject $CurrentState.AllowedSenderDomains -DifferenceObject ($Settings.AllowedSenderDomains.value ?? $Settings.AllowedSenderDomains))) + } + catch { + $StateIsCorrect = $false + } $AcceptedDomains = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-AcceptedDomain' diff --git a/cspell.json b/cspell.json index 1bb15e727bfa..74367cb934af 100644 --- a/cspell.json +++ b/cspell.json @@ -12,30 +12,54 @@ "CIPP", "CIPP-API", "Connectwise", + "CPIM", "Datto", + "endswith", + "entra", "Entra", + "gdap", "GDAP", + "IMAP", "Intune", + "locationcipp", + "MAPI", + "Multitenant", "OBEE", - "Passwordless", "passwordless", + "Passwordless", "PSTN", + "rvdwegen", + "sharepoint", + "SharePoint", "Sherweb", + "Signup", "SSPR", "Standardcal", "Terrl", "TNEF", + "weburl", "winmail", "Yubikey" ], "ignoreWords": [ + "ACOM", + "Sharepoint", "tenantid", "jnlp", + "wsfed", + "Imap", "APINAME", + "CIPPAPI", + "WCSS", + "cacheusers", "CIPPBPA", "CIPPCA", + "MCAPI", + "skuid", + "BPOS", + "EPMID", "CIPPSPO", - "CIPPAPI", + "CIPPTAP", "donotchange", "Addins", "Helptext", diff --git a/openapi.json b/openapi.json index 261f5778fe1b..9ac5a92720a0 100644 --- a/openapi.json +++ b/openapi.json @@ -8981,6 +8981,680 @@ } } }, + "/ExecDeleteSafeLinksPolicy": { + "get": { + "description": "ExecDeleteSafeLinksPolicy", + "summary": "ExecDeleteSafeLinksPolicy", + "tags": [ + "GET" + ], + "parameters": [ + { + "required": true, + "schema": { + "type": "string" + }, + "name": "tenantFilter", + "in": "query" + }, + { + "required": true, + "schema": { + "type": "string" + }, + "name": "RuleName", + "in": "query" + }, + { + "required": true, + "schema": { + "type": "string" + }, + "name": "PolicyName", + "in": "query" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": {}, + "type": "object" + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/EditSafeLinksPolicy": { + "post": { + "description": "EditSafeLinksPolicy", + "summary": "EditSafeLinksPolicy", + "tags": [ + "POST" + ], + "parameters": [ + { + "required": true, + "schema": { + "type": "string" + }, + "name": "tenantFilter", + "in": "query" + }, + { + "required": true, + "schema": { + "type": "string" + }, + "name": "PolicyName", + "in": "query" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "AdminDisplayName": { + "type": "string" + }, + "CustomNotificationText": { + "type": "string" + }, + "EnableOrganizationBranding": { + "type": "boolean" + }, + "Priority": { + "type": "integer" + }, + "Comments": { + "type": "string" + }, + "Enabled": { + "type": "boolean" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/ListSafeLinksPolicyDetails": { + "get": { + "description": "ListSafeLinksPolicyDetails", + "summary": "ListSafeLinksPolicyDetails", + "tags": [ + "GET" + ], + "parameters": [ + { + "required": true, + "schema": { + "type": "string" + }, + "name": "tenantFilter", + "in": "query" + }, + { + "required": true, + "schema": { + "type": "string" + }, + "name": "PolicyName", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "string" + }, + "name": "RuleName", + "in": "query" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "properties": { + "Policy": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "AdminDisplayName": { + "type": "string" + }, + "CustomNotificationText": { + "type": "string" + }, + "EnableOrganizationBranding": { + "type": "boolean" + } + } + }, + "Rule": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Priority": { + "type": "integer" + }, + "Comments": { + "type": "string" + }, + "State": { + "type": "string" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Message": { + "type": "string" + } + } + } + } + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/ExecNewSafeLinksPolicy": { + "post": { + "description": "Create a new SafeLinks policy and associated rule", + "summary": "Create SafeLinks Policy and Rule Configuration", + "tags": [ + "POST" + ], + "parameters": [ + { + "required": true, + "schema": { + "type": "string" + }, + "name": "tenantFilter", + "in": "query" + }, + { + "required": true, + "schema": { + "type": "string" + }, + "name": "Name", + "in": "query" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "EnableSafeLinksForEmail": { + "type": "boolean", + "description": "Enable Safe Links protection for email messages" + }, + "EnableSafeLinksForTeams": { + "type": "boolean", + "description": "Enable Safe Links protection for Teams messages" + }, + "EnableSafeLinksForOffice": { + "type": "boolean", + "description": "Enable Safe Links protection for Office applications" + }, + "TrackClicks": { + "type": "boolean", + "description": "Track user clicks related to Safe Links protection" + }, + "AllowClickThrough": { + "type": "boolean", + "description": "Allow users to click through to the original URL" + }, + "ScanUrls": { + "type": "boolean", + "description": "Enable real-time scanning of URLs" + }, + "EnableForInternalSenders": { + "type": "boolean", + "description": "Enable Safe Links for messages sent between internal senders" + }, + "DeliverMessageAfterScan": { + "type": "boolean", + "description": "Wait until URL scanning is complete before delivering the message" + }, + "DisableUrlRewrite": { + "type": "boolean", + "description": "Disable the rewriting of URLs in messages" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of URLs that will not be rewritten by Safe Links" + }, + "AdminDisplayName": { + "type": "string", + "description": "Display name for the policy in the admin interface" + }, + "CustomNotificationText": { + "type": "string", + "description": "Custom text for the notification when a link is blocked" + }, + "EnableOrganizationBranding": { + "type": "boolean", + "description": "Enable organization branding on warning pages" + }, + "Priority": { + "type": "integer", + "description": "Priority of the rule (lower numbers = higher priority)" + }, + "Comments": { + "type": "string", + "description": "Administrative comments for the rule" + }, + "Enabled": { + "type": "boolean", + "description": "Whether the rule is enabled or disabled" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Apply the rule if the recipient is any of these email addresses" + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Apply the rule if the recipient is a member of any of these groups" + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Apply the rule if the recipient's domain matches any of these domains" + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Do not apply the rule if the recipient is any of these email addresses" + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Do not apply the rule if the recipient is a member of any of these groups" + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Do not apply the rule if the recipient's domain matches any of these domains" + } + } + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "string", + "description": "Result message from the operation" + } + } + } + } + }, + "description": "Successfully created SafeLinks policy and rule" + }, + "400": { + "description": "Bad request - missing required parameters", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "string", + "description": "Error message" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "string", + "description": "Error message" + } + } + } + } + } + } + } + } + }, + "/ListSafeLinksPolicyTemplates": { + "get": { + "description": "List SafeLinks Policy Templates", + "summary": "List SafeLinks Policy Templates", + "tags": [ + "GET" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/RemoveSafeLinksPolicyTemplate": { + "get": { + "description": "Remove SafeLinks Policy Template", + "summary": "Remove SafeLinks Policy Template", + "tags": [ + "GET" + ], + "parameters": [ + { + "required": true, + "schema": { + "type": "string" + }, + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": {}, + "type": "object" + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/AddSafeLinksPolicyTemplate": { + "post": { + "description": "Add SafeLinks Policy Template", + "summary": "Add SafeLinks Policy Template", + "tags": [ + "POST" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": {}, + "type": "object" + } + } + }, + "description": "Successful operation" + } + } + } + }, + "/AddSafeLinksPolicyFromTemplate": { + "post": { + "description": "Deploy SafeLinks Policy From Template", + "summary": "Deploy SafeLinks Policy From Template", + "tags": [ + "POST" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": {}, + "type": "object" + } + } + }, + "description": "Successful operation" + } + } + } + }, "openapi": "3.1.0", "servers": [ {