Framework/Core/SVT/SVTResourceResolver.ps1

Set-StrictMode -Version Latest

class SVTResourceResolver: AzSKRoot
{
    [string[]] $ResourceNames = @();
    [string] $ResourceType = "";
    [ResourceTypeName] $ResourceTypeName = [ResourceTypeName]::All;
    [Hashtable] $Tag = $null;
    [string] $TagName = "";
    [string[]] $TagValue = "";
    hidden [string[]] $ResourceGroups = @();
    [ResourceTypeName] $ExcludeResourceTypeName = [ResourceTypeName]::All;
    [string[]] $ExcludeResourceNames=@();
    [SVTResource[]] $ExcludedResources=@();
    [string] $ExcludeResourceWarningMessage=[string]::Empty
    [string[]] $ExcludeResourceGroupNames=@();
    [string[]] $ExcludedResourceGroupNames=@();
    [string] $ExcludeResourceGroupWarningMessage=[string]::Empty
    [SVTResource[]] $SVTResources = @();
    [int] $SVTResourcesFoundCount=0;
    
    [string] $ResourcePath;
    [string] $organizationName
    hidden [string[]] $ProjectNames = @();
    hidden [string[]] $BuildNames = @();
    hidden [string[]] $ReleaseNames = @();
    hidden [string[]] $AgentPools = @();
    SVTResourceResolver([string]$organizationName,$ProjectNames,$BuildNames,$ReleaseNames,$AgentPools,$ScanAllArtifacts,$PATToken,$ResourceTypeName): Base($organizationName,$PATToken)
    {
        $this.organizationName = $organizationName
        $this.ResourceTypeName = $ResourceTypeName
        

        if(-not [string]::IsNullOrEmpty($ProjectNames))
        {
            $this.ProjectNames += $this.ConvertToStringArray($ProjectNames);

            if ($this.ProjectNames.Count -eq 0)
            {
                throw [SuppressedException] "The parameter 'ProjectNames' does not contain any string."
            }
        }    

        if(-not [string]::IsNullOrEmpty($BuildNames))
        {
            $this.BuildNames += $this.ConvertToStringArray($BuildNames);
            if ($this.BuildNames.Count -eq 0)
            {
                throw [SuppressedException] "The parameter 'BuildNames' does not contain any string."
            }
        }

        if(-not [string]::IsNullOrEmpty($ReleaseNames))
        {
            $this.ReleaseNames += $this.ConvertToStringArray($ReleaseNames);
            if ($this.ReleaseNames.Count -eq 0)
            {
                throw [SuppressedException] "The parameter 'ReleaseNames' does not contain any string."
            }
        }

        if(-not [string]::IsNullOrEmpty($AgentPools))
        {
            $this.AgentPools += $this.ConvertToStringArray($AgentPools);
            if ($this.AgentPools.Count -eq 0)
            {
                throw [SuppressedException] "The parameter 'AgentPools' does not contain any string."
            }
        }

        if($ScanAllArtifacts)
        {
            $this.ProjectNames = "*"
            $this.BuildNames = "*"
            $this.ReleaseNames = "*"
            $this.AgentPools = "*"
        }        
    }

    [void] LoadAzureResources()
    {
        
        #Call APIS for Organization,User/Builds/Releases/ServiceConnections
        if($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::Organization)
        {
            #Select Org/User by default...
            $svtResource = [SVTResource]::new();
            $svtResource.ResourceName = $this.organizationName;
            $svtResource.ResourceType = "AzureDevOps.Organization";
            $svtResource.ResourceId = "Organization/$($this.organizationName)/"
            $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                            Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                            Select-Object -First 1)
            $this.SVTResources +=$svtResource
        }
        

        if($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::User)
        {
            $svtResource = [SVTResource]::new();
            $svtResource.ResourceName = $this.organizationName;
            $svtResource.ResourceType = "AzureDevOps.User";
            $svtResource.ResourceId = "Organization/$($this.organizationName)/User"
            $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                            Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                            Select-Object -First 1)
            $this.SVTResources +=$svtResource
        }

        #Get project resources
        if($this.ProjectNames.Count -gt 0)
        {
            $this.PublishCustomMessage("Querying api for resources to be scanned. This may take a while...");

            $this.PublishCustomMessage("Getting project configurations...");

            $apiURL = "https://dev.azure.com/{0}/_apis/projects?api-version=4.1" -f $($this.SubscriptionContext.SubscriptionName);
            $responseObj = [WebRequestHelper]::InvokeGetWebRequest($apiURL) ;

            $responseObj  | Where-Object {  (($this.ProjectNames -contains $_.name) -or ($this.ProjectNames -eq "*"))  } | ForEach-Object {
                $projectName = $_.name

            if($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::Project)
            {
                $svtResource = [SVTResource]::new();
                $svtResource.ResourceName = $_.name;
                $svtResource.ResourceGroupName = $this.organizationName
                $svtResource.ResourceType = "AzureDevOps.Project";
                $svtResource.ResourceId = $_.url
                $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                Select-Object -First 1)
                
                $this.SVTResources +=$svtResource
            }


            if($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::ServiceConnection)
            {
                if($this.ProjectNames -ne "*")
                {
                    $this.PublishCustomMessage("Getting service endpoint configurations...");
                }
                
                $serviceEndpointURL = "https://dev.azure.com/{0}/{1}/_apis/serviceendpoint/endpoints?api-version=4.1-preview.1" -f $($this.organizationName),$($projectName);
                $serviceEndpointObj = [WebRequestHelper]::InvokeGetWebRequest($serviceEndpointURL)
                
                if(([Helpers]::CheckMember($serviceEndpointObj,"count") -and $serviceEndpointObj[0].count -gt 0) -or  (($serviceEndpointObj | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($serviceEndpointObj[0],"name")))
                {
                    # Currently get only Azure Connections as all controls are applicable for same
                    #TODO: temp added git in the where
                    $azureConnections = $serviceEndpointObj | Where-Object { $_.type -eq "azurerm" -or $_.type -eq "azure" -or $_.type -eq "git" -or $_.type -eq "github"} #-or $_.type -eq "git" -or $_.type -eq "git"

                    $azureConnections | ForEach-Object {
                        $connectionObject = $_
                        $svtResource = [SVTResource]::new();
                        $svtResource.ResourceName = $connectionObject.Name;
                        $svtResource.ResourceGroupName = $projectName;
                        $svtResource.ResourceType = "AzureDevOps.ServiceConnection";
                        $svtResource.ResourceId = "Organization/$($this.organizationName)/Project/$projectName/$($connectionObject.Name)"
                        $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                        Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                        Select-Object -First 1)
                        $svtResource.ResourceDetails = $connectionObject
                        $this.SVTResources +=$svtResource
                    }
                }
            }

                if($this.BuildNames.Count -gt 0  -and ($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::Build))
                {
                    if($this.ProjectNames -ne "*")
                    {
                        $this.PublishCustomMessage("Getting build configurations...");
                    }

                    if($this.BuildNames -eq "*")
                    {
                        $buildDefnURL = "https://dev.azure.com/{0}/{1}/_apis/build/definitions?api-version=4.1" -f $($this.SubscriptionContext.SubscriptionName), $_.name;
                        $buildDefnsObj = [WebRequestHelper]::InvokeGetWebRequest($buildDefnURL) 
                        if(([Helpers]::CheckMember($buildDefnsObj,"count") -and $buildDefnsObj[0].count -gt 0) -or  (($buildDefnsObj | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($buildDefnsObj[0],"name")))
                        {
                            $buildDefnsObj  | ForEach-Object {
                                $svtResource = [SVTResource]::new();
                                $svtResource.ResourceName = $_.name;
                                $svtResource.ResourceGroupName =$_.project.name;
                                $svtResource.ResourceType = "AzureDevOps.Build";
                                $svtResource.ResourceId = $_.url
                                $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                                Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                                Select-Object -First 1)
                                $svtResource.ResourceDetails = $_
                                $this.SVTResources +=$svtResource
                            }
                        }
                    }
                    else
                    {
                        $this.BuildNames | ForEach-Object {
                            $buildName = $_
                            $buildDefnURL = "https://{0}.visualstudio.com/{1}/_apis/build/definitions?name={2}&api-version=5.1-preview.7" -f $($this.SubscriptionContext.SubscriptionName),$projectName, $buildName;
                            $buildDefnsObj = [WebRequestHelper]::InvokeGetWebRequest($buildDefnURL) 
                            if(([Helpers]::CheckMember($buildDefnsObj,"count") -and $buildDefnsObj[0].count -gt 0) -or  (($buildDefnsObj | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($buildDefnsObj[0],"name")))
                            {
                                $buildDefnsObj  | ForEach-Object {
                                    $svtResource = [SVTResource]::new();
                                    $svtResource.ResourceName = $_.name;
                                    $svtResource.ResourceGroupName =$_.project.name;
                                    $svtResource.ResourceType = "AzureDevOps.Build";
                                    $svtResource.ResourceId = $_.url
                                    $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                                    Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                                    Select-Object -First 1)
                                    $this.SVTResources +=$svtResource
                                }
                            }
                        }
                    }
                           
                }

                if($this.ReleaseNames.Count -gt 0 -and ($this.ResourceTypeName -eq [ResourceTypeName]::All -or $this.ResourceTypeName -eq [ResourceTypeName]::Release))
                {
                    if($this.ProjectNames -ne "*")
                    {
                        $this.PublishCustomMessage("Getting release configurations...");
                    }
                    if($this.ReleaseNames -eq "*")
                    {
                        $releaseDefnURL = "https://vsrm.dev.azure.com/{0}/{1}/_apis/release/definitions?api-version=4.1-preview.3" -f $($this.SubscriptionContext.SubscriptionName), $projectName;
                        $releaseDefnsObj = [WebRequestHelper]::InvokeGetWebRequest($releaseDefnURL);
                        if(([Helpers]::CheckMember($releaseDefnsObj,"count") -and $releaseDefnsObj[0].count -gt 0) -or  (($releaseDefnsObj | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($releaseDefnsObj[0],"name")))
                        {
                            $releaseDefnsObj  | ForEach-Object {
                                $svtResource = [SVTResource]::new();
                                $svtResource.ResourceName = $_.name;
                                $svtResource.ResourceGroupName =$projectName;
                                $svtResource.ResourceType = "AzureDevOps.Release";
                                $svtResource.ResourceId = $_.url
                                $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                                Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                                Select-Object -First 1)
                                $this.SVTResources +=$svtResource
                            }
                        }
                    }
                    else
                    {
                        try {
                            #TODO: temporary added
                           # $releaseDefnURL2 = "https://vsrm.dev.azure.com/{0}/{1}/_apis/release/definitions?api-version=4.1-preview.3" -f $($this.SubscriptionContext.SubscriptionName), $projectName;
                           # $releaseDefnsObj2 = [WebRequestHelper]::InvokeGetWebRequest($releaseDefnURL);

                        $this.ReleaseNames | ForEach-Object {
                            $resleaseName = $_
                            $releaseDefnURL = "https://{0}.vsrm.visualstudio.com/_apis/Contribution/HierarchyQuery/project/{1}?api-version=5.0-preview.1" -f $($this.SubscriptionContext.SubscriptionName), $projectName;
                            $inputbody = "{
                                'contributionIds': [
                                    'ms.vss-releaseManagement-web.search-definitions-data-provider'
                                ],
                                'dataProviderContext': {
                                    'properties': {
                                        'searchText': '$resleaseName',
                                        'sourcePage': {
                                            'routeValues': {
                                                'project': '$projectName'
                                            }
                                        }
                                    }
                                }
                            }"
 | ConvertFrom-Json
                            
                            $releaseDefnsObj = [WebRequestHelper]::InvokePostWebRequest($releaseDefnURL,$inputbody);
                            if(([Helpers]::CheckMember($releaseDefnsObj,"dataProviders") -and $releaseDefnsObj.dataProviders."ms.vss-releaseManagement-web.search-definitions-data-provider") -and [Helpers]::CheckMember($releaseDefnsObj.dataProviders."ms.vss-releaseManagement-web.search-definitions-data-provider","releaseDefinitions") )
                            {
                                $releaseDefnsObj.dataProviders."ms.vss-releaseManagement-web.search-definitions-data-provider".releaseDefinitions  | ForEach-Object {
                                    $svtResource = [SVTResource]::new();
                                    $svtResource.ResourceName = $_.name;
                                    $svtResource.ResourceGroupName =$projectName;
                                    $svtResource.ResourceType = "AzureDevOps.Release";
                                    $svtResource.ResourceId = $_.url
                                    $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                                    Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                                    Select-Object -First 1)
                                    $this.SVTResources +=$svtResource
                                }
                        }

                        }
                    }
                    catch {
                        Write-Error $_.Exception.Message;
                        Write-Error 'Insufficient Privileges. You do not have the level of access necessary to perform the scan.'
                    }
                    }
                }
                
                if($this.AgentPools.Count -gt 0)
                {
                    if($this.AgentPools -ne "*")
                    {
                        $this.PublishCustomMessage("Getting agent pools configurations...");
                    }
                    if($this.AgentPools -eq "*")
                    {
                        $agentPoolsDefnURL = "https://{0}.visualstudio.com/{1}/_settings/agentqueues?__rt=fps&__ver=2" -f $($this.SubscriptionContext.SubscriptionName),$projectName;
                        #TODO: comment above line added below line for testing. previously above commented line was in use, below line is also working fine.
                        #$agentPoolsDefnURL = "https://dev.azure.com/{0}/{1}/_settings/agentqueues?__rt=fps&__ver=2 " -f $($this.SubscriptionContext.SubscriptionName),$projectName;
                        
                        try {
                      
                                $agentPoolsDefnsObj = [WebRequestHelper]::InvokeGetWebRequest($agentPoolsDefnURL);
                           
                        
                             if(([Helpers]::CheckMember($agentPoolsDefnsObj,"fps.dataProviders.data") ) -and  (($agentPoolsDefnsObj.fps.dataProviders.data."ms.vss-build-web.agent-queues-data-provider") -and $agentPoolsDefnsObj.fps.dataProviders.data."ms.vss-build-web.agent-queues-data-provider".taskAgentQueues))
                            {
                                $agentPoolsDefnsObj.fps.dataProviders.data."ms.vss-build-web.agent-queues-data-provider".taskAgentQueues  | ForEach-Object {
                                    $svtResource = [SVTResource]::new();
                                    $svtResource.ResourceName = $_.name;
                                    $svtResource.ResourceGroupName =$projectName;
                                    $svtResource.ResourceType = "AzureDevOps.AgentPool";
                                    $svtResource.ResourceId = "https://{0}.visualstudio.com/_apis/securityroles/scopes/distributedtask.agentqueuerole/roleassignments/resources/{1}_{2}" -f $($this.SubscriptionContext.SubscriptionName),$($_.projectId), $_.id
                                    $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKDevOpsResourceMapping |
                                                                    Where-Object { $_.ResourceType -eq $svtResource.ResourceType } |
                                                                    Select-Object -First 1)
                                    $this.SVTResources +=$svtResource
                                }
                            }
                        }
                        catch {
                           #Write-Warning "Insufficient Privileges. You do not have the level of access necessary to perform the scan.";
                           #Write-Error -Exception ([System.UnauthorizedAccessException]::new("Insufficient Privileges. You do not have the level of access necessary to perform the scan."));
                           Write-Error $_.Exception.Message;
                        }              
                    }
                }

            }

         
        }

        
        $this.SVTResourcesFoundCount = $this.SVTResources.Count
    
    }
}