Framework/BugLog/AutoBugLog.ps1
Set-StrictMode -Version Latest class AutoBugLog { hidden static [AutoBugLog] $AutoBugInstance; hidden [ControlStateExtension] $ControlStateExt; hidden [string] $OrganizationName; hidden [InvocationInfo] $InvocationContext; hidden [PSObject] $ControlSettings; hidden [bool] $IsBugLogCustomFlow = $false; hidden [bool] $ShowBugsInS360 = $false; hidden [string] $BugLogParameterValue; hidden [string] $BugDescriptionField; hidden [string] $ServiceIdPassedInCMD; hidden [bool] $UseAzureStorageAccount = $false; hidden [BugLogHelper] $BugLogHelperObj; hidden [string] $ScanSource; hidden [bool] $LogBugsForUnmappedResource = $true; AutoBugLog([string] $orgName, [InvocationInfo] $invocationContext, [ControlStateExtension] $controlStateExt, $bugLogParameterValue) { $this.OrganizationName = $orgName; $this.InvocationContext = $invocationContext; $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); $this.ControlStateExt = $controlStateExt $this.BugLogParameterValue = $bugLogParameterValue #flag to check if pluggable bug logging interface (service tree) if ([Helpers]::CheckMember($this.ControlSettings.BugLogging, "BugAssigneeAndPathCustomFlow", $null)) { $this.IsBugLogCustomFlow = $this.ControlSettings.BugLogging.BugAssigneeAndPathCustomFlow; $this.ServiceIdPassedInCMD = $InvocationContext.BoundParameters["ServiceId"]; } $this.ScanSource = [AzSKSettings]::GetInstance().GetScanSource(); #If UseAzureStorageAccount is true then initialize the BugLogHelperObj singleton class object. if ([Helpers]::CheckMember($this.ControlSettings.BugLogging, "UseAzureStorageAccount")) { $this.UseAzureStorageAccount = $this.ControlSettings.BugLogging.UseAzureStorageAccount; if ($this.UseAzureStorageAccount) { $this.BugLogHelperObj = [BugLogHelper]::BugLogHelperInstance if (!$this.BugLogHelperObj) { $this.BugLogHelperObj = [BugLogHelper]::GetInstance($this.OrganizationName); } } } # Replace the field reference name for bug description if it is customized if ($this.InvocationContext.BoundParameters['BugDescriptionField']) { $this.BugDescriptionField = "/fields/" + $this.InvocationContext.BoundParameters['BugDescriptionField'] } elseif ([Helpers]::CheckMember($this.controlsettings.BugLogging, 'BugDescriptionField') -and -not ([string]::IsNullOrEmpty($this.ControlSettings.BugLogging.BugDescriptionField))) { $this.BugDescriptionField = "/fields/" + $this.ControlSettings.BugLogging.BugDescriptionField } #Check whether LogBugsForUnmappedResource variable exist in policy fiile. $LogBugsForUnmappedResourceVarExistInPolicy = $this.ControlSettings.BugLogging.PSobject.Properties | where-object {$_.Name -eq "LogBugsForUnmappedResource"} #If LogBugForUnmappedResource exist in the policy file then get it's value. if ($LogBugsForUnmappedResourceVarExistInPolicy) { $this.LogBugsForUnmappedResource = $LogBugsForUnmappedResourceVarExistInPolicy.Value; } } #Return AutoBugLog instance hidden static [AutoBugLog] GetInstance([string] $orgName, [InvocationInfo] $invocationContext, [ControlStateExtension] $ControlStateExt, [string] $bugLogParameterValue) { [AutoBugLog]::AutoBugInstance = [AutoBugLog]::new($orgName, $invocationContext, $ControlStateExt, $bugLogParameterValue) return [AutoBugLog]::AutoBugInstance } static [string] ComputeHashX([string] $dataToHash) { return [Helpers]::ComputeHashShort($dataToHash, [Constants]::AutoBugLogTagLen) } #main function where bug logging takes place hidden [void] LogBugInADO([SVTEventContext[]] $ControlResults) { #check if user has permissions to log bug for the current resource if ($this.CheckPermsForBugLog($ControlResults[0])) { #retrieve the project name for the current resource $ProjectName = $this.GetProjectForBugLog($ControlResults[0]) #check if the area and iteration path are valid if ([BugLogPathManager]::CheckIfPathIsValid($this.OrganizationName, $ProjectName, $this.InvocationContext, $this.ControlSettings.BugLogging.BugLogAreaPath, $this.ControlSettings.BugLogging.BugLogIterationPath, $this.IsBugLogCustomFlow)) { #Obtain the assignee for the current resource, will be same for all the control failures for this particular resource $metaProviderObj = [BugMetaInfoProvider]::new(); $AssignedTo = $metaProviderObj.GetAssignee($ControlResults[0], $this.ControlSettings.BugLogging, $this.IsBugLogCustomFlow, $this.ServiceIdPassedInCMD); $serviceId = $metaProviderObj.ServiceId #Log bug only if LogBugForUnmappedResource is enabled (default value is true) or resource is mapped to serviceid #Restrict bug logging, if resource is not mapped to serviceid and LogBugForUnmappedResource is not enabled. if($this.LogBugsForUnmappedResource -or $serviceId) { #Set ShowBugsInS360 if customebuglog is enabled and sericeid not null and ShowBugsInS360 enabled in policy if ($this.IsBugLogCustomFlow -and (-not [string]::IsNullOrEmpty($serviceId)) -and ([Helpers]::CheckMember($this.ControlSettings.BugLogging, "ShowBugsInS360") -and $this.ControlSettings.BugLogging.ShowBugsInS360) ) { $this.ShowBugsInS360 = $true; } else { $this.ShowBugsInS360 = $false; } #this falg is added to restrict 'Determining bug logging' message should print only once $printLogBugMsg = $true; #Loop through all the control results for the current resource $ControlResults | ForEach-Object { $control = $_; #filter controls on basis of whether they are baseline or not depending on the value given in autobuglog flag $LogControlFlag = $false if ($this.BugLogParameterValue -eq [BugLogForControls]::All) { $LogControlFlag = $true } elseif ($this.BugLogParameterValue -eq [BugLogForControls]::BaselineControls) { $LogControlFlag = $this.CheckBaselineControl($control.ControlItem.ControlID) } elseif ($this.BugLogParameterValue -eq [BugLogForControls]::PreviewBaselineControls) { $LogControlFlag = $this.CheckPreviewBaselineControl($control.ControlItem.ControlID) } elseif ($this.BugLogParameterValue -eq [BugLogForControls]::Custom) { $LogControlFlag = $this.CheckControlInCustomControlList($control.ControlItem.ControlID) } if ($LogControlFlag -and ($control.ControlResults[0].VerificationResult -eq "Failed" -or $control.ControlResults[0].VerificationResult -eq "Verify") ) { #compute hash of control Id and resource Id $hash = $this.GetHashedTag($control.ControlItem.Id, $control.ResourceContext.ResourceId) #check if a bug with the computed hash exists #Removed ProjectName param and direcly added [BugLogPathManager]::BugLoggingProject, previously holding in variable and passing in method $workItem = $this.GetWorkItemByHash($hash, [BugLogPathManager]::BugLoggingProject) if ($workItem[0].results.count -gt 0) { #a work item with the hash exists, find if it's state and reactivate if resolved bug $this.ManageActiveAndResolvedBugs($ProjectName, $control, $workItem, $AssignedTo, $serviceId) } else { if ($printLogBugMsg) { Write-Host "Determining bugs to log..." -ForegroundColor Cyan } $printLogBugMsg = $false; #filling the bug template $Title = "[ADOScanner] Control failure - {0} for resource {1} {2}" $Description = "Control failure - {3} for resource {4} {5} </br></br> <b>Control Description: </b> {0} </br></br> <b> Control Result: </b> {6} </br> </br> <b> Rationale:</b> {1} </br></br> <b> Recommendation:</b> {2}" $Title = $Title -f $control.ControlItem.ControlID, $control.ResourceContext.ResourceTypeName, $control.ResourceContext.ResourceName if ($control.ResourceContext.ResourceTypeName -ne "Organization" -and $control.ResourceContext.ResourceTypeName -ne "Project") { $Title += " in project " + $control.ResourceContext.ResourceGroupName; } $Description = $Description -f $control.ControlItem.Description, $control.ControlItem.Rationale, $control.ControlItem.Recommendation, $control.ControlItem.ControlID, $control.ResourceContext.ResourceTypeName, $control.ResourceContext.ResourceName, $control.ControlResults[0].VerificationResult $Description += "</br></br> <b> Resource Link: </b> <a href='$($control.ResourceContext.ResourceDetails.ResourceLink)' target='_blank'>$($control.ResourceContext.ResourceName)</a>" $RunStepsForControl = " </br></br> <b>Control Scan Command:</b> Run: {0}" $Description += ($RunStepsForControl -f $this.GetControlReproStep($control)); #check and append any detailed log and state data for the control failure $log = $this.GetDetailedLogForControl($control); if ($log) { $Description += "<hr></br><b>Some other details for your reference</b> </br><hr> {7} " $Description = $Description.Replace("{7}", $log) } $Description = $Description.Replace("`"", "'") $Severity = $this.GetSeverity($control.ControlItem.ControlSeverity) #function to attempt bug logging $this.AddWorkItem($Title, $Description, $AssignedTo, $Severity, $ProjectName, $control, $hash, $serviceId); } } } } else { Write-Host "Bug logging is disabled for resources that are not mapped to any service." -ForegroundColor Yellow } } } } #function to get the security command for repro of this bug hidden [string] GetControlReproStep([SVTEventContext []] $ControlResult) { $StepsForRepro = "" if ($ControlResult.FeatureName -eq "Organization") { $StepsForRepro = "Get-AzSKADOSecurityStatus -OrganizationName '{0}' -ControlIds '{1}'" $StepsForRepro = $StepsForRepro -f $ControlResult.ResourceContext.ResourceName, $ControlResult.ControlItem.ControlID; } elseif ($ControlResult.ResourceContext.ResourceTypeName -eq "Project") { $StepsForRepro = "Get-AzSKADOSecurityStatus -OrganizationName '{0}' -ProjectNames '{1}' -ControlIds '{2}'" $StepsForRepro = $StepsForRepro -f $ControlResult.ResourceContext.ResourceGroupName, $ControlResult.ResourceContext.ResourceName, $ControlResult.ControlItem.ControlID; } else { $StepsForRepro = "Get-AzSKADOSecurityStatus -OrganizationName '{0}' -ProjectNames '{1}' -{2}Names '{3}' -ControlIds '{4}'" $StepsForRepro = $StepsForRepro -f $this.OrganizationName, $ControlResult.ResourceContext.ResourceGroupName, $ControlResult.FeatureName, $ControlResult.ResourceContext.ResourceName, $ControlResult.ControlItem.ControlID; } return $StepsForRepro } #function to retrieve project name according to the resource hidden [string] GetProjectForBugLog([SVTEventContext[]] $ControlResult) { $ProjectName = "" #if resource is the organization, call control state extension to retreive attestation host project if ($ControlResult.FeatureName -eq "Organization") { $ProjectName = $this.ControlStateExt.GetProject() } #for all the other resource types, retrieve the project name from the control itself elseif ($ControlResult.ResourceContext.ResourceTypeName -eq "Project") { $ProjectName = $ControlResult.ResourceContext.ResourceName } else { $ProjectName = $ControlResult.ResourceContext.ResourceGroupName } return $ProjectName } #function to check if the bug can be logged for the current resource type hidden [bool] CheckPermsForBugLog([SVTEventContext[]] $ControlResult) { if($ControlResult.FeatureName -eq 'Build' -or $ControlResult.FeatureName -eq 'Release' -or $ControlResult.FeatureName -eq 'ServiceConnection' -or $ControlResult.FeatureName -eq 'AgentPool' -or $ControlResult.FeatureName -eq 'VariableGroup') { return $true; } elseif($ControlResult.FeatureName -eq 'Organization') { #check if any host project can be retrieved, if not use getHostProject to return the correct behaviour output if (!($this.GetHostProject($ControlResult))) { return $false } } elseif($ControlResult.FeatureName -eq 'Project') { #check if user is member of PA/PCA if (!$this.ControlStateExt.GetControlStatePermission($ControlResult.FeatureName, $ControlResult.ResourceContext.ResourceName)) { Write-Host "`nAuto bug logging denied due to insufficient permissions. Make sure you are a project administrator. " -ForegroundColor Red return $false } } elseif($ControlResult.FeatureName -eq 'User') { #TODO: User controls dont have a project associated with them, can be rectified in future versions Write-Host "`nAuto bug logging for user control failures is currently not supported." -ForegroundColor Yellow return $false } return $true } #function to retrive the attestation host project for organization level control failures hidden [string] GetHostProject([SVTEventContext[]] $ControlResult) { $Project = $null #check if attestationhost project has been specified along with the command if ($this.InvocationContext.BoundParameters["AttestationHostProjectName"]) { #check if the user has permission to log bug at org level if ($this.ControlStateExt.GetControlStatePermission("Organization", "")) { #user is PCA member, set the host project and return the project name $this.ControlStateExt.SetProjectInExtForOrg() $Project = $this.ControlStateExt.GetProject() return $Project } #user is not a member of PCA, invalidate the bug log else { Write-Host "Error: Could not configure host project to log bugs for organization-specific control failures.`nThis may be because you may not have correct privilege (requires 'Project Collection Administrator')." -ForegroundColor Red return $null } } else { #check if the user is a member of PCA after validating that the host project name was not provided if (!$this.ControlStateExt.GetControlStatePermission("Organization", "") ) { Write-Host "Error: Auto bug logging denied.`nThis may be because you are attempting to log bugs for areas you do not have RBAC permission to." -ForegroundColor Red return $null } else { $Project = $this.ControlStateExt.GetProject() #user is a PCA member but the project has not been set for org control failures if (!$Project) { Write-Host "`nNo project defined to log bugs for organization-specific controls." -ForegroundColor Red Write-Host "Use the '-AttestationHostProjectName' parameter with this command to configure the project that will host bug logging details for organization level controls.`nRun 'Get-Help -Name Get-AzSKADOSecurityStatus -Full' for more info." -ForegroundColor Yellow return $null } } } return $Project } #function to check any detailed log and state data for the control failure hidden [string] GetDetailedLogForControl([SVTEventContext[]] $ControlResult) { $log = "" #retrieve the message data for control result $Messages = $ControlResult.ControlResults[0].Messages $Messages | ForEach-Object { if ($_.Message) { $log += "<b>$($_.Message)</b> </br></br>" } #check for state data if ($_.DataObject) { $log += "<hr>" #beautify state data for bug template $stateData = [Helpers]::ConvertObjectToString($_, $false) $stateData = $stateData.Replace("`"", "'") $stateData = $stateData.Replace("@{", "@{</br>") $stateData = $stateData.Replace("@(", "@(</br>") $stateData = $stateData.Replace(";", ";</br>") $stateData = $stateData.Replace("},", "</br>},</br>") $stateData = $stateData.Replace(");", "</br>});</br>") $log += "$($stateData) </br></br>" } } #sanitizing input for JSON $log = $log.Replace("\", "\\") return $log } #function to retrieve the person to whom the bug will be assigned hidden [string] GetAssignee([SVTEventContext[]] $ControlResult) { $metaProviderObj = [BugMetaInfoProvider]::new(); return $metaProviderObj.GetAssignee($ControlResult, $this.ControlSettings.BugLogging); } #function to map severity of the control item hidden [string] GetSeverity([string] $ControlSeverity) { $Severity = "" switch -regex ($ControlSeverity) { 'Critical' { $Severity = "1 - Critical" } 'High' { $Severity = "2 - High" } 'Important' { $Severity = "2 - High" } 'Medium' { $Severity = "3 - Medium" } 'Moderate' { $Severity = "3 - Medium" } 'Low' { $Severity = "4 - Low" } } return $Severity } hidden [string] GetSecuritySeverity([string] $ControlSeverity) { $Severity = "" switch -regex ($ControlSeverity) { 'Critical' { $Severity = "1 - Critical" } 'High' { $Severity = "2 - Important" } 'Important' { $Severity = "2 - Important" } 'Moderate' { $Severity = "3 - Moderate" } 'Medium' { $Severity = "3 - Moderate" } 'Low' { $Severity = "4 - Low" } } return $Severity } #function to find active bugs and reactivate resolved bugs hidden [void] ManageActiveAndResolvedBugs([string]$ProjectName, [SVTEventContext[]] $control, [object] $workItem, [string] $AssignedTo, [string] $serviceId) { #If using azure storage then calling documented api as we have ado id, so response will be different, so added if else condition $state = ""; $id = ""; #serviceid return in the bug api response to match with current scanned resource service id. $serviceIdInLoggedBug = ""; if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { $state = $workItem[0].results.fields."System.State" $id = $workItem[0].results.id #Check ShowBugsInS360 and Security.ServiceHierarchyId property exist in object. if ($this.ShowBugsInS360 -and ($workItem[0].results.fields.PSobject.Properties.name -match "Security.ServiceHierarchyId")) { $serviceIdInLoggedBug = $workItem[0].results.fields."Security.ServiceHierarchyId" } } else { $state = ($workItem[0].results.values[0].fields | where { $_.name -eq "State" }).value $id = ($workItem[0].results.values[0].fields | where { $_.name -eq "ID" }).value #Check ShowBugsInS360 and Security.ServiceHierarchyId property exist in object. if ($this.ShowBugsInS360 -and ($workItem[0].results.values[0].fields.PSobject.Properties.name -match "Security.ServiceHierarchyId")) { $serviceIdInLoggedBug = ($workItem[0].results.values[0].fields | where { $_.name -eq "Security.ServiceHierarchyId" }).value } } #bug url that redirects user to bug logged in ADO, this is not available via the API response and thus has to be created via the ID of bug $bugUrl = "https://{0}.visualstudio.com/{1}/_workitems/edit/{2}" -f $this.OrganizationName, $ProjectName , $id #TODO : whether the bug is active or resolved, we have to ensure the state of the bug remains active after this function #if a PCA assigns this to a non PCA, the control can never be fixed for org/project controls. to tackle this, reassign it to the original owner PCA #do this for both active and resolved bugs, as we need it to be assigned to the actual person who can fix this control #for other control results, we need not changed the assignee <# $url = "https://dev.azure.com/{0}/{1}/_apis/wit/workitems/{2}?api-version=5.1" -f $($this.OrganizationName), $ProjectName, $id $BugTemplate = [ConfigurationManager]::LoadServerConfigFile("TemplateForResolvedBug.json") $BugTemplate = $BugTemplate | ConvertTo-Json -Depth 10 $BugTemplate=$BugTemplate.Replace("{0}",$AssignedTo) $header = [WebRequestHelper]::GetAuthHeaderFromUriPatch($url) try { #TODO: shift all this as a patch request in webrequesthelper class and manage accented characters as well $responseObj = Invoke-RestMethod -Uri $url -Method Patch -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $BugTemplate } catch { #if the user to whom the bug has been assigneed is not a member of org any more if ($_.ErrorDetails.Message -like '*System.AssignedTo*') { $body = $BugTemplate | ConvertFrom-Json #let it remain assigned $body[2].value = ""; $body = $body | ConvertTo-Json try { $responseObj = Invoke-RestMethod -Uri $url -Method Patch -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $body $bugUrl = "https://{0}.visualstudio.com/_workitems/edit/{1}" -f $($this.OrganizationName), $responseObj.id } catch { Write-Host "Could not reactivate the bug" -ForegroundColor Red } } else { Write-Host "Could not reactivate the bug" -ForegroundColor Red } } #if the bug state was intially resolved, add in the state data to be referenced later if ($state.value -eq "Resolved") { $control.ControlResults.AddMessage("Resolved Bug", $bugUrl) } #if the bug state was initially active else { $control.ControlResults.AddMessage("Active Bug", $bugUrl) }#> #change the assignee for resolved bugs only $url = "https://dev.azure.com/{0}/{1}/_apis/wit/workitems/{2}?api-version=5.1" -f $this.OrganizationName, $ProjectName, $id if ($state -eq "Resolved") { $BugTemplate = $null; #Check if serviceid is not null and current resource scanned serviceid and bug respons serviceid is not equal, then update the service data. if ($this.ShowBugsInS360 -and $serviceId -and ($serviceIdInLoggedBug -ne $serviceId)) { $BugTemplate = $this.UpdateSTBugTemplate($serviceId, $control.ControlItem.ControlSeverity, $true, $AssignedTo) } else { $BugTemplate = [ConfigurationManager]::LoadServerConfigFile("TemplateForResolvedBug.json") $BugTemplate = $BugTemplate | ConvertTo-Json -Depth 10 $BugTemplate = $BugTemplate.Replace("{0}", $AssignedTo) } $header = [WebRequestHelper]::GetAuthHeaderFromUriPatch($url) try { #TODO: shift all this as a patch request in webrequesthelper class and manage accented characters as well $responseObj = Invoke-RestMethod -Uri $url -Method Patch -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $BugTemplate $control.ControlResults.AddMessage("Resolved Bug", $bugUrl) } catch { $areaPath = [BugLogPathManager]::AreaPath #if the user to whom the bug has been assigneed is not a member of org any more if ($_.ErrorDetails.Message -like '*System.AssignedTo*') { $body = $BugTemplate | ConvertFrom-Json #let it remain assigned $body[2].value = ""; $body = $body | ConvertTo-Json try { $responseObj = Invoke-RestMethod -Uri $url -Method Patch -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $body $control.ControlResults.AddMessage("Resolved Bug", $bugUrl) } catch { Write-Host "Could not reactivate the bug" -ForegroundColor Red } } elseif ($_.ErrorDetails.Message -like '*Invalid Area*') { Write-Host "Could not reactivate the bug. Please verify the area path [$areaPath]. Area path should belong under the same project area." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*Invalid tree name given for work item*' -and $_.ErrorDetails.Message -like '*System.AreaPath*') { Write-Host "Could not reactivate the bug. Please verify the area path [$areaPath]. Area path should belong under the same project area." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*The current user does not have permissions to save work items under the specified area path*') { Write-Host "Could not reactivate the bug. You do not have permissions to save work items under the area path [$areaPath]." -ForegroundColor Red } else { Write-Host "Could not reactivate the bug." -ForegroundColor Red } } } else { $control.ControlResults.AddMessage("Active Bug", $bugUrl); #Update the serviceid details, if serviceid not null and not matched with bug response serviceid. if ($this.ShowBugsInS360 -and $serviceId -and ($serviceIdInLoggedBug -ne $serviceId)) { $BugTemplate = $null; $header = [WebRequestHelper]::GetAuthHeaderFromUriPatch($url) try { $BugTemplate = $this.UpdateSTBugTemplate($serviceId, $control.ControlItem.ControlSeverity, $false, $AssignedTo); $responseObj = Invoke-RestMethod -Uri $url -Method Patch -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $BugTemplate } catch { $areaPath = [BugLogPathManager]::AreaPath if ($_.ErrorDetails.Message -like '*Invalid Area*') { Write-Host "Could not update service tree details in the bug. Please verify the area path [$areaPath]. Area path should belong under the same project area." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*Invalid tree name given for work item*' -and $_.ErrorDetails.Message -like '*System.AreaPath*') { Write-Host "Could not update service tree details in the bug. Please verify the area path [$areaPath]. Area path should belong under the same project area." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*The current user does not have permissions to save work items under the specified area path*') { Write-Host "Could not update service tree details in the bug. You do not have permissions to save work items under the area path [$areaPath]." -ForegroundColor Red } else { Write-Host "Could not update service tree details in the bug." } } } } } #status has value if it is called from resolved to activate bug, else the value is empty, if status not needed to change hidden [object] UpdateSTBugTemplate($serviceId, $controlSeverity, $reactivateBug, $assignedTo) { $BugTemplate = [ConfigurationManager]::LoadServerConfigFile("TemplateForUpdateBugS360.json"); #Activate resolved bug, else update serviceid details only. if ($reactivateBug) { $BugTemplate = $BugTemplate | ConvertTo-Json -Depth 10 $BugTemplate = $BugTemplate.Replace("{0}", "Active") } else { $BugTemplate = $BugTemplate | Where {$_.path -ne "/fields/System.State" } $BugTemplate = $BugTemplate | ConvertTo-Json -Depth 10 } $BugTemplate = $BugTemplate.Replace("{1}", $AssignedTo) #$secSeverity used to get calculated value of security severity (if supplied in command parameter then get it from command, else get from control severity) $secSeverity = ""; if ($this.InvocationContext.BoundParameters["SecuritySeverity"]) { $secSeverity = $this.InvocationContext.BoundParameters["SecuritySeverity"]; } else { $secSeverity = $controlSeverity; } $SecuritySeverity = $this.GetSecuritySeverity($secSeverity) $BugTemplate = $BugTemplate.Replace("{2}", $this.controlsettings.BugLogging.HowFound) #ComplianceArea $BugTemplate = $BugTemplate.Replace("{3}", $this.controlsettings.BugLogging.ComplianceArea) #ServiceHierarchyId $BugTemplate = $BugTemplate.Replace("{4}", $serviceId) #ServiceHierarchyIdType $BugTemplate = $BugTemplate.Replace("{5}", $this.controlsettings.BugLogging.ServiceTreeIdType) #Severity $BugTemplate = $BugTemplate.Replace("{6}", $SecuritySeverity) $BugTemplate = $BugTemplate.Replace("{7}", [BugLogPathManager]::AreaPath) return $BugTemplate; } #function to search for existing bugs based on the hash hidden [object] GetWorkItemByHash([string] $hash, [string] $ProjectName) { if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { return $this.BugLogHelperObj.GetWorkItemByHashAzureTable($hash, $ProjectName, $this.ControlSettings.BugLogging.ResolvedBugLogBehaviour); } else { $url = "https://{0}.almsearch.visualstudio.com/{1}/_apis/search/workItemQueryResults?api-version=5.1-preview" -f $this.OrganizationName, $ProjectName #TODO: validate set to allow only two values : ReactiveOldBug and CreateNewBug #check for ResolvedBugBehaviour in control settings #takeResults is used to fetch number of workitems to be return. At caller side of this method we are checking if return greter then 0, then manage work item else add new. if ($this.ControlSettings.BugLogging.ResolvedBugLogBehaviour -ne "ReactiveOldBug") { #new bug is to be logged for every resolved bug, hence search for only new/active bug $body = '{"searchText":"{0}","skipResults":0,"takeResults":2,"sortOptions":[],"summarizedHitCountsNeeded":true,"searchFilters":{"Projects":["{1}"],"Work Item Types":["Bug"],"States":["Active","New"]},"filters":[],"includeSuggestions":false}' | ConvertFrom-Json } else { #resolved bug needs to be reactivated, hence search for new/active/resolved bugs $body = '{"searchText":"{0}","skipResults":0,"takeResults":2,"sortOptions":[],"summarizedHitCountsNeeded":true,"searchFilters":{"Projects":["{1}"],"Work Item Types":["Bug"],"States":["Active","New","Resolved"]},"filters":[],"includeSuggestions":false}' | ConvertFrom-Json } #tag to be searched $body.searchText = "Tags: " + $hash $body.searchFilters.Projects = $ProjectName $response = [WebRequestHelper]::InvokePostWebRequest($url, $body) return $response } } #function to compute hash and return the tag hidden [string] GetHashedTag([string] $ControlId, [string] $ResourceId) { $hashedTag = $null $stringToHash = "$ResourceId#$ControlId"; #return the bug tag if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { return [AutoBugLog]::ComputeHashX($stringToHash); } else { return "ADOScanID: " + [AutoBugLog]::ComputeHashX($stringToHash) } } hidden [void] AddWorkItem([string] $Title, [string] $Description, [string] $AssignedTo, [string]$Severity, [string]$ProjectName, [SVTEventContext[]] $control, [string] $hash, [string] $serviceId) { #logging new bugs $apiurl = 'https://dev.azure.com/{0}/{1}/_apis/wit/workitems/$bug?api-version=5.1' -f $this.OrganizationName, $ProjectName; $BugTemplate = $null; $SecuritySeverity = ""; if ($this.ShowBugsInS360) { $BugTemplate = [ConfigurationManager]::LoadServerConfigFile("TemplateForNewBugS360.json") #Check if security severity passed in the command parameter, if passed take command parameter else take control severity. $secSeverity = ""; if ($this.InvocationContext.BoundParameters["SecuritySeverity"]) { $secSeverity = $this.InvocationContext.BoundParameters["SecuritySeverity"]; } else { $secSeverity = $control.ControlItem.ControlSeverity; } $SecuritySeverity = $this.GetSecuritySeverity($secSeverity) } else { $BugTemplate = [ConfigurationManager]::LoadServerConfigFile("TemplateForNewBug.json"); } # Replace the field reference name for bug description if it is customized if ($this.BugDescriptionField) { $BugTemplate[1].path = $this.BugDescriptionField; } $BugTemplate = $BugTemplate | ConvertTo-Json -Depth 10 #$BugTemplate = $BugTemplate -f $Title, $Description, $Severity, $AreaPath, $IterationPath, $hash, $AssignedTo $BugTemplate = $BugTemplate.Replace("{0}", $Title) $BugTemplate = $BugTemplate.Replace("{1}", $Description) $BugTemplate = $BugTemplate.Replace("{2}", $Severity) $BugTemplate = $BugTemplate.Replace("{3}", [BugLogPathManager]::AreaPath) $BugTemplate = $BugTemplate.Replace("{4}", [BugLogPathManager]::IterationPath) if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { $BugTemplate = $BugTemplate.Replace("{5}", "ADOScanner") } else { $BugTemplate = $BugTemplate.Replace("{5}", $hash) } $BugTemplate = $BugTemplate.Replace("{6}", $AssignedTo) if ($this.ShowBugsInS360) { $BugTemplate = $BugTemplate.Replace("{7}", $this.controlsettings.BugLogging.HowFound) #ComplianceArea $BugTemplate = $BugTemplate.Replace("{8}", $this.controlsettings.BugLogging.ComplianceArea) #ServiceHierarchyId $BugTemplate = $BugTemplate.Replace("{9}", $serviceId) #ServiceHierarchyIdType $BugTemplate = $BugTemplate.Replace("{10}", $this.controlsettings.BugLogging.ServiceTreeIdType) #Severity $BugTemplate = $BugTemplate.Replace("{11}", $SecuritySeverity) } $responseObj = $null $header = [WebRequestHelper]::GetAuthHeaderFromUriPatch($apiurl) try { $responseObj = Invoke-RestMethod -Uri $apiurl -Method Post -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $BugTemplate $bugUrl = "https://{0}.visualstudio.com/_workitems/edit/{1}" -f $this.OrganizationName, $responseObj.id $control.ControlResults.AddMessage("New Bug", $bugUrl); if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { $this.BugLogHelperObj.InsertBugInfoInTable($hash, $ProjectName, $responseObj.id); } } catch { #handle assignee users who are not part of org any more if ($_.ErrorDetails.Message -like '*System.AssignedTo*') { $BugTemplate = $BugTemplate | ConvertFrom-Json $BugTemplate[6].value = ""; $BugTemplate = $BugTemplate | ConvertTo-Json try { $responseObj = Invoke-RestMethod -Uri $apiurl -Method Post -ContentType "application/json-patch+json ; charset=utf-8" -Headers $header -Body $BugTemplate $bugUrl = "https://{0}.visualstudio.com/_workitems/edit/{1}" -f $this.OrganizationName, $responseObj.id $control.ControlResults.AddMessage("New Bug", $bugUrl) if ($this.UseAzureStorageAccount -and $this.ScanSource -eq "CA") { $this.BugLogHelperObj.InsertBugInfoInTable($hash, $ProjectName, $responseObj.id); } } catch { Write-Host "Could not log the bug" -ForegroundColor Red } } #handle the case wherein due to global search area/ iteration paths from different projects passed the checkvalidpath function elseif ($_.ErrorDetails.Message -like '*Invalid Area/Iteration id*') { Write-Host "Please verify the area and iteration path. They should belong under the same project area." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*Invalid tree name given for work item*' -and $_.ErrorDetails.Message -like '*System.AreaPath*') { Write-Host "Please verify the area and iteration path are valid." -ForegroundColor Red } elseif ($_.ErrorDetails.Message -like '*The current user does not have permissions to save work items under the specified area path*') { $areaPath = [BugLogPathManager]::AreaPath Write-Host "Could not log the bug. You do not have permissions to save work items under the area path [$($areaPath)]." -ForegroundColor Red } else { Write-Host "Could not log the bug" -ForegroundColor Red } } } #the next two functions to check baseline and preview baseline, are duplicate controls that are present in ADOSVTBase as well. #they have been added again, due to behaviour of framework, where the file that needs to called in a certain file has to be mentioned #above the other file as it is dumped in the memory before the second file. This behaviour will effectively create a deadlock #in this case, as we have to create autobuglog object in adosvtbase, making it be declared first in framework and hence the following controls #cant be accessed here from adosvtbase. #function to check if the current control is a baseline control or not hidden [bool] CheckBaselineControl($controlId) { $baselineControl = $this.ControlSettings.BaselineControls.ResourceTypeControlIdMappingList | Where-Object { $_.ControlIds -contains $controlId } if (($baselineControl | Measure-Object).Count -gt 0 ) { return $true } return $false } #function to check if the current control is a preview baseline control or not hidden [bool] CheckPreviewBaselineControl($controlId) { if (($null -ne $this.ControlSettings) -and [Helpers]::CheckMember($this.ControlSettings, "PreviewBaselineControls.ResourceTypeControlIdMappingList")) { $PreviewBaselineControls = $this.ControlSettings.PreviewBaselineControls.ResourceTypeControlIdMappingList | Where-Object { $_.ControlIds -contains $controlId } if (($PreviewBaselineControls | Measure-Object).Count -gt 0 ) { return $true } } return $false } hidden [bool] CheckControlInCustomControlList($controlId) { if ([Helpers]::CheckMember($this.ControlSettings.BugLogging, "CustomControlList")) { $customControlList = $this.ControlSettings.BugLogging | Where-Object { $_.CustomControlList -contains $controlId } if (($customControlList | Measure-Object).Count -gt 0 ) { return $true } } return $false } } # SIG # Begin signature block # MIIjiAYJKoZIhvcNAQcCoIIjeTCCI3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCSmGQd9AL94jCk # tTp5oLmVSJKTP2TmTT248EXq1e4HbqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVXTCCFVkCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgHwKvnFJj # NUmlOZzZMmcWsMK0Mja8LM43B0VSmLDiW1swRAYKKwYBBAGCNwIBDDE2MDSgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g # MA0GCSqGSIb3DQEBAQUABIIBAM4dvwkt1IDJtBGAw9QcfH8CZabNAXcc5S6nrgC+ # of4k/pNiI97d+EkX3d1MQsyg18JJWA1n3ISwUUz4HVTmE9prtIR8pX4oI5oZs0JE # mJnhDM777Im9R+A8TdCynvDOqScf2RcvEAAkE7nwpocZRL14s/XdOr/PYt2v4tzb # sNLbbLuIbDWiMiaPItcXQCTZATI+ga5LbFggE/sylUcTmwR4kGk4lxzhWXxePwVU # ktd0ZLOn8mtipTXS1uswJdxTRjqMBMhHorz7bl3G7EZzYPdR+XnSqvqcD2BEy5VZ # LSIQa8gcTg3HYWSveIaHaDFWucUIscMkIPPfsW/KPfAvE0KhghLlMIIS4QYKKwYB # BAGCNwMDATGCEtEwghLNBgkqhkiG9w0BBwKgghK+MIISugIBAzEPMA0GCWCGSAFl # AwQCAQUAMIIBUQYLKoZIhvcNAQkQAQSgggFABIIBPDCCATgCAQEGCisGAQQBhFkK # AwEwMTANBglghkgBZQMEAgEFAAQgEcLM+1kGGwHEZkk1CbSUaGIjZjqpIfjzHYoj # 3v8wbR8CBmAliMj7UxgTMjAyMTAyMTUwNDUxMjYuNjk4WjAEgAIB9KCB0KSBzTCB # yjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMc # TWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRT # UyBFU046MjI2NC1FMzNFLTc4MEMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2Wggg48MIIE8TCCA9mgAwIBAgITMwAAAUqk9zHE/yKiSQAAAAAB # SjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe # Fw0yMDExMTIxODI1NThaFw0yMjAyMTExODI1NThaMIHKMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoyMjY0LUUzM0Ut # NzgwQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN7KKGZkolgsvVEaNKMTVZZgEl8o # hsLgHR4gUFWLZvWzLbegDoRItpfFd+9maW2hPFlgT+wv7lxf6OB4HFYZgHfIpcZh # GU6/ebsymXYAmAKzKph71pxJU5F228YTSTLcoSAIUNBZVdTEIZILEPT5gI77Ysu7 # YKMufSmiZPzqYlkEX2/dHhOcoo90zgIJRTG1u2kF7w6a7D50yHKE46eGEwqwjExE # RCCNtFBDQrTfYID/Icj0zKikYjiJRaaPNjnvBaRJ/eFkGz8gD2XyYXjlsNjDGPaG # PQTt/Rm3nrxcyXGyCIIhWdBMXMTLl7BMDKKeLBQ0d6pFfS1LRJo+paKKBiUCAwEA # AaOCARswggEXMB0GA1UdDgQWBBRxjGEYMfrAhjWKk/99frgmKqk/4TAfBgNVHSME # GDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRw # Oi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQ # Q0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8y # MDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMI # MA0GCSqGSIb3DQEBCwUAA4IBAQBRC67dRjgFFS9kv72Vfe8gQ+Hg3FpX2TYyOq4n # rtPq9D36Udydr2ibZy5n7LphXvW20bDTugUHiwuyfWnmyc2oEevo+SrNCzxXcj59 # Wv9lQpBgtL6OM56x+v1zbNzp/moMwk3UvysE5af5rktfFtPx6apqcjU1IDt09hX8 # 0ZAzqPflPPyC5Cj3J8DQilQz2/TzSZvcbgCM9vuwLu9p9bZhJemNP++3LrHkdycf # HZf3jv7QBAigEvyVb2mrnlomFIKCyJW1cOrBjIqyntQt5PK8zKxX/yZlyiRbr8c0 # DQw8tYpXeyorgoVet9sAF+t3g/cYzVogW4qwhuyZmEmTlTSKMIIGcTCCBFmgAwIB # AgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2Vy # dGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAx # MjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw # JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZI # hvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoA # goX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiE # VEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+B # VLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3w # V3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXo # eByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYw # ggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNo # WoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW # BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH # AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGV # MIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIw # NB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4A # dAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM # 9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0 # YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgP # F/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/62 # 5Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZq # kHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96 # LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5v # vfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiF # AR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduW # sqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV # 42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto2 # 29Nfj950iEkSoYICzjCCAjcCAQEwgfihgdCkgc0wgcoxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNh # IE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjIyNjQtRTMzRS03 # ODBDMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw # BwYFKw4DAhoDFQC8BO6GhSDKwTN3KQTVtEHiiHprmKCBgzCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA49P7UzAiGA8y # MDIxMDIxNTAzNDEwN1oYDzIwMjEwMjE2MDM0MTA3WjB3MD0GCisGAQQBhFkKBAEx # LzAtMAoCBQDj0/tTAgEAMAoCAQACAh1MAgH/MAcCAQACAhFkMAoCBQDj1UzTAgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAG8HWE9kVrHDtHHbl2lM4id2qukIG # CLcSMO51d1m9Ma1Sm88apEvIjQ9G4BAOBskd1c4IM0Pa0xGTbfbz4nouscPpjdHS # /ZSySZ9s7ujgXvGeuxDHKD2x1c2hBveIFcD5eoo8LjxTaKfVhEjUzrybMAASSIBg # DT9XA20Q/0DVw7cxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ # Q0EgMjAxMAITMwAAAUqk9zHE/yKiSQAAAAABSjANBglghkgBZQMEAgEFAKCCAUow # GgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCB4olw3 # xOZJqV5o34HUAwoMcI9qa31/RJ4PPfsL0zWe7zCB+gYLKoZIhvcNAQkQAi8xgeow # gecwgeQwgb0EIGwdktetudtX/kn7Yq/AVYiBWZBq+n4EFVQ8zUD3IlEDMIGYMIGA # pH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFKpPcxxP8iokkA # AAAAAUowIgQgxhHMS6oyA0xtcdW+oKOW5xfd2JEI2VlG9cvbsfrbQUYwDQYJKoZI # hvcNAQELBQAEggEADLQfFpn9XGhIUf5nskn3xXOQrUgUpEjJsWjIVko4AHV7M423 # GLbUIhopNIQaT6vYAFpJtAX3blKjLdnTkkZaViMRvVVNgIcofXWV848XDsRyIu8G # ilaaVAcYQyC8PxA+iXq/CWtTm4qdnG9bov/4KbiNfqoarMxtz1Azb7BbJxmD8MvO # gyFTTl9UxHsAOmwav4HSa5BxPbQVEAZflYRjVm1BXukbvHlK801sNyU+AXqkz0M9 # uCi6QohqlEedHbSi+YZpeEmfRQ8ZGy+2Oscca3ZN4QdkjBOn53v1ilPWSVAKGV6j # X1SJsxvvFfKY1vrrVvxQDjDybvWVNFyBaGKMOw== # SIG # End signature block |