From 04639457bdb614ec3ab825867abc708101d71fcf Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 4 Nov 2023 13:49:10 +0100 Subject: [PATCH 01/16] PR-task: support multiple AlertTypes to check --- dependencyReviewTask/index.ts | 202 +++++++++++++++++++++------------ dependencyReviewTask/task.json | 26 +++-- 2 files changed, 149 insertions(+), 79 deletions(-) diff --git a/dependencyReviewTask/index.ts b/dependencyReviewTask/index.ts index d08c391..25f8830 100644 --- a/dependencyReviewTask/index.ts +++ b/dependencyReviewTask/index.ts @@ -26,20 +26,33 @@ interface IResponse { result: IResult; } -async function getAlerts(connection: WebApi, orgSlug: string, project: string, repository: string, branchName: string) { - const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project}/_apis/AdvancedSecurity/repositories/${repository}/alerts?criteria.alertType=1&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true`; +async function getAlerts( + connection: WebApi, + orgSlug: string, + project: string, + repository: string, + branchName: string, + alertType: number + ) +{ + if (!(alertType == 1 || alertType == 2)) { + console.log(`Error loading alerts for branch [${branchName}] with alertType [${alertType}]`) + return null + } + + const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project}/_apis/AdvancedSecurity/repositories/${repository}/alerts?criteria.alertType=${alertType}&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true` let branchResponse: IResponse try { - branchResponse = await connection.rest.get(branchUrl); + branchResponse = await connection.rest.get(branchUrl) } catch (err: unknown) { if (err instanceof Error) { if (err.message.includes('Branch does not exist')) { - console.log(`Branch [${branchName}] does not exist in GHAzDo yet. Make sure to run the Dependency Scan task first on this branch (easiest to do in the same pipeline).`); + console.log(`Branch [${branchName}] does not exist in GHAzDo yet. Make sure to run the Dependency Scan task first on this branch (easiest to do in the same pipeline).`) } else { - console.log(`An error occurred: ${err.message}`); + console.log(`An error occurred: ${err.message}`) } } } @@ -49,87 +62,136 @@ async function getAlerts(connection: WebApi, orgSlug: string, project: string, r async function run() { try { // test to see if this build was triggered with a PR context - const buildReason = tl.getVariable('Build.Reason'); + const buildReason = tl.getVariable('Build.Reason') if (buildReason != 'PullRequest') { - tl.setResult(tl.TaskResult.Skipped, `This extension only works when triggered by a Pull Request and not by a [${buildReason}]`); + tl.setResult(tl.TaskResult.Skipped, `This extension only works when triggered by a Pull Request and not by a [${buildReason}]`) return } - // todo: convert to some actual setting - const inputString: string | undefined = tl.getInput('samplestring', true); - if (inputString == 'bad') { - tl.setResult(tl.TaskResult.Failed, 'Bad input was given'); - - // stop the task execution - return; + // todo: convert to some actual value | boolean setting, for example severity score or switch between Dependency and CodeQL alerts + const scanForDependencyAlerts : string | undefined = tl.getInput('DepedencyAlertsScan', true) + if (scanForDependencyAlerts !== 'enabled') { + // todo? } - console.log('Hello', inputString); - - const token = getSystemAccessToken(); - const authHandler = getHandlerFromToken(token); - const uri = tl.getVariable("System.CollectionUri"); - const connection = new WebApi(uri, authHandler); - const organization = tl.getVariable('System.TeamFoundationCollectionUri'); - const orgSlug = organization.split('/')[3]; - const project = tl.getVariable('System.TeamProject'); - const repository = tl.getVariable('Build.Repository.ID'); - const sourceBranch = tl.getVariable('System.PullRequest.SourceBranch'); - const sourceBranchName = sourceBranch?.split('/')[2]; - const targetBranchName = tl.getVariable('System.PullRequest.targetBranchName'); - - console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`); - - const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName); - const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName); + const scanForCodeScanningAlerts : string | undefined = tl.getInput('CodeScanningAlerts', true) + if (scanForCodeScanningAlerts !== 'enabled') { + // todo? + } - tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`); - tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`); + const token = getSystemAccessToken() + const authHandler = getHandlerFromToken(token) + const uri = tl.getVariable("System.CollectionUri") + const connection = new WebApi(uri, authHandler) + + const organization = tl.getVariable('System.TeamFoundationCollectionUri') + const orgSlug = organization.split('/')[3] + const project = tl.getVariable('System.TeamProject') + const repository = tl.getVariable('Build.Repository.ID') + const sourceBranch = tl.getVariable('System.PullRequest.SourceBranch') + const sourceBranchName = sourceBranch?.split('/')[2] + const targetBranchName = tl.getVariable('System.PullRequest.targetBranchName') + + let alertType = 0 + let errorString = "" + console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`) + if (scanForDependencyAlerts == 'enabled') { + alertType = 1 // Dependency Scanning alerts + const dependencyResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) + if (dependencyResult.newAlertsFound) { + errorString += dependencyResult.message + } + } - if (sourceBranchResponse.result.count == 0) { - console.log('No alerts found for this branch'); + if (scanForCodeScanningAlerts == 'enabled') { + alertType = 3 // Code Scanning alerts + const codeScanningResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) + if (codeScanningResult.newAlertsFound) { + errorString += codeScanningResult.message + } + } - tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`); - return; + if (scanForDependencyAlerts !== 'enabled' && scanForCodeScanningAlerts !== 'enabled') { + const message = `No options selected to check for either dependency scanning alerts or code scanning alerts` + console.log(message) + tl.setResult(tl.TaskResult.Skipped, message) + return } - else { - // check by result.alertId if there is a new alert or not (so alert not in targetBranch) - - // first get the only the alertid's from the source branch - const sourceAlertIds = sourceBranchResponse.result.value.map((alert) => {return alert.alertId;}); - // do the same for the target branch - const targetAlertIds = targetBranchResponse.result.value.map((alert) => {return alert.alertId;}); - // now find the delta - const newAlertIds = sourceAlertIds.filter((alertId) => { - return !targetAlertIds.includes(alertId); - }); - - if (newAlertIds.length > 0) { - - console.log(`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new:`); - for (const alertId of newAlertIds) { - // get the alert details: - const alertUrl = `https://dev.azure.com/${orgSlug}/${project}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}`; - const alertTitle = sourceBranchResponse.result.value.find((alert) => {return alert.alertId == alertId;})?.title; - // and show them: - console.log(`- ${alertId}: ${alertTitle}, url: ${alertUrl}`); - } - - tl.setResult(tl.TaskResult.Failed, `Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new`); - } - else { - console.log(`Found no new alerts for the source branch [${sourceBranchName}]`); - tl.setResult(tl.TaskResult.Succeeded, `Found no new alerts for the source branch [${sourceBranchName}], only [${targetBranchResponse.result.count}] existing ones`); - } + + if (errorString.length > 0) { + tl.setResult(tl.TaskResult.Failed, errorString) } } catch (err: unknown) { if (err instanceof Error) { - tl.setResult(tl.TaskResult.Failed, err.message); + tl.setResult(tl.TaskResult.Failed, err.message) } else { - tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred'); + tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred') + } + } + + // everything worked, no new alerts found and at least one scanning option was enabled + tl.setResult(tl.TaskResult.Succeeded) +} + +async function checkAlertsForType( + connection: WebApi, + orgSlug: string, + project: string, + repository: string, + alertType: number, + sourceBranchName: string, + targetBranchName: string +): Promise<{newAlertsFound: boolean, message: string}> { + const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName, alertType) + const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName, alertType) + + tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`) + tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`) + + let alertTypeString = `Dependency` + if (alertType == 2) { + alertTypeString = `Code scanning` + } + + if (sourceBranchResponse.result.count == 0) { + console.log(`No alerts found for this branch for alert type [${alertTypeString}]`) + + //tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`) + return {newAlertsFound: false, message: ``} + } + else { + // check by result.alertId if there is a new alert or not (so alert not in targetBranch) + + // first get the only the alertid's from the source branch + const sourceAlertIds = sourceBranchResponse.result.value.map((alert) => {return alert.alertId;}) + // do the same for the target branch + const targetAlertIds = targetBranchResponse.result.value.map((alert) => {return alert.alertId;}) + // now find the delta + const newAlertIds = sourceAlertIds.filter((alertId) => { + return !targetAlertIds.includes(alertId) + }); + + if (newAlertIds.length > 0) { + let message =`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new:` + console.log(message) + for (const alertId of newAlertIds) { + // get the alert details: + const alertUrl = `https://dev.azure.com/${orgSlug}/${project}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}` + const alertTitle = sourceBranchResponse.result.value.find((alert) => {return alert.alertId == alertId;})?.title + // and show them: + const specificAlertMessage = `- ${alertId}: ${alertTitle}, url: ${alertUrl}` + console.log(specificAlertMessage) + message += `\\n${specificAlertMessage}` // todo: check if this new line actually works :-) + } + return {newAlertsFound: true, message: message} + } + else { + const message = `Found no new alerts for the source branch [${sourceBranchName}]` + console.log(message) + return {newAlertsFound: false, message: message} } } } -run(); \ No newline at end of file +run() \ No newline at end of file diff --git a/dependencyReviewTask/task.json b/dependencyReviewTask/task.json index 3f59ef9..1f6a04a 100644 --- a/dependencyReviewTask/task.json +++ b/dependencyReviewTask/task.json @@ -1,10 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", "id": "10c1d88a-9d0f-4288-8e37-58762caa0b8b", - "name": "Advanced-Dependency-Review", - "friendlyName": "Advanced Security Dependency Review", - "description": "Scan the source branch in your PR for known Dependency issues", - "helpMarkDown": "Checks the source branch in your PR for known Dependency issues", + "name": "Advanced-Security-Review", + "friendlyName": "Advanced Security Review", + "description": "Scan the source branch in your PR for known Advanced Security issues", + "helpMarkDown": "Checks the source branch in your PR for known Advanced Security issues", "category": "Utility", "author": "RobBos", "version": { @@ -15,12 +15,20 @@ "instanceNameFormat": "Echo $(samplestring)", "inputs": [ { - "name": "samplestring", - "type": "string", - "label": "Sample String", - "defaultValue": "", + "name": "DepedencyAlertsScan", + "type": "checkbox (if possible)", + "label": "Fail on new dependency alerts", + "defaultValue": "Yes", "required": true, - "helpMarkDown": "A sample string" + "helpMarkDown": "Fail the pipeline if there is a new depedency alert" + }, + { + "name": "CodeScanningAlerts", + "type": "checkbox (if possible)", + "label": "Fail on new dependency alerts", + "defaultValue": "Yes", + "required": true, + "helpMarkDown": "Fail the pipeline if there is a new depedency alert" } ], "execution": { From 98f4fb1f55689802d0e548892a042d6ecc25fc65 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 4 Nov 2023 14:09:32 +0100 Subject: [PATCH 02/16] Add config and load option for "all repos in project" --- widgets/library.js | 28 ++++++++++++++++++- .../widgets/widget_1x1/configuration_1x1.html | 13 +++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/widgets/library.js b/widgets/library.js index 8e2e998..f47e3f6 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -69,9 +69,35 @@ function GetAlertTypeFromValue(value) { } } +function fillSelectRepoDropdown(dropDown, repos) { + // add a top option to select no repo + dropDown.append(``); + dropDown.append(``); + // sort the repo alphabetically + repos.sort((a, b) => a.name.localeCompare(b.name)); + repos.forEach(r => { + $repoDropdown.append(``); + }); +} + async function getAlerts(organization, projectName, repoId) { - //consoleLog('getAlerts'); + if (repoId) { + // run normally for a single repo + return await getAlertsForRepo(organization, projectName, repoId) + } + else { + // todo: run for ALL repositories in the current project + return { + count: -1, + dependencyAlerts: -1, + secretAlerts: -1, + codeAlerts: -1 + } + } +} +async function getAlertsForRepo(organization, projectName, repoId) { + //consoleLog('getAlerts'); let values = { count: 0, dependencyAlerts: 0, diff --git a/widgets/widgets/widget_1x1/configuration_1x1.html b/widgets/widgets/widget_1x1/configuration_1x1.html index d401daa..bfa4955 100644 --- a/widgets/widgets/widget_1x1/configuration_1x1.html +++ b/widgets/widgets/widget_1x1/configuration_1x1.html @@ -31,7 +31,7 @@ var customSettings = { data: JSON.stringify({ repo: $repoDropdown.val(), - repoId: repo.id, + repoId: repo?.id, repoAlertType: $repoAlertType.val() }) }; @@ -46,13 +46,8 @@ // add all repos as selection options to the dropdown if (repos) { - // add a top option to select no repo - $repoDropdown.append(``); - // sort the repo alphabetically - repos.sort((a, b) => a.name.localeCompare(b.name)); - repos.forEach(r => { - $repoDropdown.append(``); - }); + // todo: use in all other widget locations as well + fillSelectRepoDropdown($repoDropdown, repos) } if (settings && settings.repo) { @@ -85,7 +80,7 @@ var customSettings = { data: JSON.stringify({ repo: $repoDropdown.val(), - repoId: repo.id, + repoId: repo?.id, repoAlertType: $repoAlertType.val() }) }; From c7767c5a677b5cd39b57b9dd84b443cc10c35d7b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:00:05 +0100 Subject: [PATCH 03/16] Determining the highst version in the extension file --- .../handle-versioning-accross-branches.yml | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/handle-versioning-accross-branches.yml diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml new file mode 100644 index 0000000..ef02434 --- /dev/null +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -0,0 +1,57 @@ +name: Handle versioning accros branches + +on: + push: + # todo: add file paths of the files with version numbers + +jobs: + extension-versioning: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: git-actions/set-user@v1 + + - name: Get highest version number accross all branches + id: get-version + shell: pwsh + run: | + # SemVer code + function Parse-SemVer ($version) { + $parts = $version.Split('.') + return @{ + Major = [int]$parts[0] + Minor = [int]$parts[1] + Patch = [int]$parts[2] + } + } + + $highestVersion = @{ + Major = 0 + Minor = 0 + Patch = 0 + } + + # loop over all branches + $highestVersion = 0 + foreach ($branch in $(git branch -r --format='%(refname:short)')) { + # checkout the branch + git checkout $branch + + # get the semantic version number from the vss-extension-dev.json file + $version = Get-Content -Path "src/$branch/vss-extension.json" | ConvertFrom-Json | Select-Object -ExpandProperty version + Write-Host "Found version: [$version] in branch: [$branch]" + + # check if the version is semantically higher than the highest version using SemVer + $version = Parse-SemVer $version + + if ($version.Major -gt $highestVersion.Major -or + ($version.Major -eq $highestVersion.Major -and $version.Minor -gt $highestVersion.Minor) -or + ($version.Major -eq $highestVersion.Major -and $version.Minor -eq $highestVersion.Minor -and $version.Patch -gt $highestVersion.Patch)) + { + $highestVersion = $version + + Write-Host "New highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" + } + } + + Write-Host "Highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" From f83d9892e4260484fecbc797f31555d0ee1b7219 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:03:22 +0100 Subject: [PATCH 04/16] fix error --- .github/workflows/handle-versioning-accross-branches.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index ef02434..7906636 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -8,9 +8,14 @@ jobs: extension-versioning: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: git-actions/set-user@v1 + - name: Prevent branch warnings + run: | + # config git advice.detachedHead to false + git config advice.detachedHead false + - name: Get highest version number accross all branches id: get-version shell: pwsh @@ -38,7 +43,7 @@ jobs: git checkout $branch # get the semantic version number from the vss-extension-dev.json file - $version = Get-Content -Path "src/$branch/vss-extension.json" | ConvertFrom-Json | Select-Object -ExpandProperty version + $version = Get-Content -Path "vss-extension.json" | ConvertFrom-Json | Select-Object -ExpandProperty version Write-Host "Found version: [$version] in branch: [$branch]" # check if the version is semantically higher than the highest version using SemVer From 0189d4a95ac543989dc777486150ef3257efb1f9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:20:52 +0100 Subject: [PATCH 05/16] Load main version from AzDo server --- .../handle-versioning-accross-branches.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index 7906636..2ee2c24 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -19,7 +19,14 @@ jobs: - name: Get highest version number accross all branches id: get-version shell: pwsh + env: + AZURE_DEVOPS_CREATE_PAT: ${{ secrets.AZURE_DEVOPS_CREATE_PAT}} run: | + # get the last updated version for this extension from the server + $output = $(tfx extension show --token $env:AZURE_DEVOPS_CREATE_PAT --vsix $vsix --publisher "RobBos" --extension-id "GH… + $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] + Write-Host "Last version: [$($lastVersion.version)] from server" + # SemVer code function Parse-SemVer ($version) { $parts = $version.Split('.') @@ -42,20 +49,18 @@ jobs: # checkout the branch git checkout $branch - # get the semantic version number from the vss-extension-dev.json file - $version = Get-Content -Path "vss-extension.json" | ConvertFrom-Json | Select-Object -ExpandProperty version + # get the semantic version number from the version in the dependencyReviewTask/task.json file + $version = Get-Content -Path "dependencyReviewTask/task.json" | ConvertFrom-Json | Select-Object -ExpandProperty version Write-Host "Found version: [$version] in branch: [$branch]" # check if the version is semantically higher than the highest version using SemVer - $version = Parse-SemVer $version - if ($version.Major -gt $highestVersion.Major -or ($version.Major -eq $highestVersion.Major -and $version.Minor -gt $highestVersion.Minor) -or ($version.Major -eq $highestVersion.Major -and $version.Minor -eq $highestVersion.Minor -and $version.Patch -gt $highestVersion.Patch)) { $highestVersion = $version - Write-Host "New highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" + Write-Host "New highest version from PR task.json: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" } } From ae553fd6f338de58cef68f1c13d0b65c52b1f5c9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:23:22 +0100 Subject: [PATCH 06/16] fix pwsh code --- .github/workflows/handle-versioning-accross-branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index 2ee2c24..4de4b87 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -23,7 +23,7 @@ jobs: AZURE_DEVOPS_CREATE_PAT: ${{ secrets.AZURE_DEVOPS_CREATE_PAT}} run: | # get the last updated version for this extension from the server - $output = $(tfx extension show --token $env:AZURE_DEVOPS_CREATE_PAT --vsix $vsix --publisher "RobBos" --extension-id "GH… + $output = $(tfx extension show --token $env:AZURE_DEVOPS_CREATE_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] Write-Host "Last version: [$($lastVersion.version)] from server" From 06e61269b35e17c2d9f7efb6bd79831a94dc26c4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:24:41 +0100 Subject: [PATCH 07/16] install the tfx extension --- .../workflows/handle-versioning-accross-branches.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index 4de4b87..81dd221 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -8,7 +8,7 @@ jobs: extension-versioning: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: git-actions/set-user@v1 - name: Prevent branch warnings @@ -16,6 +16,14 @@ jobs: # config git advice.detachedHead to false git config advice.detachedHead false + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install tfx extension + run: | + npm install -g tfx-cli + - name: Get highest version number accross all branches id: get-version shell: pwsh From eabacfb4f1f78e9ee985bb381e0aa1c73a108ca5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:27:18 +0100 Subject: [PATCH 08/16] check if we hit all branches --- .../workflows/handle-versioning-accross-branches.yml | 2 +- z-status.txt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 z-status.txt diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index 81dd221..766f1a7 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -54,7 +54,7 @@ jobs: # loop over all branches $highestVersion = 0 foreach ($branch in $(git branch -r --format='%(refname:short)')) { - # checkout the branch + Write-Host "Checkout the branch [$branch]" git checkout $branch # get the semantic version number from the version in the dependencyReviewTask/task.json file diff --git a/z-status.txt b/z-status.txt new file mode 100644 index 0000000..f7c6f10 --- /dev/null +++ b/z-status.txt @@ -0,0 +1,10 @@ +In this branch was added: +- start of selection option in the PR alert check task to switch between either/or 1. Dependency scanning alerts, 3. Code scanning alerts + - todo: check how a checkbox option here works + - todo: check if the reload works as well for this config screen +- in the 1x1 widget, code was added to configure this widget for either 1 repo or ALL repos in the project + - test loading the config, both in the config screen and the widget itself + - write the loop to go over ALL repos in the org (might be already be available in another branch) + + +Do check the versions of the widget before pushing! \ No newline at end of file From 61db55ba04560207a5b03d851176afedbb65b0a6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:29:15 +0100 Subject: [PATCH 09/16] get all branches --- .github/workflows/handle-versioning-accross-branches.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index 766f1a7..a3b1d63 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -9,6 +9,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: git-actions/set-user@v1 - name: Prevent branch warnings From bcdb3a700e970d4dbf45587bfffd79691377cfa1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:37:59 +0100 Subject: [PATCH 10/16] Updated the output --- .../workflows/handle-versioning-accross-branches.yml | 4 ++++ make.ps1 | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index a3b1d63..e4877ca 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -76,3 +76,7 @@ jobs: } Write-Host "Highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" + + # show the highest version number in GitHub by writing to the job summary file + Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the extension: [$($lastVersion.version | ConvertFrom-Json)]" + Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the PR check extension: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" diff --git a/make.ps1 b/make.ps1 index fb6a0ff..0e67b25 100644 --- a/make.ps1 +++ b/make.ps1 @@ -277,6 +277,16 @@ if ("build" -eq $command) { # delete all files with the name RobBos.GHAzDoWidget-DEV*.vsix Get-ChildItem -Path .\ -Filter $extensionPrefix*.vsix | Remove-Item -Force + # get the last updated version for this extension from the server to make sure we are rolling forward + $output = $(tfx extension show --token $env:AZURE_DEVOPS_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) + $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] + Write-Host "Last version: [$($lastVersion.version)] from server" + # overwrite the version in the json file + $json = Get-Content .\vss-extension-dev.json | ConvertFrom-Json + $json.version = $lastVersion + # write the json file back + $json | ConvertTo-Json | Set-Content .\vss-extension-dev.json + # build the task # todo: up the version number Set-Location .\dependencyReviewTask From 0fb0b40ea66a4c519af3c3ece66ff8386f2b20f6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 05:42:07 +0100 Subject: [PATCH 11/16] fix output --- .github/workflows/handle-versioning-accross-branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml index e4877ca..6ef5cfa 100644 --- a/.github/workflows/handle-versioning-accross-branches.yml +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -78,5 +78,5 @@ jobs: Write-Host "Highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" # show the highest version number in GitHub by writing to the job summary file - Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the extension: [$($lastVersion.version | ConvertFrom-Json)]" + Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the extension: [$($lastVersion.version)]" Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the PR check extension: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" From ed7fdd481cd2bbd4652ccfdc862cffa970f79440 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 06:13:19 +0100 Subject: [PATCH 12/16] Map URLS to new area name --- widgets/library.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/widgets/library.js b/widgets/library.js index f47e3f6..8eafa9c 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -117,7 +117,7 @@ async function getAlertsForRepo(organization, projectName, repoId) { // } // todo: use pagination option, now: get the first 5000 alerts - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=7.2-preview.1`; + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=7.2-preview.1`; //consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //authenticatedGet(url).then(alertResult => { @@ -154,7 +154,7 @@ async function getAlertsTrendLines(organization, projectName, repoId) { consoleLog(`getAlertsTrend for organization [${organization}], project [${projectName}], repo [${repoId}]`); try { - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&api-version=7.2-preview.1`; + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&api-version=7.2-preview.1`; consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //consoleLog('alertResult: ' + JSON.stringify(alertResult)); @@ -434,7 +434,7 @@ async function getAlertSeverityCounts(organization, projectName, repoId, alertTy ]; try { // todo: filter on alertType - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.alertType=${alertType.value}&criteria.states=1&api-version=7.2-preview.1`; + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.alertType=${alertType.value}&criteria.states=1&api-version=7.2-preview.1`; //consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //consoleLog('alertResult: ' + JSON.stringify(alertResult)); From 6766cacb44570c86f7b1e7af3e2ecb0ed4635c9c Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 07:34:30 +0100 Subject: [PATCH 13/16] updates --- dependencyReviewTask/index.ts | 12 +- dependencyReviewTask/task.json | 16 +- make.ps1 | 21 +- vss-extension-dev.json | 542 +++++++++++++++++---------------- widgets/library.js | 15 +- 5 files changed, 320 insertions(+), 286 deletions(-) diff --git a/dependencyReviewTask/index.ts b/dependencyReviewTask/index.ts index 25f8830..7117bf6 100644 --- a/dependencyReviewTask/index.ts +++ b/dependencyReviewTask/index.ts @@ -70,12 +70,14 @@ async function run() { // todo: convert to some actual value | boolean setting, for example severity score or switch between Dependency and CodeQL alerts const scanForDependencyAlerts : string | undefined = tl.getInput('DepedencyAlertsScan', true) - if (scanForDependencyAlerts !== 'enabled') { + console.log(`scanForDependencyAlerts: ${scanForDependencyAlerts}`) + if (scanForDependencyAlerts !== 'true') { // todo? } const scanForCodeScanningAlerts : string | undefined = tl.getInput('CodeScanningAlerts', true) - if (scanForCodeScanningAlerts !== 'enabled') { + console.log(`scanForCodeScanningAlerts: ${scanForCodeScanningAlerts}`) + if (scanForCodeScanningAlerts !== 'true') { // todo? } @@ -95,7 +97,7 @@ async function run() { let alertType = 0 let errorString = "" console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`) - if (scanForDependencyAlerts == 'enabled') { + if (scanForDependencyAlerts == 'true') { alertType = 1 // Dependency Scanning alerts const dependencyResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) if (dependencyResult.newAlertsFound) { @@ -103,7 +105,7 @@ async function run() { } } - if (scanForCodeScanningAlerts == 'enabled') { + if (scanForCodeScanningAlerts == 'true') { alertType = 3 // Code Scanning alerts const codeScanningResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) if (codeScanningResult.newAlertsFound) { @@ -111,7 +113,7 @@ async function run() { } } - if (scanForDependencyAlerts !== 'enabled' && scanForCodeScanningAlerts !== 'enabled') { + if (scanForDependencyAlerts !== 'true' && scanForCodeScanningAlerts !== 'true') { const message = `No options selected to check for either dependency scanning alerts or code scanning alerts` console.log(message) tl.setResult(tl.TaskResult.Skipped, message) diff --git a/dependencyReviewTask/task.json b/dependencyReviewTask/task.json index 1f6a04a..d57b941 100644 --- a/dependencyReviewTask/task.json +++ b/dependencyReviewTask/task.json @@ -10,25 +10,25 @@ "version": { "Major": 0, "Minor": 1, - "Patch": 23 + "Patch": 25 }, "instanceNameFormat": "Echo $(samplestring)", "inputs": [ { "name": "DepedencyAlertsScan", - "type": "checkbox (if possible)", + "type": "boolean", "label": "Fail on new dependency alerts", - "defaultValue": "Yes", + "defaultValue": true, "required": true, - "helpMarkDown": "Fail the pipeline if there is a new depedency alert" + "helpMarkDown": "Fail the pipeline if there is a new dependency alert" }, { "name": "CodeScanningAlerts", - "type": "checkbox (if possible)", - "label": "Fail on new dependency alerts", - "defaultValue": "Yes", + "type": "boolean", + "label": "Fail on new code scanning alerts", + "defaultValue": true, "required": true, - "helpMarkDown": "Fail the pipeline if there is a new depedency alert" + "helpMarkDown": "Fail the pipeline if there is a new code scanning alert" } ], "execution": { diff --git a/make.ps1 b/make.ps1 index 0e67b25..4822991 100644 --- a/make.ps1 +++ b/make.ps1 @@ -278,14 +278,19 @@ if ("build" -eq $command) { Get-ChildItem -Path .\ -Filter $extensionPrefix*.vsix | Remove-Item -Force # get the last updated version for this extension from the server to make sure we are rolling forward - $output = $(tfx extension show --token $env:AZURE_DEVOPS_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) - $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] - Write-Host "Last version: [$($lastVersion.version)] from server" - # overwrite the version in the json file - $json = Get-Content .\vss-extension-dev.json | ConvertFrom-Json - $json.version = $lastVersion - # write the json file back - $json | ConvertTo-Json | Set-Content .\vss-extension-dev.json + try { + $output = $(tfx extension show --token $env:AZURE_DEVOPS_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) + $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] + Write-Host "Last version: [$($lastVersion.version)] from server" + # overwrite the version in the json file + $json = Get-Content .\vss-extension-dev.json | ConvertFrom-Json -Depth 10 + $json.version = $lastVersion.version + # write the json file back + $json | ConvertTo-Json -Depth 10 | Set-Content .\vss-extension-dev.json + } + catch { + Write-Host "Error loading the version from Azure DevOps Marketplace" + } # build the task # todo: up the version number diff --git a/vss-extension-dev.json b/vss-extension-dev.json index c689d35..d4cc82d 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -1,266 +1,288 @@ { - "manifestVersion": 1, - "id": "GHAzDoWidget-DEV", - "version": "0.2.209", - "public": false, - "name": "Advanced Security dashboard Widgets [DEV]", - "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", - "publisher": "RobBos", - "categories": [ - "Azure Boards", - "Azure Repos", - "Azure Pipelines" - ], - "scopes": [ - "vso.profile", - "vso.code", - "vso.advsec" + "manifestVersion": 1, + "id": "GHAzDoWidget-DEV", + "version": "0.2.246", + "public": false, + "name": "Advanced Security dashboard Widgets [DEV]", + "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", + "publisher": "RobBos", + "categories": [ + "Azure Boards", + "Azure Repos", + "Azure Pipelines" + ], + "scopes": [ + "vso.profile", + "vso.code", + "vso.advsec" + ], + "targets": [ + { + "id": "Microsoft.VisualStudio.Services.Cloud" + } + ], + "tags": [ + "Dashboards", + "GitHub Advanced Security", + "GHAS", + "GHAzDo", + "widget", + "Security alerts", + "Pull requests" + ], + "icons": { + "default": "img/logo.png" + }, + "content": { + "details": { + "path": "overview.md" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/rajbos/GHAzDo-widget" + }, + "bugs": { + "url": "https://github.com/rajbos/GHAzDo-widget/issues" + }, + "contributions": [ + { + "id": "GHAzDoWidget.PR-tab", + "type": "ms.vss-web.tab", + "targets": [ + "ms.vss-code-web.pr-tabs" ], - "targets": [ - { - "id": "Microsoft.VisualStudio.Services.Cloud" - } - ], - "tags": [ - "Dashboards", - "GitHub Advanced Security", - "GHAS", - "GHAzDo", - "widget", - "Security alerts", - "Pull requests" - ], - "icons": { - "default": "img/logo.png" + "properties": { + "name": "GHAzDo Alerts", + "title": "GHAzDo Alerts - Pull Requests", + "uri": "widgets/pr-tab/index.html", + "action": "Advanced Security Alerts", + "dynamic": true + } }, - "content": { - "details": { - "path": "overview.md" - } + { + "id": "GHAzDoWidget.build-info-tab", + "type": "ms.vss-build-web.build-results-tab", + "description": "Advanced Security Alerts", + "targets": [ + "ms.vss-build-web.build-results-view" + ], + "properties": { + "name": "Advanced Security Alerts", + "uri": "widgets/build-info/index.html", + "title": "GHAzDo Alerts - Build Info (ms.vss-build-web.build-results-view)", + "dynamic": true + } + }, + { + "id": "GHAzDoWidget", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Configuration" + ], + "properties": { + "name": "[DEV] GHAzDO alert information", + "description": "[DEV] Display the amount of active security alerts from GitHub Advanced Security", + "catalogIconUrl": "img/publogo.png", + "previewImageUrl": "img/preview.png", + "uri": "widgets/widgets/widget_2x1/widget_2x1.html", + "supportedSizes": [ + { + "rowSpan": 1, + "columnSpan": 2 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Configuration", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Configuration", + "description": "Configures GHAzDoWidget", + "uri": "widgets/widgets/widget_2x1/configuration_2x1.html" + } + }, + { + "id": "GHAzDoWidget.1x1", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Configuration_1x1" + ], + "properties": { + "name": "[DEV] GHAzDO single alert type information", + "description": "[DEV] Display the amount of active security alerts from GitHub Advanced Security for a single type of alert", + "catalogIconUrl": "img/publogo.png", + "previewImageUrl": "img/preview.png", + "uri": "widgets/widgets/widget_1x1/widget_1x1.html", + "supportedSizes": [ + { + "rowSpan": 1, + "columnSpan": 1 + }, + { + "rowSpan": 1, + "columnSpan": 2 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Configuration_1x1", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Configuration", + "description": "Configures GHAzDoWidget", + "uri": "widgets/widgets/widget_1x1/configuration_1x1.html" + } + }, + { + "id": "GHAzDoWidget.Chart", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Chart.Configuration" + ], + "properties": { + "name": "[DEV] GHAzDoWidget - Chart", + "description": "[DEV] A trend chart widget for Advanced Security alerts.", + "catalogIconUrl": "img/publogo.png", + "uri": "widgets/widgets/chart/chart.html", + "supportedSizes": [ + { + "rowSpan": 2, + "columnSpan": 2 + }, + { + "rowSpan": 2, + "columnSpan": 3 + }, + { + "rowSpan": 2, + "columnSpan": 4 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Chart.Configuration", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Chart Configuration", + "description": "Configures GHAzDoWidget.Chart", + "uri": "widgets/widgets/chart/configuration_2x2.html" + } + }, + { + "id": "GHAzDoWidget.TestingWidget", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog" + ], + "properties": { + "name": "[DEV] GHAzDO testing widget", + "description": "[DEV] test stuff", + "catalogIconUrl": "img/publogo.png", + "previewImageUrl": "img/preview.png", + "uri": "widgets/widgets/testing_widget/testing.html", + "supportedSizes": [ + { + "rowSpan": 4, + "columnSpan": 3 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Hub", + "type": "ms.vss-web.hub", + "description": "[DEV] GHAzDO Hub", + "targets": [ + "ms.vss-work-web.work-hub-group", + "ms.vss-code-web.code-hub-group" + ], + "properties": { + "name": "Advanced Security Dashboard", + "uri": "widgets/widgets/hub/hub.html", + "iconName": "Shield", + "order": 99 + } + }, + { + "id": "GHAzDoDependencyReviewTask", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "dependencyReviewTask" + } + } + ], + "files": [ + { + "path": "widgets/widgets", + "addressable": true + }, + { + "path": "widgets/build-info", + "addressable": true + }, + { + "path": "widgets/pr-tab", + "addressable": true + }, + { + "path": "widgets/library.js", + "addressable": true + }, + { + "path": "widgets/styles.css", + "addressable": true + }, + { + "path": "img", + "addressable": true + }, + { + "path": "dependencyReviewTask/index.js", + "addressable": true }, - "repository": { - "type": "git", - "url": "https://github.com/rajbos/GHAzDo-widget" + { + "path": "dependencyReviewTask/task.json", + "addressable": true }, - "bugs": { - "url": "https://github.com/rajbos/GHAzDo-widget/issues" + { + "path": "dependencyReviewTask/node_modules/", + "addressable": true }, - "contributions": [ - { - "id": "GHAzDoWidget.PR-tab", - "type": "ms.vss-web.tab", - "targets": [ - "ms.vss-code-web.pr-tabs" - ], - "properties": { - "name": "GHAzDo Alerts", - "title": "GHAzDo Alerts - Pull Requests", - "uri": "widgets/pr-tab/index.html", - "action": "Advanced Security Alerts", - "dynamic": true - } - }, - { - "id": "GHAzDoWidget.build-info-tab", - "type": "ms.vss-build-web.build-results-tab", - "description": "Advanced Security Alerts", - "targets": [ - "ms.vss-build-web.build-results-view" - ], - "properties": { - "name": "Advanced Security Alerts", - "uri": "widgets/build-info/index.html", - "title":"GHAzDo Alerts - Build Info (ms.vss-build-web.build-results-view)", - "dynamic": true - } - }, - { - "id": "GHAzDoWidget", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Configuration" - ], - "properties": { - "name": "[DEV] GHAzDO alert information", - "description": "[DEV] Display the amount of active security alerts from GitHub Advanced Security", - "catalogIconUrl": "img/publogo.png", - "previewImageUrl": "img/preview.png", - "uri": "widgets/widgets/widget_2x1/widget_2x1.html", - "supportedSizes": [ - { - "rowSpan": 1, - "columnSpan": 2 - } - ], - "supportedScopes": ["project_team"] - } - }, - { - "id": "GHAzDoWidget.Configuration", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Configuration", - "description": "Configures GHAzDoWidget", - "uri": "widgets/widgets/widget_2x1/configuration_2x1.html" - } - }, - { - "id": "GHAzDoWidget.1x1", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Configuration_1x1" - ], - "properties": { - "name": "[DEV] GHAzDO single alert type information", - "description": "[DEV] Display the amount of active security alerts from GitHub Advanced Security for a single type of alert", - "catalogIconUrl": "img/publogo.png", - "previewImageUrl": "img/preview.png", - "uri": "widgets/widgets/widget_1x1/widget_1x1.html", - "supportedSizes": [ - { - "rowSpan": 1, - "columnSpan": 1 - }, - { - "rowSpan": 1, - "columnSpan": 2 - }], - "supportedScopes": ["project_team"] - } - }, - { - "id": "GHAzDoWidget.Configuration_1x1", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Configuration", - "description": "Configures GHAzDoWidget", - "uri": "widgets/widgets/widget_1x1/configuration_1x1.html" - } - }, - { - "id": "GHAzDoWidget.Chart", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Chart.Configuration" - ], - "properties": { - "name": "[DEV] GHAzDoWidget - Chart", - "description": "[DEV] A trend chart widget for Advanced Security alerts.", - "catalogIconUrl": "img/publogo.png", - "uri": "widgets/widgets/chart/chart.html", - "supportedSizes": [ - { - "rowSpan": 2, - "columnSpan": 2 - }, - { - "rowSpan": 2, - "columnSpan": 3 - }, - { - "rowSpan": 2, - "columnSpan": 4 - } - ], - "supportedScopes": [ - "project_team" - ] - } - }, - { - "id": "GHAzDoWidget.Chart.Configuration", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Chart Configuration", - "description": "Configures GHAzDoWidget.Chart", - "uri": "widgets/widgets/chart/configuration_2x2.html" - } - }, - { - "id": "GHAzDoWidget.TestingWidget", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog" - ], - "properties": { - "name": "[DEV] GHAzDO testing widget", - "description": "[DEV] test stuff", - "catalogIconUrl": "img/publogo.png", - "previewImageUrl": "img/preview.png", - "uri": "widgets/widgets/testing_widget/testing.html", - "supportedSizes": [ - { - "rowSpan": 4, - "columnSpan": 3 - } - ], - "supportedScopes": ["project_team"] - } - }, - { - "id": "GHAzDoWidget.Hub", - "type": "ms.vss-web.hub", - "description": "[DEV] GHAzDO Hub", - "targets": [ - "ms.vss-work-web.work-hub-group", - "ms.vss-code-web.code-hub-group" - ], - "properties": { - "name": "Advanced Security Dashboard", - "uri": "widgets/widgets/hub/hub.html", - "iconName": "Shield", - "order": 99 - } - }, - { - "id": "GHAzDoDependencyReviewTask", - "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], - "properties": { - "name": "dependencyReviewTask" - } - } - ], - "files": [ - { - "path": "widgets/widgets", "addressable": true - }, - { - "path": "widgets/build-info", "addressable": true - }, - { - "path": "widgets/pr-tab", "addressable": true - }, - { - "path": "widgets/library.js", "addressable": true - }, - { - "path": "widgets/styles.css", "addressable": true - }, - { - "path": "img", "addressable": true - }, - { - "path": "dependencyReviewTask/index.js", "addressable": true - }, - { - "path": "dependencyReviewTask/task.json", "addressable": true - }, - { - "path": "dependencyReviewTask/node_modules/", "addressable": true - }, - { - "path": "widgets/node_modules/vss-web-extension-sdk/lib", - "addressable": true, - "packagePath": "lib" - } - ] -} \ No newline at end of file + { + "path": "widgets/node_modules/vss-web-extension-sdk/lib", + "addressable": true, + "packagePath": "lib" + } + ] +} diff --git a/widgets/library.js b/widgets/library.js index 8eafa9c..5c49e6d 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -1,3 +1,7 @@ +// global variables +const areaName = "AdvancedSecurity" // old: 'AdvancedSecurity', new: 'alerts' todo: rename to alerts when CORS issues are fixed +const apiVersion = "7.2-preview.1" + function getAuthHeader() { return new Promise((resolve, reject) => { VSS.require(["VSS/Authentication/Services"], function( @@ -76,7 +80,7 @@ function fillSelectRepoDropdown(dropDown, repos) { // sort the repo alphabetically repos.sort((a, b) => a.name.localeCompare(b.name)); repos.forEach(r => { - $repoDropdown.append(``); + dropDown.append(``); }); } @@ -107,7 +111,7 @@ async function getAlertsForRepo(organization, projectName, repoId) { try { // first check if GHAzDo is enabled or not - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/Management/repositories/${repoId}/enablement?api-version=7.2-preview.1` + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/management/repositories/${repoId}/enablement?api-version=${apiVersion}`; //const featuresEnabledResult = await authenticatedGet(url); //authenticatedGet(url).then(featuresEnabledResult => { @@ -117,7 +121,8 @@ async function getAlertsForRepo(organization, projectName, repoId) { // } // todo: use pagination option, now: get the first 5000 alerts - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=7.2-preview.1`; + console.log(`Getting alerts for repo [${repoId}]`); + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/${areaName}/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=${apiVersion}`; //consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //authenticatedGet(url).then(alertResult => { @@ -154,7 +159,7 @@ async function getAlertsTrendLines(organization, projectName, repoId) { consoleLog(`getAlertsTrend for organization [${organization}], project [${projectName}], repo [${repoId}]`); try { - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&api-version=7.2-preview.1`; + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/${areaName}/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&api-version=${apiVersion}`; consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //consoleLog('alertResult: ' + JSON.stringify(alertResult)); @@ -434,7 +439,7 @@ async function getAlertSeverityCounts(organization, projectName, repoId, alertTy ]; try { // todo: filter on alertType - url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/alerts/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.alertType=${alertType.value}&criteria.states=1&api-version=7.2-preview.1`; + url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/${areaName}/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.alertType=${alertType.value}&criteria.states=1&api-version=${apiVersion}`; //consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); //consoleLog('alertResult: ' + JSON.stringify(alertResult)); From 6eda68da8b0ac8e9cf08b8081b1919c0e402e783 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Nov 2023 14:56:57 -0800 Subject: [PATCH 14/16] Updated advanced security review task --- dependencyReviewTask/index.ts | 27 ++++++++++++--------------- dependencyReviewTask/task.json | 2 +- img/dependencyReviewTask.png | Bin 0 -> 63022 bytes overview.md | 10 ++++++++++ vss-extension-dev.json | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 img/dependencyReviewTask.png diff --git a/dependencyReviewTask/index.ts b/dependencyReviewTask/index.ts index 7117bf6..597810f 100644 --- a/dependencyReviewTask/index.ts +++ b/dependencyReviewTask/index.ts @@ -35,7 +35,7 @@ async function getAlerts( alertType: number ) { - if (!(alertType == 1 || alertType == 2)) { + if (!(alertType == 1 || alertType == 3)) { console.log(`Error loading alerts for branch [${branchName}] with alertType [${alertType}]`) return null } @@ -70,16 +70,10 @@ async function run() { // todo: convert to some actual value | boolean setting, for example severity score or switch between Dependency and CodeQL alerts const scanForDependencyAlerts : string | undefined = tl.getInput('DepedencyAlertsScan', true) - console.log(`scanForDependencyAlerts: ${scanForDependencyAlerts}`) - if (scanForDependencyAlerts !== 'true') { - // todo? - } + tl.debug(`scanForDependencyAlerts setting value: ${scanForDependencyAlerts}`) const scanForCodeScanningAlerts : string | undefined = tl.getInput('CodeScanningAlerts', true) - console.log(`scanForCodeScanningAlerts: ${scanForCodeScanningAlerts}`) - if (scanForCodeScanningAlerts !== 'true') { - // todo? - } + tl.debug(`scanForCodeScanningAlerts setting value: ${scanForCodeScanningAlerts}`) const token = getSystemAccessToken() const authHandler = getHandlerFromToken(token) @@ -144,7 +138,8 @@ async function checkAlertsForType( alertType: number, sourceBranchName: string, targetBranchName: string -): Promise<{newAlertsFound: boolean, message: string}> { +): Promise<{newAlertsFound: boolean, message: string}> +{ const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName, alertType) const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName, alertType) @@ -152,15 +147,15 @@ async function checkAlertsForType( tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`) let alertTypeString = `Dependency` - if (alertType == 2) { + if (alertType == 3) { alertTypeString = `Code scanning` } - if (sourceBranchResponse.result.count == 0) { - console.log(`No alerts found for this branch for alert type [${alertTypeString}]`) + if (!sourceBranchResponse || sourceBranchResponse.result.count == 0) { + console.log(`No alerts found for this branch [${sourceBranchName}] for alert type [${alertTypeString}]`) //tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`) - return {newAlertsFound: false, message: ``} + return { newAlertsFound: false, message: `` } } else { // check by result.alertId if there is a new alert or not (so alert not in targetBranch) @@ -184,7 +179,9 @@ async function checkAlertsForType( // and show them: const specificAlertMessage = `- ${alertId}: ${alertTitle}, url: ${alertUrl}` console.log(specificAlertMessage) - message += `\\n${specificAlertMessage}` // todo: check if this new line actually works :-) + message += `\r\n${specificAlertMessage}` // todo: check if this new line actually works :-) + // tested \\n --> did not work + // tested \\r\\n --> did not work } return {newAlertsFound: true, message: message} } diff --git a/dependencyReviewTask/task.json b/dependencyReviewTask/task.json index d57b941..3822ca6 100644 --- a/dependencyReviewTask/task.json +++ b/dependencyReviewTask/task.json @@ -10,7 +10,7 @@ "version": { "Major": 0, "Minor": 1, - "Patch": 25 + "Patch": 34 }, "instanceNameFormat": "Echo $(samplestring)", "inputs": [ diff --git a/img/dependencyReviewTask.png b/img/dependencyReviewTask.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd6b0e041fcf38b365f7e1882eda378189b9722 GIT binary patch literal 63022 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 HuRr`BQFt^_ literal 0 HcmV?d00001 diff --git a/overview.md b/overview.md index f79020a..9347d03 100644 --- a/overview.md +++ b/overview.md @@ -9,6 +9,16 @@ Install from the marketplace: https://marketplace.visualstudio.com/items?itemNam ![Screenshot of the all the widgets with alert count for dependencies, secrets, and code scanning](/img/overview_600.png) +## Pipeline tasks +* Advanced-Security-Review: lets you check the pull request for newly introduced alerts from Dependency Scanning or Code Scanning (configurable). If new alerts are introduced, the task will fail. +> Note: +> * Needs to run in a PR context, or it will be skipped. +> * The DependencyScanTask needs to have run on the source branch of the PR **before the PR check runs** + +### Advanced Security Review Output +If new alerts are found in the source branch (compared to the target branch), the task will fail: +![Screenshot of the failure message of the review task](/img/dependencyReviewTask.png) + ## GitHub repo Please report issues, feature request, and feedback here: https://github.com/rajbos/GHAzDo-widget. diff --git a/vss-extension-dev.json b/vss-extension-dev.json index d4cc82d..cd229e3 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "GHAzDoWidget-DEV", - "version": "0.2.246", + "version": "0.2.256", "public": false, "name": "Advanced Security dashboard Widgets [DEV]", "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", From 31beb774ad45c28cf7df76b466e4eb65dda60cd7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 29 Nov 2023 22:20:42 +0100 Subject: [PATCH 15/16] temp checkin --- vss-extension-dev.json | 2 +- widgets/library.js | 8 ++-- widgets/widgets/widget_1x1/widget_1x1.html | 17 +++++--- widgets/widgets/widget_1x1/widget_1x1.js | 45 ++++++++++++++++++---- widgets/widgets/widget_2x1/widget_2x1.html | 2 +- z-status.txt | 3 -- 6 files changed, 56 insertions(+), 21 deletions(-) diff --git a/vss-extension-dev.json b/vss-extension-dev.json index cd229e3..e18d10a 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "GHAzDoWidget-DEV", - "version": "0.2.256", + "version": "0.2.270", "public": false, "name": "Advanced Security dashboard Widgets [DEV]", "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", diff --git a/widgets/library.js b/widgets/library.js index 5c49e6d..a9ca845 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -84,13 +84,14 @@ function fillSelectRepoDropdown(dropDown, repos) { }); } -async function getAlerts(organization, projectName, repoId) { +async function getAlerts(organization, projectName, repoId, repos) { if (repoId) { // run normally for a single repo return await getAlertsForRepo(organization, projectName, repoId) } else { // todo: run for ALL repositories in the current project + // load all repos in the project return { count: -1, dependencyAlerts: -1, @@ -361,6 +362,7 @@ async function getProjects(VSS, Service, CoreRestClient) { async function getRepos(VSS, Service, GitWebApi, projectName, useCache = true) { + consoleLog(`inside getRepos`); const webContext = VSS.getWebContext(); const project = webContext.project; let projectNameForSearch = projectName ? projectName : project.name; @@ -376,7 +378,7 @@ async function getRepos(VSS, Service, GitWebApi, projectName, useCache = true) { try { const document = await getSavedDocument(VSS, documentCollection, documentId); consoleLog(`document inside getRepos: ${JSON.stringify(document)}`); - if (document || document.data.length > 0) { + if (document || document?.data?.length > 0) { consoleLog(`Loaded repos from document store. Last updated [${document.lastUpdated}]`); // get the data type of lastUpdated consoleLog(`typeof document.lastUpdated: ${typeof document.lastUpdated}`) @@ -398,7 +400,7 @@ async function getRepos(VSS, Service, GitWebApi, projectName, useCache = true) { } } - consoleLog(`Loading repositories from the API`); + consoleLog(`Loading repositories from the API for project [${projectNameForSearch}]`); try { const gitClient = Service.getClient(GitWebApi.GitHttpClient); let repos = await gitClient.getRepositories(projectNameForSearch); diff --git a/widgets/widgets/widget_1x1/widget_1x1.html b/widgets/widgets/widget_1x1/widget_1x1.html index ba1f6da..f73add2 100644 --- a/widgets/widgets/widget_1x1/widget_1x1.html +++ b/widgets/widgets/widget_1x1/widget_1x1.html @@ -13,11 +13,11 @@ }); VSS.require( - ["TFS/Dashboards/WidgetHelpers", "VSS/Context", "VSS/Authentication/Services"], - async function (WidgetHelpers, context) + ["VSS/Service", "TFS/Dashboards/WidgetHelpers", "VSS/Context", "TFS/VersionControl/GitRestClient", "TFS/Core/RestClient"], + async function (Service, WidgetHelpers, context, GitWebApi, RestClient) { WidgetHelpers.IncludeWidgetStyles(); - VSS.register("GHAzDoWidget.1x1", function () { + VSS.register("GHAzDoWidget.1x1", async function () { const webContext = VSS.getWebContext(); const project = webContext.project; const organization = webContext.account.name; @@ -29,15 +29,22 @@ consoleLog('project name: ' + projectName); consoleLog('organization name: ' + organization); + // log if vss, service, gitwebapi have a value or not + consoleLog(`vss: [${VSS}], GitWebApi: [${JSON.stringify(GitWebApi)}]`) + consoleLog(`GitWebApi.GitHttpClient: [${GitWebApi.GitHttpClient}]`); + + const repos = await getRepos(VSS, Service, GitWebApi, projectName, useCache = false); + consoleLog(`Found repos:[${repos.length}]`); + return { load: async function (widgetSettings) { - await loadWidget(widgetSettings, organization, projectName); + await loadWidget(widgetSettings, organization, projectName, VSS, Service, GitWebApi); return WidgetHelpers.WidgetStatusHelper.Success(); }, reload: async function (widgetSettings) { consoleLog('reload with widgetSettings: ' + JSON.stringify(widgetSettings)); - await loadWidget(widgetSettings, organization, projectName); + await loadWidget(widgetSettings, organization, projectName, VSS, Service, GitWebApi); return; } } diff --git a/widgets/widgets/widget_1x1/widget_1x1.js b/widgets/widgets/widget_1x1/widget_1x1.js index e0d3eba..abe92ec 100644 --- a/widgets/widgets/widget_1x1/widget_1x1.js +++ b/widgets/widgets/widget_1x1/widget_1x1.js @@ -1,9 +1,10 @@ -async function loadWidget(widgetSettings, organization, projectName) { +async function loadWidget(widgetSettings, organization, projectName, VSS, Service, GitWebApi) { consoleLog(`WidgetSettings inside loadWidget_1x1: ${JSON.stringify(widgetSettings)}`); consoleLog(`Running for organization [${organization}], projectName [${projectName}]`); // data contains a stringified json object, so we need to make a json object from it const data = JSON.parse(widgetSettings.customSettings.data); + consoleLog(`data from the widgetSettings: ${JSON.stringify(data)}`); // init with default values let alerts = { @@ -13,19 +14,43 @@ async function loadWidget(widgetSettings, organization, projectName) { }; let linkBase = 'https://dev.azure.com'; - if (data && data.repo) { - const repoName = data.repo; + if (data) { const repoId = data.repoId; - consoleLog(`loaded repoName from widgetSettings_1x1: [${repoName}] and id [${repoId}]`); + let repoName = ""; + if (data.repo) { + repoName = data.repo + consoleLog(`loaded repoName from widgetSettings_1x1: [${repoName}] and id [${repoId}]`); - // set the tile + alerts = await getAlerts(organization, projectName, repoId); + } + else { + // load alerts for ALL repos in the project + repoName = `${projectName}`; + // todo: load all + consoleLog(`loading alerts for all repos in the project [${repoName}]`); + + const repos = await getRepos(VSS, Service, GitWebApi, projectName, useCache = true) + for (let repoIndex in repos) { + const repo = repos[repoIndex]; + consoleLog(`loading alerts for repo [${repo.name}] with id [${repo.id}]`); + // call and let the promise handle the rest + const repoAlerts = await getAlerts(organization, projectName, repo.id) + + alerts.codeAlerts += repoAlerts.codeAlerts; + alerts.dependencyAlerts += repoAlerts.dependencyAlerts; + alerts.secretAlerts += repoAlerts.secretAlerts; + } + } + consoleLog('alerts: ' + JSON.stringify(alerts)); + + // remove %20 from the name so that it displays correctly + repoName = repoName.replace(/%20/g, ' '); //todo: support more of these weird characters + + // set the title var title = $('h2.ghazdo-title'); title.text(`${repoName}`); title.attr('title', repoName); - consoleLog(`title set to [${repoName}]`); - alerts = await getAlerts(organization, projectName, repoId); - consoleLog('alerts: ' + JSON.stringify(alerts)); // GHAS is only available on the SaaS version, so we can hardcode the domain linkBase = `https://dev.azure.com/${organization}/${projectName}/_git/${repoName}/alerts`; @@ -39,8 +64,12 @@ async function loadWidget(widgetSettings, organization, projectName) { let alertTypeToShow = 1 if (data && data.repoAlertType) { + consoleLog(`loaded repoAlertType from widgetSettings_1x1: [${data.repoAlertType}]`); alertTypeToShow = data.repoAlertType; } + else { + consoleLog(`repoAlertType not found in widgetSettings_1x1, defaulting to [${alertTypeToShow}]`); + } // set the alert count consoleLog(`Setting the alert count 1x1 for type ${alertTypeToShow}`); diff --git a/widgets/widgets/widget_2x1/widget_2x1.html b/widgets/widgets/widget_2x1/widget_2x1.html index 6c38fc1..92340c3 100644 --- a/widgets/widgets/widget_2x1/widget_2x1.html +++ b/widgets/widgets/widget_2x1/widget_2x1.html @@ -13,7 +13,7 @@ }); VSS.require( - ["TFS/Dashboards/WidgetHelpers", "VSS/Context", "VSS/Authentication/Services"], + ["TFS/Dashboards/WidgetHelpers", "VSS/Context"], async function (WidgetHelpers, context) { WidgetHelpers.IncludeWidgetStyles(); diff --git a/z-status.txt b/z-status.txt index f7c6f10..5a4c8a8 100644 --- a/z-status.txt +++ b/z-status.txt @@ -1,7 +1,4 @@ In this branch was added: -- start of selection option in the PR alert check task to switch between either/or 1. Dependency scanning alerts, 3. Code scanning alerts - - todo: check how a checkbox option here works - - todo: check if the reload works as well for this config screen - in the 1x1 widget, code was added to configure this widget for either 1 repo or ALL repos in the project - test loading the config, both in the config screen and the widget itself - write the loop to go over ALL repos in the org (might be already be available in another branch) From b22f9e83e5b73380bf4180f47b675b3b6c227308 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 30 Nov 2023 00:33:40 +0100 Subject: [PATCH 16/16] update extension with PR task support - release to PROD --- dependencyReviewTask/index.ts | 15 +- dependencyReviewTask/task.json | 2 +- make.ps1 | 44 ++- overview.md | 11 +- vss-extension-dev.json | 2 +- vss-extension.json | 372 ++++++++++++--------- widgets/library.js | 2 +- widgets/widgets/widget_1x1/widget_1x1.html | 8 +- 8 files changed, 260 insertions(+), 196 deletions(-) diff --git a/dependencyReviewTask/index.ts b/dependencyReviewTask/index.ts index 597810f..256cbac 100644 --- a/dependencyReviewTask/index.ts +++ b/dependencyReviewTask/index.ts @@ -36,11 +36,13 @@ async function getAlerts( ) { if (!(alertType == 1 || alertType == 3)) { - console.log(`Error loading alerts for branch [${branchName}] with alertType [${alertType}]`) + console.log(`Error loading alerts for branch [${branchName}] with unknown alertType [${alertType}]`) return null } - const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project}/_apis/AdvancedSecurity/repositories/${repository}/alerts?criteria.alertType=${alertType}&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true` + const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project.replace(" ", "%20")}/_apis/alert/repositories/${repository}/alerts?criteria.alertType=${alertType}&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true` + tl.debug(`Calling api with url: [${branchUrl}]`) + let branchResponse: IResponse try { @@ -143,6 +145,7 @@ async function checkAlertsForType( const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName, alertType) const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName, alertType) + // todo: check if response.statuscode === 404 and skip the rest, do report a warning tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`) tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`) @@ -151,7 +154,7 @@ async function checkAlertsForType( alertTypeString = `Code scanning` } - if (!sourceBranchResponse || sourceBranchResponse.result.count == 0) { + if (!sourceBranchResponse || sourceBranchResponse?.result?.count == 0) { console.log(`No alerts found for this branch [${sourceBranchName}] for alert type [${alertTypeString}]`) //tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`) @@ -170,11 +173,11 @@ async function checkAlertsForType( }); if (newAlertIds.length > 0) { - let message =`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new:` + let message =`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] for alert type [${alertTypeString}] of which [${newAlertIds.length}] are new:` console.log(message) for (const alertId of newAlertIds) { // get the alert details: - const alertUrl = `https://dev.azure.com/${orgSlug}/${project}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}` + const alertUrl = `https://dev.azure.com/${orgSlug}/${project.replace(" ", "%20")}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}` const alertTitle = sourceBranchResponse.result.value.find((alert) => {return alert.alertId == alertId;})?.title // and show them: const specificAlertMessage = `- ${alertId}: ${alertTitle}, url: ${alertUrl}` @@ -186,7 +189,7 @@ async function checkAlertsForType( return {newAlertsFound: true, message: message} } else { - const message = `Found no new alerts for the source branch [${sourceBranchName}]` + const message = `Found no new alerts for the source branch [${sourceBranchName}] for alert type [${alertTypeString}]` console.log(message) return {newAlertsFound: false, message: message} } diff --git a/dependencyReviewTask/task.json b/dependencyReviewTask/task.json index 3822ca6..204ab8c 100644 --- a/dependencyReviewTask/task.json +++ b/dependencyReviewTask/task.json @@ -10,7 +10,7 @@ "version": { "Major": 0, "Minor": 1, - "Patch": 34 + "Patch": 37 }, "instanceNameFormat": "Echo $(samplestring)", "inputs": [ diff --git a/make.ps1 b/make.ps1 index 4822991..1752268 100644 --- a/make.ps1 +++ b/make.ps1 @@ -1,6 +1,6 @@ # load the given arguments param( - [ValidateSet("provision", "build", "")] + [ValidateSet("provision", "build", "publish", "")] [string]$command = "provision", [int] $provisionCount = 100 ) @@ -263,8 +263,12 @@ function New-VSTSAuthenticationToken { return $accesstoken; } -if ("build" -eq $command) { - Write-Host "Building the dev version" +function Build { + param ( + [ValidateSet("dev", "prod")] + [string] $buildtype + ) + Write-Host "Building the [$buildtype] version" # check if $env:AZURE_DEVOPS_PAT has a value if ($null -eq $env:AZURE_DEVOPS_PAT) { @@ -272,21 +276,31 @@ if ("build" -eq $command) { exit } - # run the default: build the dev version + # default values for dev + $vsix = "vss-extension-dev.json" $extensionPrefix="RobBos.GHAzDoWidget-DEV-" - # delete all files with the name RobBos.GHAzDoWidget-DEV*.vsix + $extensionId = "GHAzDoWidget-DEV" + + if ($buildtype -eq "prod") { + # values for prod + $vsix = "vss-extension.json" + $extensionPrefix="RobBos.GHAzDoWidget-" + $extensionId = "GHAzDoWidget" + } + + # delete all files with the name prefix*.vsix Get-ChildItem -Path .\ -Filter $extensionPrefix*.vsix | Remove-Item -Force # get the last updated version for this extension from the server to make sure we are rolling forward try { - $output = $(tfx extension show --token $env:AZURE_DEVOPS_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) + $output = $(tfx extension show --token $env:AZURE_DEVOPS_PAT --vsix $vsix --publisher "RobBos" --extension-id $extensionId --output json | ConvertFrom-Json) $lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0] Write-Host "Last version: [$($lastVersion.version)] from server" # overwrite the version in the json file - $json = Get-Content .\vss-extension-dev.json | ConvertFrom-Json -Depth 10 + $json = Get-Content .\$vsix | ConvertFrom-Json -Depth 10 $json.version = $lastVersion.version # write the json file back - $json | ConvertTo-Json -Depth 10 | Set-Content .\vss-extension-dev.json + $json | ConvertTo-Json -Depth 10 | Set-Content .\$vsix } catch { Write-Host "Error loading the version from Azure DevOps Marketplace" @@ -301,15 +315,23 @@ if ("build" -eq $command) { Set-Location .. # package the whole extension - tfx extension create --manifest-globs vss-extension-dev.json --rev-version + tfx extension create --manifest-globs $vsix --rev-version # get the new version number from the json file - $json = Get-Content .\vss-extension-dev.json | ConvertFrom-Json + $json = Get-Content .\$vsix | ConvertFrom-Json $visx = "$extensionPrefix$($json.version).vsix" Write-Host "Publishing [$visx]" - tfx extension publish --vsix $visx --service-url https://marketplace.visualstudio.com --token "$($env:AZURE_DEVOPS_PAT)" + tfx extension publish --vsix $visx --service-url https://marketplace.visualstudio.com --token "$($env:AZURE_DEVOPS_PAT)" +} + +if ("build" -eq $command) { + Build -buildtype "dev" + exit +} +if ("publish" -eq $command) { + Build -buildtype "prod" exit } diff --git a/overview.md b/overview.md index 9347d03..f053cba 100644 --- a/overview.md +++ b/overview.md @@ -1,4 +1,4 @@ -Extension for Azure DevOps that shows the number of open security alerts for the configured repository. Please install it and let me know what you think! Create an issue for feedback or feature requests. +Extension for Azure DevOps that shows the number of open security alerts from GitHub Advanced Security for Azure DevOps, for the configured repository. Please install it and let me know what you think! Create an issue for feedback or feature requests. Install from the marketplace: https://marketplace.visualstudio.com/items?itemName=RobBos.GHAzDoWidget @@ -6,8 +6,11 @@ Install from the marketplace: https://marketplace.visualstudio.com/items?itemNam * Show all three alert counts in one widget in 2 by 1 layout * Split it into three separate widgets with just the single value you scan for (1x1 or 2x1) * Show a trend line of all alerts in the last 3 weeks (2x2,3x2,4x2) +* Show a pie chart with the distribution of alerts based on the severity level (2x2,3x2,4x2) -![Screenshot of the all the widgets with alert count for dependencies, secrets, and code scanning](/img/overview_600.png) +![Screenshot of the all the widgets with alert count for dependencies, secrets, and code scanning](/img/overview_600.png) + +> Note: only project level dashboards are supported at the moment. ## Pipeline tasks * Advanced-Security-Review: lets you check the pull request for newly introduced alerts from Dependency Scanning or Code Scanning (configurable). If new alerts are introduced, the task will fail. @@ -17,9 +20,7 @@ Install from the marketplace: https://marketplace.visualstudio.com/items?itemNam ### Advanced Security Review Output If new alerts are found in the source branch (compared to the target branch), the task will fail: -![Screenshot of the failure message of the review task](/img/dependencyReviewTask.png) +![Screenshot of the failure message of the review task](/img/dependencyReviewTask.png) ## GitHub repo Please report issues, feature request, and feedback here: https://github.com/rajbos/GHAzDo-widget. - -> Note: only project level dashboards are supported at the moment. \ No newline at end of file diff --git a/vss-extension-dev.json b/vss-extension-dev.json index e18d10a..2f0be87 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "GHAzDoWidget-DEV", - "version": "0.2.270", + "version": "0.2.278", "public": false, "name": "Advanced Security dashboard Widgets [DEV]", "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", diff --git a/vss-extension.json b/vss-extension.json index 24a00f7..e95ca1e 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -1,173 +1,211 @@ { - "manifestVersion": 1, - "id": "GHAzDoWidget", - "version": "0.0.1.8", - "public": true, - "name": "Advanced Security dashboard Widgets", - "description": "GitHub Advanced Security for Azure DevOps dashboard widgets", - "publisher": "RobBos", - "categories": ["Azure Boards"], - "scopes": [ - "vso.profile", - "vso.code", - "vso.advsec" + "manifestVersion": 1, + "id": "GHAzDoWidget", + "version": "0.0.1.11", + "public": true, + "name": "Advanced Security dashboard Widgets", + "description": "GitHub Advanced Security for Azure DevOps dashboard widgets", + "publisher": "RobBos", + "categories": [ + "Azure Boards" + ], + "scopes": [ + "vso.profile", + "vso.code", + "vso.advsec" + ], + "targets": [ + { + "id": "Microsoft.VisualStudio.Services.Cloud" + } + ], + "tags": [ + "Dashboards", + "GitHub Advanced Security", + "GHAS", + "GHAzDo", + "widget", + "Security alerts" + ], + "icons": { + "default": "img/logo.png" + }, + "content": { + "details": { + "path": "overview.md" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/rajbos/GHAzDo-widget" + }, + "bugs": { + "url": "https://github.com/rajbos/GHAzDo-widget/issues" + }, + "contributions": [ + { + "id": "GHAzDoWidget", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget.GHAzDoWidget.Configuration" ], - "targets": [ - { - "id": "Microsoft.VisualStudio.Services.Cloud" - } - ], - "tags": [ - "Dashboards", - "GitHub Advanced Security", - "GHAS", - "GHAzDo", - "widget", - "Security alerts" - ], - "icons": { - "default": "img/logo.png" + "properties": { + "name": "GHAzDO alert information", + "description": "Display the amount of active security alerts from GitHub Advanced Security", + "catalogIconUrl": "img/publogo.png", + "previewImageUrl": "img/preview.png", + "uri": "widgets/widgets/widget_2x1/widget_2x1.html", + "supportedSizes": [ + { + "rowSpan": 1, + "columnSpan": 2 + } + ], + "supportedScopes": [ + "project_team" + ] + } }, - "content": { - "details": { - "path": "overview.md" - } + { + "id": "GHAzDoWidget.Configuration", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Configuration", + "description": "Configures GHAzDoWidget", + "uri": "widgets/widgets/widget_2x1/configuration_2x1.html" + } + }, + { + "id": "GHAzDoWidget.1x1", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget.GHAzDoWidget.Configuration_1x1" + ], + "properties": { + "name": "GHAzDO single alert type information", + "description": "Display the amount of active security alerts from GitHub Advanced Security for a single type of alert", + "catalogIconUrl": "img/publogo.png", + "previewImageUrl": "img/preview.png", + "uri": "widgets/widgets/widget_1x1/widget_1x1.html", + "supportedSizes": [ + { + "rowSpan": 1, + "columnSpan": 1 + }, + { + "rowSpan": 1, + "columnSpan": 2 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Configuration_1x1", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Configuration", + "description": "Configures GHAzDoWidget", + "uri": "widgets/widgets/widget_1x1/configuration_1x1.html" + } + }, + { + "id": "GHAzDoWidget.Chart", + "type": "ms.vss-dashboards-web.widget", + "targets": [ + "ms.vss-dashboards-web.widget-catalog", + "RobBos.GHAzDoWidget.GHAzDoWidget.Chart.Configuration" + ], + "properties": { + "name": "GHAzDoWidget - Chart", + "description": "A trend chart widget for Advanced Security alerts.", + "catalogIconUrl": "img/publogo.png", + "uri": "widgets/widgets/chart/chart.html", + "supportedSizes": [ + { + "rowSpan": 2, + "columnSpan": 2 + }, + { + "rowSpan": 2, + "columnSpan": 3 + }, + { + "rowSpan": 2, + "columnSpan": 4 + } + ], + "supportedScopes": [ + "project_team" + ] + } + }, + { + "id": "GHAzDoWidget.Chart.Configuration", + "type": "ms.vss-dashboards-web.widget-configuration", + "targets": [ + "ms.vss-dashboards-web.widget-configuration" + ], + "properties": { + "name": "GHAzDoWidget Chart Configuration", + "description": "Configures GHAzDoWidget.Chart", + "uri": "widgets/widgets/chart/configuration_2x2.html" + } + }, + { + "id": "GHAzDoDependencyReviewTask", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "dependencyReviewTask" + } + } + ], + "files": [ + { + "path": "widgets/widgets", + "addressable": true + }, + { + "path": "widgets/library.js", + "addressable": true + }, + { + "path": "widgets/styles.css", + "addressable": true + }, + { + "path": "img", + "addressable": true + }, + { + "path": "dependencyReviewTask/index.js", + "addressable": true }, - "repository": { - "type": "git", - "url": "https://github.com/rajbos/GHAzDo-widget" + { + "path": "dependencyReviewTask/task.json", + "addressable": true }, - "bugs": { - "url": "https://github.com/rajbos/GHAzDo-widget/issues" + { + "path": "dependencyReviewTask/node_modules/", + "addressable": true }, - "contributions": [ - { - "id": "GHAzDoWidget", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget.GHAzDoWidget.Configuration" - ], - "properties": { - "name": "GHAzDO alert information", - "description": "Display the amount of active security alerts from GitHub Advanced Security", - "catalogIconUrl": "img/publogo.png", - "previewImageUrl": "img/preview.png", - "uri": "widgets/widget_2x1/widget_2x1.html", - "supportedSizes": [ - { - "rowSpan": 1, - "columnSpan": 2 - } - ], - "supportedScopes": ["project_team"] - } - }, - { - "id": "GHAzDoWidget.Configuration", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Configuration", - "description": "Configures GHAzDoWidget", - "uri": "widgets/widget_2x1/configuration_2x1.html" - } - }, - { - "id": "GHAzDoWidget.1x1", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget.GHAzDoWidget.Configuration_1x1" - ], - "properties": { - "name": "GHAzDO single alert type information", - "description": "Display the amount of active security alerts from GitHub Advanced Security for a single type of alert", - "catalogIconUrl": "img/publogo.png", - "previewImageUrl": "img/preview.png", - "uri": "widgets/widget_1x1/widget_1x1.html", - "supportedSizes": [ - { - "rowSpan": 1, - "columnSpan": 1 - }, - { - "rowSpan": 1, - "columnSpan": 2 - } - ], - "supportedScopes": ["project_team"] - } - }, - { - "id": "GHAzDoWidget.Configuration_1x1", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Configuration", - "description": "Configures GHAzDoWidget", - "uri": "widgets/widget_1x1/configuration_1x1.html" - } - }, - { - "id": "GHAzDoWidget.Chart", - "type": "ms.vss-dashboards-web.widget", - "targets": [ - "ms.vss-dashboards-web.widget-catalog", - "RobBos.GHAzDoWidget.GHAzDoWidget.Chart.Configuration" - ], - "properties": { - "name": "GHAzDoWidget - Chart", - "description": "A trend chart widget for Advanced Security alerts.", - "catalogIconUrl": "img/publogo.png", - "uri": "widgets/chart/chart.html", - "supportedSizes": [ - { - "rowSpan": 2, - "columnSpan": 2 - }, - { - "rowSpan": 2, - "columnSpan": 3 - }, - { - "rowSpan": 2, - "columnSpan": 4 - } - ], - "supportedScopes": [ - "project_team" - ] - } - }, - { - "id": "GHAzDoWidget.Chart.Configuration", - "type": "ms.vss-dashboards-web.widget-configuration", - "targets": [ "ms.vss-dashboards-web.widget-configuration" ], - "properties": { - "name": "GHAzDoWidget Chart Configuration", - "description": "Configures GHAzDoWidget.Chart", - "uri": "widgets/chart/configuration_2x2.html" - } - } - ], - "files": [ - { - "path": "widgets", "addressable": true - }, - { - "path": "library.js", "addressable": true - }, - { - "path": "styles.css", "addressable": true - }, - { - "path": "img", "addressable": true - }, - { - "path": "node_modules/vss-web-extension-sdk/lib", - "addressable": true, - "packagePath": "lib" - } - ] -} \ No newline at end of file + { + "path": "widgets/node_modules/vss-web-extension-sdk/lib", + "addressable": true, + "packagePath": "lib" + } + ] +} diff --git a/widgets/library.js b/widgets/library.js index a9ca845..245325c 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -1,5 +1,5 @@ // global variables -const areaName = "AdvancedSecurity" // old: 'AdvancedSecurity', new: 'alerts' todo: rename to alerts when CORS issues are fixed +const areaName = "alert" // old: 'AdvancedSecurity', new: 'alert' todo: rename to alerts when CORS issues are fixed const apiVersion = "7.2-preview.1" function getAuthHeader() { diff --git a/widgets/widgets/widget_1x1/widget_1x1.html b/widgets/widgets/widget_1x1/widget_1x1.html index f73add2..204e92c 100644 --- a/widgets/widgets/widget_1x1/widget_1x1.html +++ b/widgets/widgets/widget_1x1/widget_1x1.html @@ -30,11 +30,11 @@ consoleLog('organization name: ' + organization); // log if vss, service, gitwebapi have a value or not - consoleLog(`vss: [${VSS}], GitWebApi: [${JSON.stringify(GitWebApi)}]`) - consoleLog(`GitWebApi.GitHttpClient: [${GitWebApi.GitHttpClient}]`); + // consoleLog(`vss: [${VSS}], GitWebApi: [${JSON.stringify(GitWebApi)}]`) + // consoleLog(`GitWebApi.GitHttpClient: [${GitWebApi.GitHttpClient}]`); - const repos = await getRepos(VSS, Service, GitWebApi, projectName, useCache = false); - consoleLog(`Found repos:[${repos.length}]`); + // const repos = await getRepos(VSS, Service, GitWebApi, projectName, useCache = false); + // consoleLog(`Found repos:[${repos.length}]`); return { load: async function (widgetSettings) {