Skip to content

Commit 3fedadb

Browse files
committed
created a function that checks if code scanning is enabled in github
1 parent 5a1084a commit 3fedadb

File tree

11 files changed

+201
-1
lines changed

11 files changed

+201
-1
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Currently supported providers are: [GitHub](#github), [Bitbucket Server](#bitbuc
6666
- [List Pull Request Labels](#list-pull-request-labels)
6767
- [Unlabel Pull Request](#unlabel-pull-request)
6868
- [Upload Code Scanning](#upload-code-scanning)
69+
- [Check If Code Scanning Is Enabled](#check-if-code-scanning-is-enabled)
6970
- [Download a File From a Repository](#download-a-file-from-a-repository)
7071
- [Create a branch](#create-branch)
7172
- [Allow workflows on organization](#allow-workflows)
@@ -836,6 +837,22 @@ scanResults := "results"
836837
sarifID, err := client.UploadCodeScanning(ctx, owner, repo, branch, scanResults)
837838
```
838839

840+
#### Check If Code Scanning Is Enabled
841+
842+
Notice - Code Scanning availability check is currently supported on GitHub only.
843+
844+
```go
845+
// Go context
846+
ctx := context.Background()
847+
// The account owner of the git repository
848+
owner := "user"
849+
// The name of the repository
850+
repo := "my_repo"
851+
852+
// Checks if code scanning is enabled for the repository
853+
enabled, err := client.IsCodeScanningEnabled(ctx, owner, repo)
854+
```
855+
839856
#### Download a File From a Repository
840857

841858
Note - This API is currently not supported for Bitbucket Cloud.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ require (
3030
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
3131
github.com/go-git/go-billy/v5 v5.6.2 // indirect
3232
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
33+
github.com/google/go-github/v53 v53.2.0 // indirect
3334
github.com/google/go-querystring v1.1.0 // indirect
3435
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
3536
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
4444
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
4545
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4646
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
47+
github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI=
48+
github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao=
4749
github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4=
4850
github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4=
4951
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=

vcsclient/azurerepos.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,10 @@ func (client *AzureReposClient) UploadSnapshotToDependencyGraph(ctx context.Cont
789789
return getUnsupportedInAzureError("uploading snapshot to dependency graph UI")
790790
}
791791

792+
func (client *AzureReposClient) IsCodeScanningEnabled(ctx context.Context, owner, repository string) (bool, error) {
793+
return false, getUnsupportedInAzureError("code scanning")
794+
}
795+
792796
func parsePullRequestDetails(client *AzureReposClient, pullRequest git.GitPullRequest, owner, repository string, withBody bool) PullRequestInfo {
793797
// Trim the branches prefix and get the actual branches name
794798
shortSourceName := plumbing.ReferenceName(*pullRequest.SourceRefName).Short()

vcsclient/bitbucketcloud.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,10 @@ func (client *BitbucketCloudClient) UploadSnapshotToDependencyGraph(ctx context.
747747
return errBitbucketUploadSnapshotToDependencyGraphNotSupported
748748
}
749749

750+
func (client *BitbucketCloudClient) IsCodeScanningEnabled(ctx context.Context, owner, repository string) (bool, error) {
751+
return false, errBitbucketCodeScanningNotSupported
752+
}
753+
750754
type pullRequestsResponse struct {
751755
Values []pullRequestsDetails `json:"values"`
752756
}

vcsclient/bitbucketcommon.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package vcsclient
22

33
import (
44
"fmt"
5+
"time"
6+
57
"github.com/jfrog/froggit-go/vcsutils"
68
"github.com/mitchellh/mapstructure"
7-
"time"
89
)
910

1011
const (
@@ -36,6 +37,8 @@ var (
3637
errBitbucketListAppReposNotSupported = fmt.Errorf("list app repositories is %s", notSupportedOnBitbucket)
3738
errBitbucketCreatePullRequestDetailedNotSupported = fmt.Errorf("creating pull request detailed is %s", notSupportedOnBitbucket)
3839
errBitbucketUploadSnapshotToDependencyGraphNotSupported = fmt.Errorf("uploading snapshot to dependency graph UI is %s", notSupportedOnBitbucket)
40+
errBitbucketIsCodeScanningEnabledNotSupported = fmt.Errorf("checking if code scanning is enabled is %s", notSupportedOnBitbucket)
41+
errBitbucketIsCodeScanningEnabledViaSARIFNotSupported = fmt.Errorf("checking if code scanning is enabled via SARIF upload is %s", notSupportedOnBitbucket)
3942
)
4043

4144
type BitbucketCommitInfo struct {

vcsclient/bitbucketserver.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,10 @@ func (client *BitbucketServerClient) UploadSnapshotToDependencyGraph(ctx context
939939
return errBitbucketUploadSnapshotToDependencyGraphNotSupported
940940
}
941941

942+
func (client *BitbucketServerClient) IsCodeScanningEnabled(ctx context.Context, owner, repository string) (bool, error) {
943+
return false, errBitbucketCodeScanningNotSupported
944+
}
945+
942946
func getBitbucketServerRepositoryVisibility(public bool) RepositoryVisibility {
943947
if public {
944948
return Public

vcsclient/github.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,3 +1715,64 @@ func convertToGitHubSnapshot(snapshot *SbomSnapshot) (*github.DependencyGraphSna
17151715
}
17161716
return ghSnapshot, nil
17171717
}
1718+
1719+
// IsCodeScanningEnabled checks if code scanning is enabled for a repository.
1720+
// Tests the SARIF upload endpoint with an empty body: 422=enabled, 403=disabled.
1721+
// TODO: Replace with proper GitHub API when available.
1722+
func (client *GitHubClient) IsCodeScanningEnabled(ctx context.Context, owner, repository string) (bool, error) {
1723+
err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
1724+
if err != nil {
1725+
return false, err
1726+
}
1727+
1728+
requestUrl := fmt.Sprintf("%srepos/%s/%s/code-scanning/sarifs", client.ghClient.BaseURL.String(), owner, repository)
1729+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestUrl, strings.NewReader(""))
1730+
if err != nil {
1731+
return false, err
1732+
}
1733+
1734+
req.Header.Set("Accept", "application/vnd.github+json")
1735+
req.Header.Set("Content-Type", "application/json")
1736+
if client.vcsInfo.Token != "" {
1737+
req.Header.Set("Authorization", "Bearer "+client.vcsInfo.Token)
1738+
}
1739+
1740+
resp, err := client.ghClient.Client().Do(req)
1741+
if err != nil {
1742+
return false, err
1743+
}
1744+
defer func() {
1745+
if closeErr := resp.Body.Close(); closeErr != nil {
1746+
client.logger.Warn("Failed to close response body:", closeErr)
1747+
}
1748+
}()
1749+
1750+
switch resp.StatusCode {
1751+
case http.StatusForbidden:
1752+
// Check if the error message mentions code scanning
1753+
body, readErr := io.ReadAll(resp.Body)
1754+
if readErr == nil {
1755+
bodyStr := strings.ToLower(string(body))
1756+
if strings.Contains(bodyStr, "code scanning") || strings.Contains(bodyStr, "advanced security") {
1757+
return false, nil
1758+
}
1759+
}
1760+
// If we can't read the body or it doesn't mention code scanning, log warning and return false
1761+
client.logger.Warn("Received 403 for code scanning check on", owner+"/"+repository, "but response doesn't clearly indicate code scanning status")
1762+
return false, nil
1763+
case http.StatusUnprocessableEntity:
1764+
// 422 means the endpoint exists and is processing our request, but the empty body is invalid
1765+
return true, nil
1766+
default:
1767+
// Any other status code is unexpected
1768+
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
1769+
// Success is unexpected with empty body
1770+
client.logger.Warn("Code scanning request unexpectedly succeeded for", owner+"/"+repository, "with status", resp.StatusCode, ". This should not happen.")
1771+
return true, nil
1772+
} else {
1773+
// Other error status codes
1774+
client.logger.Warn("Unexpected status code", resp.StatusCode, "when checking code scanning for", owner+"/"+repository)
1775+
return false, nil
1776+
}
1777+
}
1778+
}

vcsclient/github_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,3 +1608,97 @@ func TestGithubClient_UploadSnapshotToDependencyGraph(t *testing.T) {
16081608
err = createBadGitHubClient(t).UploadSnapshotToDependencyGraph(ctx, owner, repo1, &snapshot)
16091609
assert.Error(t, err)
16101610
}
1611+
1612+
func TestGitHubClient_IsCodeScanningEnabled(t *testing.T) {
1613+
ctx := context.Background()
1614+
1615+
t.Run("code scanning enabled - 422 response", func(t *testing.T) {
1616+
unprocessableResponse := []byte(`{
1617+
"message": "Invalid request",
1618+
"errors": [
1619+
{
1620+
"code": "invalid",
1621+
"field": "sarif"
1622+
}
1623+
]
1624+
}`)
1625+
1626+
client, cleanUp := createServerAndClientReturningStatus(t, vcsutils.GitHub, false, unprocessableResponse,
1627+
fmt.Sprintf("/repos/%s/%s/code-scanning/sarifs", owner, repo1), http.StatusUnprocessableEntity,
1628+
createGitHubHandler)
1629+
defer cleanUp()
1630+
1631+
enabled, err := client.IsCodeScanningEnabled(ctx, owner, repo1)
1632+
assert.NoError(t, err)
1633+
assert.True(t, enabled)
1634+
})
1635+
1636+
t.Run("code scanning disabled - 403 with code scanning message", func(t *testing.T) {
1637+
forbiddenResponse := []byte(`{
1638+
"message": "Advanced Security must be enabled for this repository to use code scanning.",
1639+
"documentation_url": "https://docs.github.com/rest/reference/code-scanning"
1640+
}`)
1641+
1642+
client, cleanUp := createServerAndClientReturningStatus(t, vcsutils.GitHub, false, forbiddenResponse,
1643+
fmt.Sprintf("/repos/%s/%s/code-scanning/sarifs", owner, repo1), http.StatusForbidden,
1644+
createGitHubHandler)
1645+
defer cleanUp()
1646+
1647+
enabled, err := client.IsCodeScanningEnabled(ctx, owner, repo1)
1648+
assert.NoError(t, err)
1649+
assert.False(t, enabled)
1650+
})
1651+
1652+
t.Run("generic forbidden - 403 without code scanning message", func(t *testing.T) {
1653+
genericForbiddenResponse := []byte(`{
1654+
"message": "Forbidden"
1655+
}`)
1656+
1657+
client, cleanUp := createServerAndClientReturningStatus(t, vcsutils.GitHub, false, genericForbiddenResponse,
1658+
fmt.Sprintf("/repos/%s/%s/code-scanning/sarifs", owner, repo1), http.StatusForbidden,
1659+
createGitHubHandler)
1660+
defer cleanUp()
1661+
1662+
enabled, err := client.IsCodeScanningEnabled(ctx, owner, repo1)
1663+
assert.NoError(t, err)
1664+
assert.False(t, enabled)
1665+
})
1666+
1667+
t.Run("unexpected success - 200 response", func(t *testing.T) {
1668+
successResponse := []byte(`{"message": "Success"}`)
1669+
1670+
client, cleanUp := createServerAndClientReturningStatus(t, vcsutils.GitHub, false, successResponse,
1671+
fmt.Sprintf("/repos/%s/%s/code-scanning/sarifs", owner, repo1), http.StatusOK,
1672+
createGitHubHandler)
1673+
defer cleanUp()
1674+
1675+
enabled, err := client.IsCodeScanningEnabled(ctx, owner, repo1)
1676+
assert.NoError(t, err)
1677+
assert.True(t, enabled) // Should return true but log a warning
1678+
})
1679+
1680+
t.Run("network error", func(t *testing.T) {
1681+
enabled, err := createBadGitHubClient(t).IsCodeScanningEnabled(ctx, owner, repo1)
1682+
assert.Error(t, err)
1683+
assert.False(t, enabled)
1684+
})
1685+
1686+
t.Run("invalid parameters", func(t *testing.T) {
1687+
client, cleanUp := createServerAndClientReturningStatus(t, vcsutils.GitHub, false, nil,
1688+
fmt.Sprintf("/repos/%s/%s/code-scanning/sarifs", owner, repo1), http.StatusUnprocessableEntity,
1689+
createGitHubHandler)
1690+
defer cleanUp()
1691+
1692+
// Test empty owner
1693+
enabled, err := client.IsCodeScanningEnabled(ctx, "", repo1)
1694+
assert.Error(t, err)
1695+
assert.False(t, enabled)
1696+
assert.Contains(t, err.Error(), "owner")
1697+
1698+
// Test empty repository
1699+
enabled, err = client.IsCodeScanningEnabled(ctx, owner, "")
1700+
assert.Error(t, err)
1701+
assert.False(t, enabled)
1702+
assert.Contains(t, err.Error(), "repository")
1703+
})
1704+
}

vcsclient/gitlab.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,10 @@ func (client *GitLabClient) UploadSnapshotToDependencyGraph(ctx context.Context,
827827
return errGitLabUploadSnapshotToDependencyGraphNotSupported
828828
}
829829

830+
func (client *GitLabClient) IsCodeScanningEnabled(ctx context.Context, owner, repository string) (bool, error) {
831+
return false, errGitLabCodeScanningNotSupported
832+
}
833+
830834
func getProjectID(owner, project string) string {
831835
return fmt.Sprintf("%s/%s", owner, project)
832836
}

0 commit comments

Comments
 (0)