diff --git a/Z_CIPPHttpTrigger/function.json b/CIPPHttpTrigger/function.json similarity index 100% rename from Z_CIPPHttpTrigger/function.json rename to CIPPHttpTrigger/function.json diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 index bfd2aacc155e..2faa3fac26eb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -1,14 +1,33 @@ function Push-AuditLogTenant { Param($Item) - - $SchedulerConfig = Get-CippTable -TableName 'SchedulerConfig' $ConfigTable = Get-CippTable -TableName 'WebhookRules' - #$Tenant = Get-Tenants -TenantFilter $Item.customerId -IncludeErrors $TenantFilter = $Item.TenantFilter Write-Information "Audit Logs: Processing $($TenantFilter)" - # Query CIPPURL for linking - $CIPPURL = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" | Select-Object -First 1 -ExpandProperty CIPPURL + + # Get CIPP Url, cleanup legacy tasks + $SchedulerConfig = Get-CippTable -TableName 'SchedulerConfig' + $LegacyWebhookTasks = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" + $LegacyUrl = $LegacyWebhookTasks | Select-Object -First 1 -ExpandProperty CIPPURL + $CippConfigTable = Get-CippTable -tablename Config + $CippConfig = Get-CIPPAzDataTableEntity @CippConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" + if ($LegacyUrl) { + if (!$CippConfig) { + $Entity = @{ + PartitionKey = 'InstanceProperties' + RowKey = 'CIPPURL' + Value = [string]([System.Uri]$LegacyUrl).Host + } + Add-CIPPAzDataTableEntity @CippConfigTable -Entity $Entity -Force + } + # remove legacy webhooks + foreach ($Task in $LegacyWebhookTasks) { + Remove-AzDataTableEntity @SchedulerConfig -Entity $Task + } + $CIPPURL = $LegacyUrl + } else { + $CIPPURL = 'https://{0}' -f $CippConfig.Value + } # Get webhook rules $ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable 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 38b476922adc..320a196a0f3a 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 @@ -14,11 +14,10 @@ Function Invoke-AddUser { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $UserObj = $Request.body - # Write to the Azure Functions log stream. - Write-Host 'PowerShell HTTP trigger function processed a request.' + if ($UserObj.Scheduled.Enabled) { $TaskBody = [pscustomobject]@{ - TenantFilter = 'AllTenants' + TenantFilter = $UserObj.tenantID Name = "New user creation: $($UserObj.User)@$($UserObj.Domain)" Command = @{ value = 'New-CIPPUserTask' 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 new file mode 100644 index 000000000000..ee418b6b145b --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ExecAuditLogSearch.ps1 @@ -0,0 +1,54 @@ +function Invoke-ExecAuditLogSearch { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Alert.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $Query = $Request.Body + if (!$Query.TenantFilter) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = 'TenantFilter is required' + }) + return + } + if (!$Query.StartTime -or !$Query.EndTime) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = 'StartTime and EndTime are required' + }) + return + } + + $Command = Get-Command New-CippAuditLogSearch + $AvailableParameters = $Command.Parameters.Keys + $BadProps = foreach ($Prop in $Query.PSObject.Properties.Name) { + if ($AvailableParameters -notcontains $Prop) { + $Prop + } + } + if ($BadProps) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "Invalid parameters: $($BadProps -join ', ')" + }) + return + } + + try { + $Results = New-CippAuditLogSearch @Query + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) + } catch { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = $_.Exception.Message + }) + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 new file mode 100644 index 000000000000..b72187063e48 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 @@ -0,0 +1,77 @@ +function Invoke-ListAuditLogSearches { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Alert.Read + #> + Param($Request, $TriggerMetadata) + + if ($Request.Query.TenantFilter) { + switch ($Request.Query.Type) { + 'Searches' { + $Results = Get-CippAuditLogSearches -TenantFilter $Request.Query.TenantFilter + $Body = @{ + Results = @($Results) + Metadata = @{ + TenantFilter = $Request.Query.TenantFilter + TotalSearches = $Results.Count + } + } | ConvertTo-Json -Depth 10 -Compress + } + 'SearchResults' { + $Results = Get-CippAuditLogSearchResults -TenantFilter $Request.Query.TenantFilter -QueryId $Request.Query.SearchId + $Body = @{ + Results = @($Results) + Metadata = @{ + SearchId = $Request.Query.SearchId + TenantFilter = $Request.Query.TenantFilter + TotalResults = $Results.Count + } + } | ConvertTo-Json -Depth 10 -Compress + } + default { + if ($Request.Query.Days) { + $Days = $Request.Query.Days + } else { + $Days = 1 + } + $StartTime = (Get-Date).AddDays(-$Days).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + + $Table = Get-CIPPTable -TableName 'AuditLogSearches' + $Results = Get-CIPPAzDataTableEntity @Table -Filter "StartTime ge datetime'$StartTime'" | ForEach-Object { + $Query = try { $_.Query | ConvertFrom-Json } catch { $_.Query } + $MatchedRules = try { $_.MatchedRules | ConvertFrom-Json } catch { $_.MatchedRules } + [PSCustomObject]@{ + SearchId = $_.RowKey + StartTime = $_.StartTime.DateTime + EndTime = $_.EndTime.DateTime + Query = $Query + MatchedRules = $MatchedRules + TotalLogs = $_.TotalLogs + MatchedLogs = $_.MatchedLogs + CippStatus = $_.CippStatus + } + } + + $Body = @{ + Results = @($Results) + Metadata = @{ + StartTime = $StartTime + TenantFilter = $Request.Query.TenantFilter + } + } | ConvertTo-Json -Depth 10 -Compress + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) + } else { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = 'TenantFilter is required' + }) + } +} 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 34576f5b5811..bc3a73fbe686 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 @@ -10,8 +10,7 @@ function Invoke-ListAuditLogTest { $AuditLogQuery = @{ TenantFilter = $Request.Query.TenantFilter - LogType = $Request.Query.LogType - ShowAll = $true + SearchId = $Request.Query.SearchId } try { $TestResults = Test-CIPPAuditLogRules @AuditLogQuery @@ -38,4 +37,4 @@ function Invoke-ListAuditLogTest { Body = $Body }) -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 index 6a1267a53e51..dbb63b088425 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 @@ -112,6 +112,10 @@ function Invoke-ListGraphRequest { } } + if ($Request.Query.AsApp) { + $GraphRequestParams.AsApp = $true + } + Write-Host ($GraphRequestParams | ConvertTo-Json) $Metadata = $GraphRequestParams diff --git a/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 b/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 index ea18ca891c16..6d00a366827f 100644 --- a/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 @@ -30,7 +30,7 @@ function New-CIPPUserTask { try { if ($Userobj.AddedAliases) { - $AliasResults = Add-CIPPAlias -user $CreationResults.username -Aliases ($UserObj.AddedAliases -split '\s') -UserprincipalName $UserObj.UserprincipalName -TenantFilter $UserObj.tenantID -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' + $AliasResults = Add-CIPPAlias -user $CreationResults.username -Aliases ($UserObj.AddedAliases -split '\s') -UserprincipalName $CreationResults.Username -TenantFilter $UserObj.tenantID -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' $results.add($AliasResults) } } catch { @@ -38,7 +38,7 @@ function New-CIPPUserTask { $body = $results.add("Failed to create the Aliases: $($_.Exception.Message)") } if ($userobj.CopyFrom -ne '') { - $CopyFrom = Set-CIPPCopyGroupMembers -ExecutingUser $request.headers.'x-ms-client-principal' -CopyFromId $userObj.CopyFrom -UserID $UserObj.UserprincipalName -TenantFilter $UserObj.tenantID + $CopyFrom = Set-CIPPCopyGroupMembers -ExecutingUser $request.headers.'x-ms-client-principal' -CopyFromId $userObj.CopyFrom -UserID $CreationResults.Username -TenantFilter $UserObj.tenantID $CopyFrom.Success | ForEach-Object { $results.Add($_) } $CopyFrom.Error | ForEach-Object { $results.Add($_) } } diff --git a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 index 41d60a70b521..7711dd0c7f26 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 @@ -5,26 +5,92 @@ function Set-CIPPCopyGroupMembers { [string]$UserId, [string]$CopyFromId, [string]$TenantFilter, - [string]$APIName = 'Copy User Groups' + [string]$APIName = 'Copy User Groups', + [switch]$ExchangeOnly ) - $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserId" -tenantid $TenantFilter).id - $AddMemberBody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" + + $Requests = @( + @{ + id = 'User' + url = 'users/{0}' -f $UserId + method = 'GET' + } + @{ + id = 'UserMembership' + url = 'users/{0}/memberOf' -f $UserId + method = 'GET' + } + @{ + id = 'CopyFromMembership' + url = 'users/{0}/memberOf' -f $CopyFromId + method = 'GET' + } + ) + $Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter + $User = ($Results | Where-Object { $_.id -eq 'User' }).body + $CurrentMemberships = ($Results | Where-Object { $_.id -eq 'UserMembership' }).body.value + $CopyFromMemberships = ($Results | Where-Object { $_.id -eq 'CopyFromMembership' }).body.value + + Write-Information ($Results | ConvertTo-Json -Depth 10) + + $ODataBind = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $User.id + $AddMemberBody = @{ + '@odata.id' = $ODataBind + } | ConvertTo-Json -Compress $Success = [System.Collections.Generic.List[string]]::new() $Errors = [System.Collections.Generic.List[string]]::new() - (New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$CopyFromId/memberOf" -tenantid $TenantFilter) | Where-Object { $_.GroupTypes -notin 'herohero' } | ForEach-Object { + $Memberships = $CopyFromMemberships | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' -and $_.groupTypes -notcontains 'DynamicMembership' -and $_.onPremisesSyncEnabled -ne $true -and $_.visibility -ne 'Public' -and $CurrentMemberships.id -notcontains $_.id } + $ScheduleExchangeGroupTask = $false + foreach ($MailGroup in $Memberships) { try { - $MailGroup = $_ - if ($PSCmdlet.ShouldProcess($_.displayName, "Add $UserId to group")) { - if ($MailGroup.MailEnabled -and $Mailgroup.ResourceProvisioningOptions -notin 'Team') { - $Params = @{ Identity = $MailGroup.mail; Member = $UserId; 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/$($_.id)" -tenantid $TenantFilter -type patch -body $AddMemberBody -Verbose + if ($PSCmdlet.ShouldProcess($MailGroup.displayName, "Add $UserId to group")) { + if ($MailGroup.MailEnabled -and $Mailgroup.ResourceProvisioningOptions -notcontains 'Team' -and $MailGroup.groupTypes -notcontains 'Unified') { + $Params = @{ Identity = $MailGroup.mailNickname; Member = $UserId; BypassSecurityGroupManagerCheck = $true } + try { + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + } catch { + if ($_.Exception.Message -match 'Ex94914C|Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException') { + if (($User.assignedLicenses | Measure-Object).Count -gt 0 -and !$ExchangeOnly.IsPresent) { + $ScheduleExchangeGroupTask = $true + } else { + throw $_ + } + } else { + throw $_ + } + } + } elseif (!$ExchangeOnly.IsPresent) { + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($MailGroup.id)/members/`$ref" -tenantid $TenantFilter -body $AddMemberBody -Verbose + } + } + + if ($ScheduleExchangeGroupTask) { + $TaskBody = [PSCustomObject]@{ + TenantFilter = $TenantFilter + Name = "Copy Exchange Group Membership: $UserId from $CopyFromId" + Command = @{ + value = 'Set-CIPPCopyGroupMembers' + } + Parameters = [PSCustomObject]@{ + UserId = $UserId + CopyFromId = $CopyFromId + TenantFilter = $TenantFilter + ExchangeOnly = $true + } + ScheduledTime = [int64](([datetime]::UtcNow).AddMinutes(5) - (Get-Date '1/1/1970')).TotalSeconds + PostExecution = @{ + Webhook = $false + Email = $false + PSA = $false + } } + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + $Errors.Add("We've scheduled a task to add $UserId to the Exchange group $($MailGroup.displayName)") | Out-Null + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Added $UserId to group $($MailGroup.displayName)" -Sev 'Info' -tenant $TenantFilter + $Success.Add("Added user to group: $($MailGroup.displayName)") | Out-Null } - Write-LogMessage -user $ExecutingUser -API $APIName -message "Added $UserId to group $($_.displayName)" -Sev 'Info' -tenant $TenantFilter - $Success.Add("Added group: $($MailGroup.displayName)") | Out-Null } catch { $ErrorMessage = Get-CippException -Exception $_ $Errors.Add("We've failed to add the group $($MailGroup.displayName): $($ErrorMessage.NormalizedError)") | Out-Null diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 6286b64246f0..1bda0cce5b84 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -57,38 +57,6 @@ function Receive-CippHttpTrigger { } } -function Receive-CippQueueTrigger { - Param($QueueItem, $TriggerMetadata) - - Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName - $Start = (Get-Date).ToUniversalTime() - $APIName = $TriggerMetadata.FunctionName - Write-Information "#### Running $APINAME" - Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName - $FunctionName = 'Push-{0}' -f $APIName - $QueueTrigger = @{ - QueueItem = $QueueItem - TriggerMetadata = $TriggerMetadata - } - try { - & $FunctionName @QueueTrigger - } catch { - $ErrorMsg = $_.Exception.Message - } - - $End = (Get-Date).ToUniversalTime() - - $Stats = @{ - FunctionType = 'Queue' - Entity = $QueueItem - Start = $Start - End = $End - ErrorMsg = $ErrorMsg - } - Write-Information '####### Adding stats' - Write-CippFunctionStats @Stats -} - function Receive-CippOrchestrationTrigger { param($Context) @@ -267,5 +235,5 @@ function Receive-CIPPTimerTrigger { } } -Export-ModuleMember -Function @('Receive-CippHttpTrigger', 'Receive-CippQueueTrigger', 'Receive-CippOrchestrationTrigger', 'Receive-CippActivityTrigger', 'Receive-CIPPTimerTrigger') +Export-ModuleMember -Function @('Receive-CippHttpTrigger', 'Receive-CippOrchestrationTrigger', 'Receive-CippActivityTrigger', 'Receive-CIPPTimerTrigger') diff --git a/Z_CIPPQueueTrigger/function.json b/Z_CIPPQueueTrigger/function.json deleted file mode 100644 index 7f7dde0a4cbd..000000000000 --- a/Z_CIPPQueueTrigger/function.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "scriptFile": "../Modules/CippEntrypoints/CippEntrypoints.psm1", - "entryPoint": "Receive-CippQueueTrigger", - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "CIPPGenericQueue" - }, - { - "name": "starter", - "type": "durableClient", - "direction": "in" - } - ] -}