diff --git a/.github/workflows/handle-versioning-accross-branches.yml b/.github/workflows/handle-versioning-accross-branches.yml new file mode 100644 index 0000000..6ef5cfa --- /dev/null +++ b/.github/workflows/handle-versioning-accross-branches.yml @@ -0,0 +1,82 @@ +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@v4 + with: + fetch-depth: 0 + + - uses: git-actions/set-user@v1 + + - name: Prevent branch warnings + run: | + # 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 + 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 "GHAzDoWidget-DEV" --output json | ConvertFrom-Json) + $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('.') + 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)')) { + Write-Host "Checkout the branch [$branch]" + git checkout $branch + + # 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 + 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 from PR task.json: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" + } + } + + 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)]" + Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the PR check extension: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]" diff --git a/dependencyReviewTask/index.ts b/dependencyReviewTask/index.ts index d08c391..256cbac 100644 --- a/dependencyReviewTask/index.ts +++ b/dependencyReviewTask/index.ts @@ -26,20 +26,35 @@ 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 == 3)) { + console.log(`Error loading alerts for branch [${branchName}] with unknown alertType [${alertType}]`) + return null + } + + 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 { - 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 +64,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) + tl.debug(`scanForDependencyAlerts setting value: ${scanForDependencyAlerts}`) + + const scanForCodeScanningAlerts : string | undefined = tl.getInput('CodeScanningAlerts', true) + tl.debug(`scanForCodeScanningAlerts setting value: ${scanForCodeScanningAlerts}`) + + 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 == 'true') { + alertType = 1 // Dependency Scanning alerts + const dependencyResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) + if (dependencyResult.newAlertsFound) { + errorString += dependencyResult.message + } } - console.log('Hello', inputString); - const token = getSystemAccessToken(); - const authHandler = getHandlerFromToken(token); - const uri = tl.getVariable("System.CollectionUri"); - const connection = new WebApi(uri, authHandler); + if (scanForCodeScanningAlerts == 'true') { + alertType = 3 // Code Scanning alerts + const codeScanningResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName) + if (codeScanningResult.newAlertsFound) { + errorString += codeScanningResult.message + } + } - 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'); + 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) + return + } - console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`); + if (errorString.length > 0) { + tl.setResult(tl.TaskResult.Failed, errorString) + } + } + catch (err: unknown) { + if (err instanceof Error) { + tl.setResult(tl.TaskResult.Failed, err.message) + } else { + tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred') + } + } - const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName); - const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName); + // everything worked, no new alerts found and at least one scanning option was enabled + tl.setResult(tl.TaskResult.Succeeded) +} - tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`); - tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`); +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) + + // 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)}`) + + let alertTypeString = `Dependency` + if (alertType == 3) { + alertTypeString = `Code scanning` + } - if (sourceBranchResponse.result.count == 0) { - console.log('No alerts found for this branch'); + 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; - } - 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`); + //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}] 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.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}` + console.log(specificAlertMessage) + 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} } - } - catch (err: unknown) { - if (err instanceof Error) { - tl.setResult(tl.TaskResult.Failed, err.message); - } else { - tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred'); + else { + const message = `Found no new alerts for the source branch [${sourceBranchName}] for alert type [${alertTypeString}]` + 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..204ab8c 100644 --- a/dependencyReviewTask/task.json +++ b/dependencyReviewTask/task.json @@ -1,26 +1,34 @@ { "$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": { "Major": 0, "Minor": 1, - "Patch": 23 + "Patch": 37 }, "instanceNameFormat": "Echo $(samplestring)", "inputs": [ { - "name": "samplestring", - "type": "string", - "label": "Sample String", - "defaultValue": "", + "name": "DepedencyAlertsScan", + "type": "boolean", + "label": "Fail on new dependency alerts", + "defaultValue": true, "required": true, - "helpMarkDown": "A sample string" + "helpMarkDown": "Fail the pipeline if there is a new dependency alert" + }, + { + "name": "CodeScanningAlerts", + "type": "boolean", + "label": "Fail on new code scanning alerts", + "defaultValue": true, + "required": true, + "helpMarkDown": "Fail the pipeline if there is a new code scanning alert" } ], "execution": { diff --git a/img/dependencyReviewTask.png b/img/dependencyReviewTask.png new file mode 100644 index 0000000..dbd6b0e Binary files /dev/null and b/img/dependencyReviewTask.png differ diff --git a/make.ps1 b/make.ps1 index fb6a0ff..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,11 +276,36 @@ 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 $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 .\$vsix | ConvertFrom-Json -Depth 10 + $json.version = $lastVersion.version + # write the json file back + $json | ConvertTo-Json -Depth 10 | Set-Content .\$vsix + } + catch { + Write-Host "Error loading the version from Azure DevOps Marketplace" + } + # build the task # todo: up the version number Set-Location .\dependencyReviewTask @@ -286,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 f79020a..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,10 +6,21 @@ 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. +> 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. - -> 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 c689d35..2f0be87 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.278", + "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/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 8e2e998..245325c 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -1,3 +1,7 @@ +// global variables +const areaName = "alert" // old: 'AdvancedSecurity', new: 'alert' 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( @@ -69,9 +73,36 @@ function GetAlertTypeFromValue(value) { } } -async function getAlerts(organization, projectName, repoId) { - //consoleLog('getAlerts'); +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 => { + dropDown.append(``); + }); +} +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, + secretAlerts: -1, + codeAlerts: -1 + } + } +} + +async function getAlertsForRepo(organization, projectName, repoId) { + //consoleLog('getAlerts'); let values = { count: 0, dependencyAlerts: 0, @@ -81,7 +112,7 @@ async function getAlerts(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 => { @@ -91,7 +122,8 @@ async function getAlerts(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`; + 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 => { @@ -128,7 +160,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/${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)); @@ -330,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; @@ -345,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}`) @@ -367,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); @@ -408,7 +441,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/${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)); 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() }) }; diff --git a/widgets/widgets/widget_1x1/widget_1x1.html b/widgets/widgets/widget_1x1/widget_1x1.html index ba1f6da..204e92c 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 new file mode 100644 index 0000000..5a4c8a8 --- /dev/null +++ b/z-status.txt @@ -0,0 +1,7 @@ +In this branch was added: +- 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