core/modules/monkeyhtml/public/New-HtmlReport.ps1

# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Function New-HtmlReport{
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .NOTES
            Author : Juan Garrido
            Twitter : @tr1ana
            File Name : New-HtmlReport
            Version : 1.0
 
        .LINK
            https://github.com/silverhack/monkey365
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true, HelpMessage="Report Object")]
        [Object]$Report,

        [parameter(Mandatory= $true, ParameterSetName = 'ConfigFile', HelpMessage= "json config")]
        [ValidateScript({
            if( -Not (Test-Path -Path $_) ){
                throw ("The HTML config does not exist in {0}" -f (Split-Path -Path $_))
            }
            if(-Not (Test-Path -Path $_ -PathType Leaf) ){
                throw "The HTML config argument must be a json file. Folder paths are not allowed."
            }
            if($_ -notmatch "(\.json)"){
                throw "The file specified in the config argument must be of type json"
            }
            return $true
        })]
        [System.IO.FileInfo]$ConfigFile,

        [Parameter(Mandatory=$true, ParameterSetName = 'CDN', HelpMessage="Load resources from external source")]
        [String]$Repository,

        [Parameter(Mandatory=$false, HelpMessage="Repository branch")]
        [String]$Branch = "main",

        [Parameter(Mandatory=$true, ParameterSetName = 'Config', HelpMessage="Config object")]
        [Object]$Config,

        [Parameter(Mandatory=$false, HelpMessage="Local assets path")]
        [Parameter(Mandatory=$true, ParameterSetName = 'Config', HelpMessage="Config object")]
        [System.IO.DirectoryInfo]$AssetsPath,

        [Parameter(Mandatory=$true, HelpMessage="Execution info object")]
        [Object]$ExecutionInfo,

        [Parameter(Mandatory=$true, HelpMessage="Rules")]
        [Object]$Rules,

        [Parameter(Mandatory=$true, HelpMessage="Ruleset info")]
        [Object]$RulesetInfo,

        [Parameter(Mandatory=$false, HelpMessage="Instance")]
        [String]$Instance,

        [parameter(Mandatory= $true, HelpMessage= "Directory output")]
        [ValidateScript({
            If( -Not (Test-Path -Path $_) ){
                throw ("The directory does not exist in {0}" -f (Split-Path -Path $_))
            }
            If(-Not (Test-Path -Path $_ -PathType Container) ){
                throw "The OutDir argument must be a directory. Files are not allowed."
            }
            return $true
        })]
        [System.IO.DirectoryInfo]$OutDir
    )
    Process{
        Write-Verbose $Script:messages.InitializeMonkeyhtml
        $initialized = Initialize-MonkeyHtml @PSBoundParameters
        If($initialized){
            $header = Get-HtmlHeader
            #Add to template
            [void]$Script:Template.DocumentElement.AppendChild($header)
            #Add body
            $bodyDiv = New-HtmlTag -Name body -ClassName "monkey-scrollbar"
            $bodyDiv.RemoveAttribute("xmlns");
            #Add wrapper
            $wrapperDiv = New-HtmlTag -Name div -ClassName "wrapper"
            #Add sidebar Div
            $sideBarDiv = New-SideBar -InputObject $Script:Report
            #Add main Div
            $mainDiv = New-HtmlTag -Name div -ClassName "main"
            #Get navBar
            $navBar = New-HTMLNavBar
            #Get issue cards
            $ContainerCards = Get-HtmlContainerCard -InputObject $Script:Report
            #Get all modal objects
            $modals = Get-AllModalHtmlObject -Report $Script:Report
            $c = $Script:Template.CreateComment('Monkey365 modal objects')
            #Add to body
            [void]$bodyDiv.AppendChild($c);
            #Add modals
            Foreach($modalObj in $modals){
                [void]$bodyDiv.AppendChild($modalObj);
            }
            $c = $Script:Template.CreateComment('End Monkey365 modal objects')
            #Add to body
            [void]$bodyDiv.AppendChild($c);
            #Add to main div
            [void]$mainDiv.AppendChild($navBar);
            #Add sidebar and main to wrapper div
            $c = $Script:Template.CreateComment('Sidebar')
            [void]$wrapperDiv.AppendChild($c);
            [void]$wrapperDiv.AppendChild($sideBarDiv);
            $c = $Script:Template.CreateComment('End Sidebar');
            [void]$wrapperDiv.AppendChild($c);
            #Create Div class content
            $DivContent = New-HtmlTag -Name div -ClassName "content"
            #Create Div container fluid
            $DivContainerFluid = New-HtmlTag -Name div -ClassName "container-fluid" -Id "Monkey365Data"
            #Add provider info
            $Provider = New-AccountInfo -Instance $Instance
            #Add to container fluid
            $c = $Script:Template.CreateComment('Provider info');
            [void]$DivContainerFluid.AppendChild($c);
            [void]$DivContainerFluid.AppendChild($Provider);
            $c = $Script:Template.CreateComment('End Provider info');
            [void]$DivContainerFluid.AppendChild($c);
            #Add scan details info content
            $scanInfoContent = New-HtmlScanDetailsCard
            If($scanInfoContent){
                $c = $Script:Template.CreateComment('Monkey365 scan info');
                [void]$DivContainerFluid.AppendChild($c);
                [void]$DivContainerFluid.AppendChild($scanInfoContent);
                $c = $Script:Template.CreateComment('End Monkey365 scan info');
                [void]$DivContainerFluid.AppendChild($c);
            }
            #Add dashboard to main content
            $mainDashboard = New-HtmlMainDashboard -InputObject $Script:Report -HorizontalStackedBar -Donut
            If($mainDashboard){
                $c = $Script:Template.CreateComment('Monkey365 main dashboard');
                [void]$DivContainerFluid.AppendChild($c);
                [void]$DivContainerFluid.AppendChild($mainDashboard);
                $c = $Script:Template.CreateComment('End Monkey365 main dashboard');
                [void]$DivContainerFluid.AppendChild($c);
            }
            #Add finding cards
            $c = $Script:Template.CreateComment('Monkey365 finding cards');
            [void]$DivContainerFluid.AppendChild($c);
            Foreach($containerCard in @($ContainerCards)){
                [void]$DivContainerFluid.AppendChild($containerCard);
            }
            $c = $Script:Template.CreateComment('End Monkey365 finding cards');
            [void]$DivContainerFluid.AppendChild($c);
            #Get empty card
            $monkeyEmptyCard = New-HtmlContainerCard -CardTitle "Monkey365 findings" -Img (Get-SvgIcon -InputObject monkey365)
            #Get Body
            $cardBody = $monkeyEmptyCard.SelectSingleNode('//div[contains(@class,"card-body")]')
            #Add Id
            [void]$cardBody.SetAttribute('id','Monkey365Findings')
            $newRow = $Script:Template.CreateElement("div");
            [void]$newRow.SetAttribute('class','row d-none')
            [void]$newRow.SetAttribute('id','Monkey365GlobalFindings')
            #Append card
            [void]$newRow.AppendChild($monkeyEmptyCard);
            $c = $Script:Template.CreateComment('Monkey365 empty card')
            #Add to body
            [void]$DivContainerFluid.AppendChild($c);
            #Add row
            [void]$DivContainerFluid.AppendChild($newRow);
            $c = $Script:Template.CreateComment('End Monkey365 empty card')
            [void]$DivContainerFluid.AppendChild($c);
            #Add to div content
            [void]$DivContent.AppendChild($DivContainerFluid);
            #Add div content to main div
            [void]$mainDiv.AppendChild($DivContent);
            #Add main content to wrapper div
            [void]$wrapperDiv.AppendChild($mainDiv);
            #Add to body
            [void]$bodyDiv.AppendChild($wrapperDiv);
            #Insert body into html
            #$myHeader = $html.ImportNode($header.get_DocumentElement(), $True)
            #$myHeader.RemoveAttribute("xmlns");
            #Add more javascript
            $helperJS = Get-JSHelper
            ForEach($helper in @($helperJS)){
                [void]$bodyDiv.AppendChild($helper);
            }
            #Add to template
            [void]$Script:Template.DocumentElement.AppendChild($bodyDiv)
            #Create comment for body
            $c = $Script:Template.CreateComment('End body element')
            [void]$Script:Template.DocumentElement.AppendChild($c);
            #Add to html
            [void]$Script:Template.html.SetAttribute('xmlns','http://www.w3.org/1999/html')
            #Set html document type
            #check PsVersion
            If($PSVersionTable.PSVersion.Major -lt 7){
                $documentType = $Script:Template.CreateDocumentType('html',$null,$null,$null)
            }
            Else{
                $documentType = $Script:Template.CreateDocumentType('html','-//W3C//DTD HTML 4.01//EN','http://www.w3.org/TR/html4/strict.dtd',$null)
            }
            #$documentType = $html.CreateDocumentType('html',$null,$null,$null)
            [void]$Script:Template.PrependChild($documentType);
            $indented = Update-XMLIndent -Content $Script:Template -Indent 1
            $_decodedHtml = [System.Web.HttpUtility]::HtmlDecode($indented)
            #Set out file
            If($PSCmdlet.ParameterSetName.ToLower() -eq "cdn"){
                $outFile = ("{0}{1}monkey365_cdn_{2}{3}.html" -f $Script:OutDir, [System.IO.Path]::DirectorySeparatorChar, $ExecutionInfo.tenant.tenantId.Replace('-',''), ([System.DateTime]::UtcNow).ToString("yyyyMMddHHmmss"))
            }
            Else{
                $outFile = ("{0}{1}monkey365_local_{2}{3}.html" -f $Script:OutDir, [System.IO.Path]::DirectorySeparatorChar, $ExecutionInfo.tenant.tenantId.Replace('-',''), ([System.DateTime]::UtcNow).ToString("yyyyMMddHHmmss"))
            }
            $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
            [System.IO.File]::WriteAllLines($outFile, $_decodedHtml, $Utf8NoBomEncoding)
        }
    }
    End{
        #Cleaning vars
        Remove-Variable -Name Template -Force -ErrorAction Ignore
        Remove-Variable -Name Report -Force -ErrorAction Ignore
        Remove-Variable -Name ExecutionInfo -Force -ErrorAction Ignore
        Remove-Variable -Name Rules -Force -ErrorAction Ignore
        Remove-Variable -Name RulesetInfo -Force -ErrorAction Ignore
    }
}