core/init/New-O365Object.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-O365Object{
    <#
        .SYNOPSIS
        Function to create new O365Object
 
        .DESCRIPTION
        Function to create new O365Object
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .NOTES
            Author : Juan Garrido
            Twitter : @tr1ana
            File Name : New-O365Object
            Version : 1.0
 
        .LINK
            https://github.com/silverhack/monkey365
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [CmdletBinding()]
    Param()
    Begin{
        try{
            #Check for MyParams var
            If($null -eq (Get-Variable -Name MyParams -ErrorAction Ignore)){
                $MyParams = @{}
                return
            }
            #Set vars
            $OnlineServices = @{}
            $VerboseOptions = @{}
            $SystemInfo = $UserAgent = $nestedMaxThreads = $internal_config_json = $null
            #Init params
            $init_params = @(
                'AuditorName','Threads',
                'PromptBehavior','Environment',
                'Instance', 'Ruleset','RulesPath','ExportTo',
                'Collect','WriteLog','TenantId','ClientId',
                'IncludeEntraID','ImportJob',
                'SaveProject','ResolveTenantDomainName',
                'ResolveTenantUserName','ExcludeCollector',
                'ExcludedResources','ForceMSALDesktop',
                'Compress'
            )
            #Get SystemInfo
            If($null -ne (Get-Command -Name "Get-MonkeySystemInfo" -ErrorAction Ignore)){
                $SystemInfo = Get-MonkeySystemInfo
                #Get OS version
                If($null -ne (Get-Command -Name "Get-OSVersion" -ErrorAction Ignore) -and $null -ne $SystemInfo){
                    $SystemInfo.OSVersion = Get-OSVersion
                }
                If($null -ne $SystemInfo -and ($MyParams.ContainsKey('ForceMSALDesktop') -and $MyParams['ForceMSALDesktop'])){
                    $SystemInfo.MsalType = 'Desktop'
                }
            }
            #Get a new User-Agent
            $UserAgent = Get-MonkeyUserAgent
            #Set OnlineServices
            If($null -ne (Get-Variable -Name m365_plugins -ErrorAction Ignore)){
                #Add Azure and EntraID
                [ref]$null = $OnlineServices.Add('Azure',$false)
                [ref]$null = $OnlineServices.Add('EntraID',$false)
                #Iterate over all Microsoft 365 services
                ForEach($service in $m365_plugins.GetEnumerator()){
                    [ref]$null = $OnlineServices.Add($service,$false)
                }
            }
            ################### VERBOSE OPTIONS #######################
            #Check verbose options
            If($MyParams.ContainsKey('Verbose') -and $MyParams.Verbose -eq $true){
                [void]$VerboseOptions.Add('Verbose',$true);
                [void]$VerboseOptions.Add('VerbosePreference','Continue');
            }
            Else{
                [void]$VerboseOptions.Add('Verbose',$false)
            }
            #Check Debug options
            If($MyParams.ContainsKey('Debug') -and $MyParams.Debug -eq $true){
                [void]$VerboseOptions.Add('DebugPreference','Continue')
                [void]$VerboseOptions.Add("Debug",$true)
            }
            Else{
                [void]$VerboseOptions.Add("Debug",$false)
            }
            ################### LOG, CONSOLE OPTIONS #######################
            If($MyParams.ContainsKey('InformationAction')){
                Set-Variable InformationAction -Value $MyParams.InformationAction -Scope Script -Force
            }
            Else{
                Set-Variable InformationAction -Value "SilentlyContinue" -Scope Script -Force
            }
            ################## Set Initial params #########################
            ForEach ($p in $init_params){
                If ($false -eq $MyParams.ContainsKey($p)){
                    [void]$MyParams.Add($p,$null)
                }
            }
            #Set Compress option
            If($null -eq $MyParams.Compress){
                $MyParams.Compress = $false
            }
            #Set Output Dir
            If($false -eq $MyParams.ContainsKey('OutDir')){
                $MyParams.OutDir = ("{0}/monkey-reports" -f $ScriptPath)
            }
            #Set Environment
            If($null -eq $MyParams.Environment){
                $MyParams.Environment = $Environment
            }
            <#
            #Set Prompt
            If($null -eq $MyParams.PromptBehavior){
                $MyParams.PromptBehavior = 'Auto'
            }
            #>

            #Add threads to params If not exists
            If($null -eq $MyParams.Threads){
                $MyParams.Threads = 2;
            }
            #Add auditorName If not exists
            If($null -eq $MyParams.auditorName){
                $MyParams.AuditorName = $env:USERNAME
            }
            #TODO Set instance
            If($null -eq $MyParams.Instance -and $null -ne $MyParams.IncludeEntraID){
                $MyParams.Instance = 'EntraID';
            }
            #Set Verbose and Debug options
            If($false -eq $MyParams.ContainsKey('Verbose')){
                $MyParams.Verbose = $false
            }
            If($false -eq $MyParams.ContainsKey('Debug')){
                $MyParams.Debug = $false
            }
            #Set informationAction
            If($false -eq $MyParams.ContainsKey('InformationAction')){
                $MyParams.InformationAction = 'SilentlyContinue'
            }
            #Override params with environment vars If any
            #Check If username and password
            If ((Test-Path env:MONKEY_ENV_MONKEY_USER) -and (Test-Path env:MONKEY_ENV_MONKEY_PASSWORD)){
                try{
                    [securestring]$cred = ConvertTo-SecureString $env:MONKEY_ENV_MONKEY_PASSWORD
                    [pscredential]$InputObject = New-Object System.Management.Automation.PSCredential ($env:MONKEY_ENV_MONKEY_USER, $cred)
                    $MyParams.UserCredentials = $InputObject
                }
                catch{
                    Write-Error $_
                }
            }
            #Check If TenantID
            If (Test-Path env:MONKEY_ENV_TENANT_ID){
                $MyParams.TenantID = $env:MONKEY_ENV_TENANT_ID
            }
            #Check If AuthMode
            If (Test-Path env:MONKEY_ENV_AUTH_MODE){
                $MyParams.AuthMode = $env:MONKEY_ENV_AUTH_MODE
            }
            #Check If subscriptions
            If (Test-Path env:MONKEY_ENV_SUBSCRIPTIONS){
                $MyParams.Subscriptions = $env:MONKEY_ENV_SUBSCRIPTIONS
            }
            #Check If collect
            If (Test-Path env:MONKEY_ENV_COLLECT){
                $collect = @()
                ForEach($element in $env:MONKEY_ENV_COLLECT.Split(',')){
                    $collect+=$element
                }
                If('all' -in $collect){
                    [void]$collect.Clear();
                    $collect+='all'
                }
                If($collect.Count -gt 0){
                    #Remove duplicate before adding data to analysis var
                    $MyParams.Collect = $collect | Sort-Object -Property @{Expression={$_.Trim()}} -Unique
                }
            }
            #Check If exportTo
            If (Test-Path env:MONKEY_ENV_EXPORT_TO){
                $exportTo = @()
                ForEach($element in $env:MONKEY_ENV_EXPORT_TO.Split(',')){
                    $exportTo+=$element
                }
                If($exportTo.Count -gt 0){
                    #Remove duplicate before adding data to exportTo var
                    $MyParams.ExportTo = $exportTo | Sort-Object -Property @{Expression={$_.Trim()}} -Unique
                }
            }
            #Check If writelog
            If (Test-Path env:MONKEY_ENV_WRITELOG){
                try{
                    $MyParams.WriteLog = [System.Convert]::ToBoolean($env:MONKEY_ENV_WRITELOG)
                }
                catch{
                    $MyParams.WriteLog = $false
                }
            }
            #Check If Verbose
            If (Test-Path env:MONKEY_ENV_VERBOSE){
                try{
                    $MyParams.Verbose = [System.Convert]::ToBoolean($env:MONKEY_ENV_VERBOSE)
                }
                catch{
                    $MyParams.Verbose = $false
                }
            }
            #Check If Debug
            If (Test-Path env:MONKEY_ENV_DEBUG){
                try{
                    $MyParams.Debug = [System.Convert]::ToBoolean($env:MONKEY_ENV_DEBUG)
                }
                catch{
                    $MyParams.Debug = $false
                }
            }
            #Calculate threads for nested runspaces
            [int]$nestedMaxThreads = ($MyParams.Threads / 2)
            If($nestedMaxThreads -eq 0){$nestedMaxThreads = 1}
        }
        Catch{
            throw ("[ParameterError] {0}: {1}" -f "Unable to create Monkey365 object",$_.Exception.Message)
        }
    }
    Process{
        #Get config files
        #JSON config
        try{
            $json_path = ("{0}/config/monkey365.config" -f $ScriptPath)
            If (!(Test-Path -Path $json_path)){
                throw ("{0} config does not exists" -f $json_path)
            }
            $internal_config_json = (Get-Content $json_path -Raw) | ConvertFrom-Json
            #DLP config
            $json_path = ("{0}/core/utils/dlp/monkeydlp.json" -f $ScriptPath)
            If (!(Test-Path -Path $json_path)){
                throw ("{0} dlp file does not exists" -f $json_path)
            }
            $internal_dlp_json = (Get-Content $json_path -Raw) | ConvertFrom-Json
            #Get User Properties
            $json_path = ("{0}/core/utils/properties/monkeyuserprop.json" -f $ScriptPath)
            If (!(Test-Path -Path $json_path)){
                throw ("{0} user properties file does not exists" -f $json_path)
            }
            $user_prop_json = (Get-Content $json_path -Raw) | ConvertFrom-Json
            #Get diag settings unsupported resources
            $json_path = ("{0}/core/utils/diagnosticSettings/unsupportedResources.json" -f $ScriptPath)
            If (!(Test-Path -Path $json_path)){
                throw ("{0} diagnostic settings file does not exists" -f $json_path)
            }
            $diag_settings_json = (Get-Content $json_path -Raw) | ConvertFrom-Json
            ############ Get Ruleset info ################
            $ruleSet = $rulesPath = $null;
            #Get ruleset
            If($null -ne $MyParams.ruleset){
                $ruleSet = $MyParams.ruleset
            }
            Else{
                If($null -ne $MyParams.Instance -and $MyParams.Instance.ToLower() -eq "azure"){
                    $ruleSet = $internal_config_json.ruleSettings.azureDefaultRuleset
                }
                ElseIf($null -ne $MyParams.Instance -and $MyParams.Instance.ToLower() -eq "microsoft365"){
                    $ruleSet = $internal_config_json.ruleSettings.m365DefaultRuleset
                }
                Else{
                    #Probably Azure AD
                    $ruleSet = $internal_config_json.ruleSettings.m365DefaultRuleset
                }
            }
            $isRoot = [System.IO.Path]::IsPathRooted($ruleSet)
            If(-NOT $isRoot){
                $ruleSet = ("{0}/{1}" -f $ScriptPath, $ruleSet)
            }
            If (!(Test-Path -Path $ruleSet)){
                Write-Warning ("{0} not found" -f $ruleSet)
                $ruleSet = $null;
            }
            #Get rulespath
            If($null -ne $MyParams.RulesPath){
                $rulesPath = $MyParams.RulesPath
            }
            Else{
                $rulesPath = $internal_config_json.ruleSettings.rules
            }
            $isRoot = [System.IO.Path]::IsPathRooted($rulesPath)
            If(-NOT $isRoot){
                $rulesPath = ("{0}/{1}" -f $ScriptPath, $rulesPath)
            }
            If (!(Test-Path -Path $rulesPath)){
                Write-Warning ("{0} not found" -f $rulesPath)
                $rulesPath = $null;
            }
        }
        Catch{
            throw ("[ConfigFileError] {0}: {1}" -f "Unable to create Monkey365 object",$_.Exception.Message)
        }
        #Get Path libs
        Try{
            #Runspace init
            $runspace_init = @(
                ('{0}/core/runspace_init/Initialize-MonkeyRunspace.ps1' -f $ScriptPath)
            )
            #runspaces modules
            $runspaces_modules = @(
                ('{0}/core/modules/monkeylogger' -f $ScriptPath),
                ('{0}/core/modules/monkeyhttpwebrequest' -f $ScriptPath),
                ('{0}/core/modules/monkeyjob' -f $ScriptPath),
                ('{0}/core/modules/monkeyutils' -f $ScriptPath),
                ('{0}/core/api/m365/SharePointOnline/utils/enum.ps1' -f $ScriptPath)
                ('{0}/core/tasks/Initialize-MonkeyScan.ps1' -f $ScriptPath)
            )
        }
        Catch{
            throw ("[MonkeyLibError] {0}: {1}" -f "Unable to create Monkey365 object",$_.Exception.Message)
        }
    }
    End{
        Try{
            #Create and return a new PsObject
            $tmp_object = [ordered]@{
                Environment = Get-MonkeyEnvironment -Environment $MyParams.Environment;
                cloudEnvironment = $MyParams.Environment;
                runspaces_modules = $runspaces_modules;
                runspace_init = $runspace_init;
                runspace_vars = $null;
                onlineServices = $OnlineServices;
                Localpath =  $ScriptPath;
                InitialPath = (Get-Location -PSProvider FileSystem).ProviderPath;
                auth_tokens = [hashtable]::Synchronized(@{
                    Graph = $null;
                    Intune = $null;
                    ExchangeOnline = $null;
                    ResourceManager = $null;
                    ServiceManagement = $null;
                    SecurityPortal = $null;
                    AzureVault = $null;
                    LogAnalytics = $null;
                    AzureStorage = $null;
                    ComplianceCenter = $null;
                    AzurePortal = $null;
                    Yammer = $null;
                    Forms = $null;
                    Lync= $null;
                    SharePointAdminOnline = $null;
                    SharePointOnline = $null;
                    OneDrive = $null;
                    AADRM = $null;
                    MSGraph = $null;
                    Teams = $null;
                    PowerBI = $null;
                    M365Admin = $null;
                    MSPIM = $null;
                });
                userPrincipalName = $null;
                userId = $null;
                orgRegions = $null;
                Tenant = $null;
                tenantOrigin = $null;
                Collectors = $null;
                Licensing = $null;
                LogPath = $null;
                loggers = $null;
                MonkeyLogQueue = [System.Collections.Concurrent.BlockingCollection[System.Management.Automation.InformationRecord]]::new();
                msal_public_applications = $null;
                msal_confidential_applications = $null;
                msalapplication = $null;
                application_args = $null;
                msal_application_args = $null;
                msalAuthArgs = $null;
                forceMSALDesktop = If($null -ne $MyParams.ForceMSALDesktop){$MyParams.ForceMSALDesktop}Else{$false};
                isConfidentialApp = $null;
                isSharePointAdministrator = $null;
                spoSites = $null;
                InformationAction= $InformationAction;
                VerboseOptions = $VerboseOptions;
                AuthType = $null;
                WriteLog = $MyParams.WriteLog;
                userAgent = $UserAgent;
                userProperties = $user_prop_json;
                subscriptions = $null;
                current_subscription = $null;
                aadPermissions = $null;
                azPermissions = $null;
                canRequestMFAForUsers = $null;
                canRequestUsersFromMsGraph = $null;
                canRequestGroupsFromMsGraph = $null;
                all_resources = $null;
                ResourceGroups = $null;
                internal_config = $internal_config_json;
                dlp = $internal_dlp_json;
                executionInfo = $null;
                startDate = $null;
                monkey_runspacePool = $null;
                threads= $MyParams.Threads;
                SaveProject= $MyParams.SaveProject;
                Instance= $MyParams.Instance;
                IncludeEntraID= $MyParams.IncludeEntraID;
                verbose= $MyParams.Verbose;
                debug= $MyParams.Debug;
                initParams= $MyParams;
                clientApplicationId= $MyParams.ClientId;
                TenantId = $MyParams.TenantId;
                isValidTenantGuid = $false;
                exportTo= $MyParams.ExportTo;
                LocalizedDataParams = $LocalizedDataParams;
                BatchSleep = $internal_config_json.performance.BatchSleep;
                BatchSize = $internal_config_json.performance.BatchSize;
                MaxQueue = $internal_config_json.performance.nestedRunspaces.MaxQueue;
                nestedRunspaces = @{
                    BatchSleep = ($internal_config_json.performance.BatchSleep * 2);
                    BatchSize = ($internal_config_json.performance.BatchSize * 2);
                    MaxQueue = $internal_config_json.performance.nestedRunspaces.MaxQueue;
                }
                nestedRunspaceMaxThreads = $nestedMaxThreads;
                PowerBIBackendUri = $null;
                SecCompBackendUri = $null;
                me = $null;
                SystemInfo = $SystemInfo;
                diag_settings_unsupported_resources = $diag_settings_json;
                OutDir = $MyParams.OutDir;
                excludeCollectors = $MyParams.ExcludeCollector;
                excludedResources = $MyParams.ExcludedResources;
                ruleset = $ruleSet;
                rulesPath = $rulesPath;
                Compress = $MyParams.Compress;
                useMSGraphForAAD = [System.Convert]::ToBoolean($internal_config_json.entraId.useMsGraph);
                useOldAADAPIforUsers = [System.Convert]::ToBoolean($internal_config_json.entraId.getUsersWithAADInternalAPI)
            }
            #Create new object
            $MonkeyObj = New-Object -TypeName PSCustomObject -Property $tmp_object
            #Set new internal variable
            New-Variable O365Object -Value $MonkeyObj -Scope Script -Force

        }
        Catch{
            throw ("[CustomObjectError] {0}: {1}" -f "Unable to create Monkey365 object",$_.Exception.Message)
        }
    }
}